From 33e8a0b1b230e4799f567af2e892bcc6aea23fb6 Mon Sep 17 00:00:00 2001 From: Elizabeth Okerio Date: Wed, 27 May 2020 00:59:20 +0300 Subject: [PATCH] entity navigation property support in complex types (#1743) * navigation property support in complex types * Fixed based on review comments Co-authored-by: elokerio --- src/Microsoft.OData.Client/ClientEdmModel.cs | 44 +++++++------ .../Microsoft.OData.Client.cs | 2 +- .../Parameterized.Microsoft.OData.Client.cs | 6 +- .../Microsoft.OData.Edm.cs | 2 +- .../Parameterized.Microsoft.OData.Edm.cs | 6 +- .../Schema/EdmNavigationProperty.cs | 47 ++++++++++++-- .../ComplexNavigationTests.cs | 65 +++++++++++++++++++ .../ClientCSharpRegressionTests.cs | 5 -- 8 files changed, 136 insertions(+), 41 deletions(-) create mode 100644 test/FunctionalTests/Microsoft.OData.Client.Tests/ComplexNavigations/ComplexNavigationTests.cs diff --git a/src/Microsoft.OData.Client/ClientEdmModel.cs b/src/Microsoft.OData.Client/ClientEdmModel.cs index 4c0a12ffbd..bef3e3a9c5 100644 --- a/src/Microsoft.OData.Client/ClientEdmModel.cs +++ b/src/Microsoft.OData.Client/ClientEdmModel.cs @@ -591,32 +591,34 @@ private IEdmProperty CreateEdmProperty(IEdmStructuredType declaringType, Propert propertyEdmType.TypeKind == EdmTypeKind.Collection, "Property kind should be Entity, Complex, Enum, Primitive or Collection."); - IEdmProperty edmProperty; + IEdmProperty edmProperty = null; bool isPropertyNullable = ClientTypeUtil.CanAssignNull(propertyInfo.PropertyType); if (propertyEdmType.TypeKind == EdmTypeKind.Entity || (propertyEdmType.TypeKind == EdmTypeKind.Collection && ((IEdmCollectionType)propertyEdmType).ElementType.TypeKind() == EdmTypeKind.Entity)) { - IEdmEntityType declaringEntityType = declaringType as IEdmEntityType; - if (declaringEntityType == null) + if (declaringType.TypeKind == EdmTypeKind.Entity || declaringType.TypeKind == EdmTypeKind.Complex) { - throw c.Error.InvalidOperation(c.Strings.ClientTypeCache_NonEntityTypeCannotContainEntityProperties(propertyInfo.Name, propertyInfo.DeclaringType.ToString())); - } + if (declaringType as IEdmEntityType == null && declaringType as IEdmComplexType == null) + { + throw c.Error.InvalidOperation(c.Strings.ClientTypeCache_NonEntityTypeOrNonComplexTypeCannotContainEntityProperties(propertyInfo.Name, propertyInfo.DeclaringType.ToString())); + } - // Create a navigation property representing one side of an association. - // The partner representing the other side exists only inside this property and is not added to the target entity type, - // so it should not cause any name collisions. - edmProperty = EdmNavigationProperty.CreateNavigationPropertyWithPartner( - ClientTypeUtil.GetServerDefinedName(propertyInfo), - propertyEdmType.ToEdmTypeReference(isPropertyNullable), - /*dependentProperties*/ null, - /*principalProperties*/ null, - /*containsTarget*/ false, - EdmOnDeleteAction.None, - "Partner", - declaringEntityType.ToEdmTypeReference(true), - /*partnerDependentProperties*/ null, - /*partnerPrincipalProperties*/ null, - /*partnerContainsTarget*/ false, - EdmOnDeleteAction.None); + // Create a navigation property representing one side of an association. + // The partner representing the other side exists only inside this property and is not added to the target entity type, + // so it should not cause any name collisions. + edmProperty = EdmNavigationProperty.CreateNavigationPropertyWithPartner( + ClientTypeUtil.GetServerDefinedName(propertyInfo), + propertyEdmType.ToEdmTypeReference(isPropertyNullable), + /*dependentProperties*/ null, + /*principalProperties*/ null, + /*containsTarget*/ false, + EdmOnDeleteAction.None, + "Partner", + declaringType.ToEdmTypeReference(true), + /*partnerDependentProperties*/ null, + /*partnerPrincipalProperties*/ null, + /*partnerContainsTarget*/ false, + EdmOnDeleteAction.None); + } } else { diff --git a/src/Microsoft.OData.Client/Microsoft.OData.Client.cs b/src/Microsoft.OData.Client/Microsoft.OData.Client.cs index ca11db7e84..d0ef244943 100644 --- a/src/Microsoft.OData.Client/Microsoft.OData.Client.cs +++ b/src/Microsoft.OData.Client/Microsoft.OData.Client.cs @@ -112,7 +112,7 @@ internal sealed class TextRes { internal const string ClientType_MultipleTypesWithSameName = "ClientType_MultipleTypesWithSameName"; internal const string WebUtil_TypeMismatchInCollection = "WebUtil_TypeMismatchInCollection"; internal const string WebUtil_TypeMismatchInNonPropertyCollection = "WebUtil_TypeMismatchInNonPropertyCollection"; - internal const string ClientTypeCache_NonEntityTypeCannotContainEntityProperties = "ClientTypeCache_NonEntityTypeCannotContainEntityProperties"; + internal const string ClientTypeCache_NonEntityTypeOrNonComplexTypeCannotContainEntityProperties = "ClientTypeCache_NonEntityTypeOrNonComplexTypeCannotContainEntityProperties"; internal const string DataServiceException_GeneralError = "DataServiceException_GeneralError"; internal const string Deserialize_GetEnumerator = "Deserialize_GetEnumerator"; internal const string Deserialize_Current = "Deserialize_Current"; diff --git a/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs b/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs index dfca345056..cb12747d51 100644 --- a/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs +++ b/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs @@ -736,10 +736,10 @@ internal static string WebUtil_TypeMismatchInNonPropertyCollection(object p0) { } /// - /// A string like "The property '{0}' is of entity type and it cannot be a property of the type '{1}', which is not of entity type. Only entity types can contain navigation properties." + /// A string like "The property '{0}' is of entity type and it cannot be a property of the type '{1}', which is not of entity type or complex type. Only entity types and complex types can contain navigation properties." /// - internal static string ClientTypeCache_NonEntityTypeCannotContainEntityProperties(object p0, object p1) { - return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientTypeCache_NonEntityTypeCannotContainEntityProperties, p0, p1); + internal static string ClientTypeCache_NonEntityTypeOrNonComplexTypeCannotContainEntityProperties(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientTypeCache_NonEntityTypeOrNonComplexTypeCannotContainEntityProperties, p0, p1); } /// diff --git a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs index 70523f3660..6200c3a4e9 100644 --- a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs +++ b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs @@ -25,7 +25,7 @@ internal sealed class EdmRes { internal const string EdmPath_UnexpectedKind = "EdmPath_UnexpectedKind"; internal const string Annotations_TypeMismatch = "Annotations_TypeMismatch"; internal const string Constructable_VocabularyAnnotationMustHaveTarget = "Constructable_VocabularyAnnotationMustHaveTarget"; - internal const string Constructable_EntityTypeOrCollectionOfEntityTypeExpected = "Constructable_EntityTypeOrCollectionOfEntityTypeExpected"; + internal const string Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected = "Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected"; internal const string Constructable_TargetMustBeStock = "Constructable_TargetMustBeStock"; internal const string TypeSemantics_CouldNotConvertTypeReference = "TypeSemantics_CouldNotConvertTypeReference"; internal const string EdmModel_CannotUseElementWithTypeNone = "EdmModel_CannotUseElementWithTypeNone"; diff --git a/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs b/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs index 2df1e90dab..111ad26c66 100644 --- a/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs +++ b/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs @@ -50,11 +50,11 @@ internal static string Constructable_VocabularyAnnotationMustHaveTarget { } /// - /// A string like "An entity type or a collection of an entity type is expected." + /// A string like "An entity type or a collection of an entity type or a complex type is expected." /// - internal static string Constructable_EntityTypeOrCollectionOfEntityTypeExpected { + internal static string Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected { get { - return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.Constructable_EntityTypeOrCollectionOfEntityTypeExpected); + return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected); } } diff --git a/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs b/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs index 1c22c644ec..d71570a0a3 100644 --- a/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs +++ b/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs @@ -181,17 +181,32 @@ public static EdmNavigationProperty CreateNavigationPropertyWithPartner( EdmUtil.CheckArgumentNull(propertyType, "propertyType"); EdmUtil.CheckArgumentNull(partnerPropertyName, "partnerPropertyName"); EdmUtil.CheckArgumentNull(partnerPropertyType, "partnerPropertyType"); - - IEdmEntityType declaringType = GetEntityType(partnerPropertyType); - if (declaringType == null) + IEdmStructuredType declaringType = null; + if (partnerPropertyType.Definition.TypeKind == EdmTypeKind.Entity) + { + declaringType = GetEntityType(partnerPropertyType) as IEdmEntityType; + if (declaringType == null) + { + throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected, "partnerPropertyType"); + } + } + else if (partnerPropertyType.Definition.TypeKind == EdmTypeKind.Complex) { - throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeExpected, "partnerPropertyType"); + declaringType = GetComplexType(partnerPropertyType) as IEdmComplexType; + if (declaringType == null) + { + throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected, "partnerPropertyType"); + } + } + else + { + throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected, "partnerPropertyType"); } IEdmEntityType partnerDeclaringType = GetEntityType(propertyType); if (partnerDeclaringType == null) { - throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeExpected, "propertyType"); + throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeOrComplexTypeExpected, "propertyType"); } EdmNavigationProperty end1 = new EdmNavigationProperty( @@ -225,8 +240,8 @@ public static EdmNavigationProperty CreateNavigationPropertyWithPartner( internal void SetPartner(IEdmNavigationProperty navigationProperty, IEdmPathExpression navigationPropertyPath) { Debug.Assert( - DeclaringType is IEdmEntityType, - "Partner info cannot be set for nav. property on a non-entity type."); + DeclaringType is IEdmEntityType || DeclaringType is IEdmComplexType, + "Partner info cannot be set for nav. property on a non-entity or non-complex type."); partner = navigationProperty; PartnerPath = navigationPropertyPath; } @@ -249,6 +264,24 @@ private static IEdmEntityType GetEntityType(IEdmTypeReference type) return entityType; } + private static IEdmComplexType GetComplexType(IEdmTypeReference type) + { + if (type.IsComplex()) + { + return (IEdmComplexType)type.Definition; + + } + else if (type.IsCollection()) + { + type = ((IEdmCollectionType)type.Definition).ElementType; + if (type.IsComplex()) + { + return (IEdmComplexType)type.Definition; + } + } + + return null; + } private static IEdmTypeReference CreateNavigationPropertyType(IEdmEntityType entityType, EdmMultiplicity multiplicity, string multiplicityParameterName) { diff --git a/test/FunctionalTests/Microsoft.OData.Client.Tests/ComplexNavigations/ComplexNavigationTests.cs b/test/FunctionalTests/Microsoft.OData.Client.Tests/ComplexNavigations/ComplexNavigationTests.cs new file mode 100644 index 0000000000..7943f3feea --- /dev/null +++ b/test/FunctionalTests/Microsoft.OData.Client.Tests/ComplexNavigations/ComplexNavigationTests.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Client.Metadata; +using Microsoft.OData.Edm; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.OData.Client.Tests.ComplexNavigations +{ + /// + ///tests to show that it is possible to have an entity navigation on a complex type. + /// + public class ComplexNavigationTests + { + /// + /// This test checks whether the declaring type kind of the navigation property created is complex. + /// + [Fact] + public void DeclaringTypeOFAnEntityNavigationCanBeAComplexType() + { + //Arrange + Type complexDeclaringType = typeof(Address); + Type entityNavigationType = typeof(City); + EdmTypeKind expectedDeclaringTypeKind = EdmTypeKind.Complex; + + //Act + ClientEdmModel clientEdmModel = new ClientEdmModel(ODataProtocolVersion.V401); + IEdmType edmTypeOfComplexDeclaringType = clientEdmModel.GetOrCreateEdmType(complexDeclaringType); + IEdmType edmTypeOfEntityNavigationType = clientEdmModel.GetOrCreateEdmType(entityNavigationType); + IEdmStructuredType entiyNavigationType = clientEdmModel.GetOrCreateEdmType(complexDeclaringType) as IEdmStructuredType; + EdmNavigationProperty edmNavigationProperty = EdmNavigationProperty.CreateNavigationPropertyWithPartner("City",ClientTypeUtil.ToEdmTypeReference(edmTypeOfEntityNavigationType, true),null,null,false, EdmOnDeleteAction.None, "Partner", ClientTypeUtil.ToEdmTypeReference(edmTypeOfComplexDeclaringType, true),null,null,false,EdmOnDeleteAction.None); + EdmTypeKind resultingDeclaringTypeKind = edmNavigationProperty.DeclaringType.TypeKind; + + //Assert + Assert.Equal(expectedDeclaringTypeKind, resultingDeclaringTypeKind); + } + } + + [EntityType] + [Key("UserName")] + public class Person + { + public string UserName { get; set; } + public Address Address { get; set; } + public List
Addresses { get; set; } + } + + [EntityType] + [Key("Name")] + public class City + { + public string Name { get; set; } + } + + public class Address + { + public string Road { get; set; } + public City City { get; set; } + } +} diff --git a/test/FunctionalTests/Tests/DataServices/UnitTests/ClientCSharpUnitTests/DataWebClientCSharp/ClientCSharpRegressionTests.cs b/test/FunctionalTests/Tests/DataServices/UnitTests/ClientCSharpUnitTests/DataWebClientCSharp/ClientCSharpRegressionTests.cs index 2802bf2553..696dadab4d 100644 --- a/test/FunctionalTests/Tests/DataServices/UnitTests/ClientCSharpUnitTests/DataWebClientCSharp/ClientCSharpRegressionTests.cs +++ b/test/FunctionalTests/Tests/DataServices/UnitTests/ClientCSharpUnitTests/DataWebClientCSharp/ClientCSharpRegressionTests.cs @@ -1568,11 +1568,6 @@ public void ClientTypeCacheError_LoadProperties() { ex = ex.InnerException; } - - Assert.AreEqual(DataServicesClientResourceUtil.GetString( - "ClientTypeCache_NonEntityTypeCannotContainEntityProperties", - "NavProp", - "AstoriaUnitTests.Tests.ClientCSharpRegressionTests+ClientTypeCacheError_NonEntityType"), ex.Message); } }