diff --git a/src/EFCore.Cosmos/Extensions/CosmosComplexCollectionBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosComplexCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..47463f61001 --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosComplexCollectionBuilderExtensions.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Cosmos-specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Azure Cosmos DB with EF Core for more information and examples. +/// +public static class CosmosComplexCollectionBuilderExtensions +{ + /// + /// Configures the property name that the complex collection is mapped to when stored as an embedded document. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The builder for the complex type being configured. + /// The name of the parent property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexCollectionBuilder ToJsonProperty( + this ComplexCollectionBuilder complexPropertyBuilder, + string? name) + { + complexPropertyBuilder.Metadata.SetJsonPropertyName(name); + return complexPropertyBuilder; + } + + /// + /// Configures the property name that the complex collection is mapped to when stored as an embedded document. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The builder for the complex type being configured. + /// The name of the parent property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexCollectionBuilder ToJsonProperty( + this ComplexCollectionBuilder complexPropertyBuilder, + string? name) + where TComplex : notnull + { + complexPropertyBuilder.Metadata.SetJsonPropertyName(name); + return complexPropertyBuilder; + } +} diff --git a/src/EFCore.Cosmos/Extensions/CosmosComplexCollectionTypePropertyBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosComplexCollectionTypePropertyBuilderExtensions.cs new file mode 100644 index 00000000000..d1a425ed8ac --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosComplexCollectionTypePropertyBuilderExtensions.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Cosmos-specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Azure Cosmos DB with EF Core for more information and examples. +/// +public static class CosmosComplexCollectionTypePropertyBuilderExtensions +{ + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexCollectionTypePropertyBuilder ToJsonProperty( + this ComplexCollectionTypePropertyBuilder propertyBuilder, + string name) + { + Check.NotEmpty(name); + + propertyBuilder.Metadata.SetJsonPropertyName(name); + + return propertyBuilder; + } + + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexCollectionTypePropertyBuilder ToJsonProperty( + this ComplexCollectionTypePropertyBuilder propertyBuilder, + string name) + => (ComplexCollectionTypePropertyBuilder)ToJsonProperty((ComplexCollectionTypePropertyBuilder)propertyBuilder, name); + + // Vector and fulltext properties are not supported on collection types +} diff --git a/src/EFCore.Cosmos/Extensions/CosmosComplexPropertyBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosComplexPropertyBuilderExtensions.cs new file mode 100644 index 00000000000..88e795e0795 --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosComplexPropertyBuilderExtensions.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Cosmos-specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Azure Cosmos DB with EF Core for more information and examples. +/// +public static class CosmosComplexPropertyBuilderExtensions +{ + /// + /// Configures the property name that the complex property is mapped to when stored as an embedded document. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The builder for the complex type being configured. + /// The name of the parent property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexPropertyBuilder ToJsonProperty( + this ComplexPropertyBuilder complexPropertyBuilder, + string? name) + { + complexPropertyBuilder.Metadata.SetJsonPropertyName(name); + return complexPropertyBuilder; + } + + /// + /// Configures the property name that the complex property is mapped to when stored as an embedded document. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The builder for the complex type being configured. + /// The name of the parent property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexPropertyBuilder ToJsonProperty( + this ComplexPropertyBuilder complexPropertyBuilder, + string? name) + where TComplex : notnull + { + complexPropertyBuilder.Metadata.SetJsonPropertyName(name); + return complexPropertyBuilder; + } +} diff --git a/src/EFCore.Cosmos/Extensions/CosmosComplexPropertyExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosComplexPropertyExtensions.cs new file mode 100644 index 00000000000..a512696d38e --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosComplexPropertyExtensions.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Complex property extension methods for Cosmos metadata. +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Azure Cosmos DB with EF Core for more information and examples. +/// +public static class CosmosComplexPropertyExtensions +{ + /// + /// Returns the property name that the property is mapped to when targeting Cosmos. + /// + /// The property. + /// Returns the property name that the property is mapped to when targeting Cosmos. + public static string GetJsonPropertyName(this IReadOnlyComplexProperty property) + => (string?)property[CosmosAnnotationNames.PropertyName] + ?? property.Name; + + /// + /// Sets the property name that the property is mapped to when targeting Cosmos. + /// + /// The property. + /// The name to set. + public static void SetJsonPropertyName(this IMutableComplexProperty property, string? name) + => property.SetOrRemoveAnnotation( + CosmosAnnotationNames.PropertyName, + Check.NullButNotEmpty(name)); +} diff --git a/src/EFCore.Cosmos/Extensions/CosmosComplexTypePrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosComplexTypePrimitiveCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..28b34a740ea --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosComplexTypePrimitiveCollectionBuilderExtensions.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Cosmos-specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Azure Cosmos DB with EF Core for more information and examples. +/// +public static class CosmosComplexTypePrimitiveCollectionBuilderExtensions +{ + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// + /// If an empty string is supplied, the property will not be persisted. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder ToJsonProperty( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string name) + { + Check.NotNull(name); + + primitiveCollectionBuilder.Metadata.SetJsonPropertyName(name); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder ToJsonProperty( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string name) + => (ComplexTypePrimitiveCollectionBuilder)ToJsonProperty((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, name); +} diff --git a/src/EFCore.Cosmos/Extensions/CosmosComplexTypePropertyBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosComplexTypePropertyBuilderExtensions.cs new file mode 100644 index 00000000000..65176fdb854 --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosComplexTypePropertyBuilderExtensions.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Cosmos-specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Azure Cosmos DB with EF Core for more information and examples. +/// +public static class CosmosComplexTypePropertyBuilderExtensions +{ + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// + /// If an empty string is supplied, the property will not be persisted. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder ToJsonProperty( + this ComplexTypePropertyBuilder propertyBuilder, + string name) + { + Check.NotNull(name); + + propertyBuilder.Metadata.SetJsonPropertyName(name); + + return propertyBuilder; + } + + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder ToJsonProperty( + this ComplexTypePropertyBuilder propertyBuilder, + string name) + => (ComplexTypePropertyBuilder)ToJsonProperty((ComplexTypePropertyBuilder)propertyBuilder, name); +} diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosSerializationUtilities.cs b/src/EFCore.Cosmos/Query/Internal/CosmosSerializationUtilities.cs index 3bb327b2ed9..8fdc08a938f 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosSerializationUtilities.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosSerializationUtilities.cs @@ -56,6 +56,11 @@ public static JToken SerializeObjectToComplexProperty(IComplexType type, object? { var jsonPropertyName = property.GetJsonPropertyName(); + if (string.IsNullOrEmpty(jsonPropertyName)) + { + continue; + } + var propertyValue = property.GetGetter().GetClrValue(value); #pragma warning disable EF1001 // Internal EF Core API usage. var providerValue = property.ConvertToProviderValue(propertyValue); @@ -77,7 +82,7 @@ public static JToken SerializeObjectToComplexProperty(IComplexType type, object? foreach (var complexProperty in type.GetComplexProperties()) { - var jsonPropertyName = complexProperty.Name; + var jsonPropertyName = complexProperty.GetJsonPropertyName(); var propertyValue = complexProperty.GetGetter().GetClrValue(value); if (propertyValue is null) { diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs index b727e42d12f..99519b67381 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs @@ -198,7 +198,7 @@ private BlockExpression CreateComplexPropertyAssignmentBlock(MemberExpression me Call( CosmosProjectionBindingRemovingExpressionVisitorBase.ToObjectWithSerializerMethodInfo.MakeGenericMethod(typeof(JObject)), Call(_parentJObject, CosmosProjectionBindingRemovingExpressionVisitorBase.GetItemMethodInfo, - Constant(complexProperty.Name)))); + Constant(complexProperty.GetJsonPropertyName())))); var materializeExpression = CreateComplexTypeMaterializeExpression(complexProperty, jObjectVariable); if (complexProperty.IsNullable) @@ -227,7 +227,7 @@ private BlockExpression CreateComplexCollectionAssignmentBlock(MemberExpression Call( CosmosProjectionBindingRemovingExpressionVisitorBase.ToObjectWithSerializerMethodInfo.MakeGenericMethod(typeof(JArray)), Call(_parentJObject, CosmosProjectionBindingRemovingExpressionVisitorBase.GetItemMethodInfo, - Constant(complexProperty.Name)))); + Constant(complexProperty.GetJsonPropertyName())))); var jObjectParameter = Parameter(typeof(JObject), "complexJObject" + _currentComplexIndex); var materializeExpression = CreateComplexTypeMaterializeExpression(complexProperty, jObjectParameter); diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectAccessExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectAccessExpression.cs index fedc94aeb0f..b437c8ce6f1 100644 --- a/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectAccessExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectAccessExpression.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Cosmos.Internal; -using Microsoft.EntityFrameworkCore.Metadata.Internal; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; @@ -42,7 +41,7 @@ public ObjectAccessExpression( break; case IComplexProperty complexProperty: structuralType = complexProperty.ComplexType; - propertyName = complexProperty.Name; + propertyName = complexProperty.GetJsonPropertyName(); break; default: throw new UnreachableException($"Unexpected structural property type: {structuralProperty.GetType().FullName}"); diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectArrayAccessExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectArrayAccessExpression.cs index dbfb1f8f855..8f12f964f98 100644 --- a/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectArrayAccessExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/Expressions/ObjectArrayAccessExpression.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Cosmos.Internal; -using Microsoft.EntityFrameworkCore.Metadata.Internal; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; @@ -44,7 +43,7 @@ public ObjectArrayAccessExpression( break; case IComplexProperty complexProperty: targetType = complexProperty.ComplexType; - propertyName = complexProperty.Name; + propertyName = complexProperty.GetJsonPropertyName(); break; default: throw new UnreachableException($"Unexpected structural property type: {structuralProperty.GetType().FullName}"); diff --git a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs index 93c3169b396..2f5f1cfdd6d 100644 --- a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs +++ b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs @@ -147,7 +147,7 @@ private JObject CreateDocument(IInternalEntry entry, ITypeBase structuralType, i foreach (var complexProperty in structuralType.GetComplexProperties()) { var embeddedValue = entry.GetCurrentValue(complexProperty); - var embeddedPropertyName = complexProperty.Name; + var embeddedPropertyName = complexProperty.GetJsonPropertyName(); if (embeddedValue == null) { document[embeddedPropertyName] = null; @@ -287,7 +287,7 @@ private JObject CreateDocument(IInternalEntry entry, ITypeBase structuralType, i foreach (var complexProperty in structuralType.GetComplexProperties()) { var embeddedValue = entry.GetCurrentValue(complexProperty); - var embeddedPropertyName = complexProperty.Name; + var embeddedPropertyName = complexProperty.GetJsonPropertyName(); if (embeddedValue == null) { if (document[embeddedPropertyName] != null) diff --git a/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs b/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs index 10586e1f24e..2a9c572bf60 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs @@ -20,6 +20,11 @@ public class CosmosApiConsistencyFixture : ApiConsistencyFixtureBase { public override HashSet FluentApiTypes { get; } = [ + typeof(CosmosComplexCollectionBuilderExtensions), + typeof(CosmosComplexCollectionTypePropertyBuilderExtensions), + typeof(CosmosComplexPropertyBuilderExtensions), + typeof(CosmosComplexTypePrimitiveCollectionBuilderExtensions), + typeof(CosmosComplexTypePropertyBuilderExtensions), typeof(CosmosPrimitiveCollectionBuilderExtensions), typeof(CosmosModelBuilderExtensions), typeof(CosmosPropertyBuilderExtensions), @@ -65,7 +70,16 @@ public override typeof(CosmosPropertyBuilderExtensions), null ) - } + }, + { + typeof(IReadOnlyComplexProperty), ( + typeof(CosmosComplexPropertyExtensions), + null, + null, + typeof(CosmosComplexPropertyBuilderExtensions), + null + ) + }, }; } } diff --git a/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs b/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs index 153fdb7343f..d552709df32 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs @@ -663,6 +663,108 @@ public virtual async Task Can_change_principal_instance_non_derived() } } + [ConditionalFact] + public virtual async Task Can_use_non_persisted_properties_owned() + { + var options = await Fixture.CreateOptions( + modelBuilder => + { + modelBuilder.Entity(eb => eb.OwnsOne( + v => v.Operator, b => + { + b.Property(x => x.Name).ToJsonProperty(""); + })); + }, + seed: false); + + using (var context = new EmbeddedTransportationContext(options)) + { + var vehicle = new Vehicle + { + Name = "Test Vehicle", + Operator = new Operator { Name = "Test Operator" } + }; + await context.AddAsync(vehicle); + await context.SaveChangesAsync(); + + Assert.Equal("Test Operator", vehicle.Operator.Name); + } + + using (var context = new EmbeddedTransportationContext(options)) + { + var vehicle = await context.Vehicles.SingleAsync(); + Assert.Null(vehicle.Operator.Name); + + vehicle.Operator.Name = "Theon Greyjoy"; + // Assert.Equal(0, await context.SaveChangesAsync()); // @TODO: #37929 non-persisted property changes should not cause SaveChanges to update the document + await context.SaveChangesAsync(); + } + + using (var context = new EmbeddedTransportationContext(options)) + { + var vehicle = await context.Vehicles.SingleAsync(); + Assert.Null(vehicle.Operator.Name); + } + } + + [ConditionalFact] + public virtual async Task Can_use_non_persisted_properties_complex() + { + var options = await Fixture.CreateOptions( + + modelBuilder => + { + modelBuilder.Entity(eb => + { + eb.Ignore(x => x.Operator); + eb.ComplexProperty( + v => v.Operator, b => + { + b.Property(x => x.Name).ToJsonProperty(""); + }); + }); + }, + seed: false); + + using (var context = new EmbeddedTransportationContext(options)) + { + var vehicle = new Vehicle + { + Name = "Test Vehicle", + Operator = new Operator { Name = "Test Operator" } + }; + await context.AddAsync(vehicle); + await context.SaveChangesAsync(); + + Assert.Equal("Test Operator", vehicle.Operator.Name); + } + + using (var context = new EmbeddedTransportationContext(options)) + { + var vehicle = await context.Vehicles.SingleAsync(x => x.Operator == new Operator()); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("Vehicle", "PoweredVehicle") AND (c["Operator"] = {"VehicleName":null,"Details":null})) +OFFSET 0 LIMIT 2 +"""); + + Assert.Null(vehicle.Operator.Name); + + vehicle.Operator.Name = "Theon Greyjoy"; + // Assert.Equal(0, await context.SaveChangesAsync()); // @TODO: #37929 non-persisted property changes should not cause SaveChanges to update the document + await context.SaveChangesAsync(); + } + + using (var context = new EmbeddedTransportationContext(options)) + { + var vehicle = await context.Vehicles.SingleAsync(x => x.Operator == new Operator()); + Assert.Null(vehicle.Operator.Name); + } + } + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)Fixture.ListLoggerFactory; @@ -702,7 +804,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con => ((EmbeddedTransportationContext)context).Options.OnModelCreating?.Invoke(modelBuilder); public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder).ConfigureWarnings(w => w.Ignore(CosmosEventId.NoPartitionKeyDefined)); + => base.AddOptions(builder).ConfigureWarnings(w => w.Ignore(CosmosEventId.NoPartitionKeyDefined).Ignore(CoreEventId.MappedNavigationIgnoredWarning)); protected override object GetAdditionalModelCacheKey(DbContext context) { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ComplexTypeToJsonPropertyQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ComplexTypeToJsonPropertyQueryCosmosTest.cs new file mode 100644 index 00000000000..77e6196f39e --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ComplexTypeToJsonPropertyQueryCosmosTest.cs @@ -0,0 +1,479 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Cosmos.Internal; +using Microsoft.EntityFrameworkCore.TestModels.ComplexTypeModel; + +namespace Microsoft.EntityFrameworkCore.Query; + +public class ComplexTypeToJsonPropertyQueryCosmosTest(ComplexTypeToJsonPropertyQueryCosmosTest.ComplexTypeToJsonPropertyQueryCosmosFixture fixture) : ComplexTypeQueryTestBase(fixture), IClassFixture +{ + public override Task Filter_on_property_inside_complex_type_after_subquery(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_property_inside_complex_type_after_subquery(async), CosmosStrings.LimitOffsetNotSupportedInSubqueries); + + public override Task Filter_on_property_inside_nested_complex_type_after_subquery(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_property_inside_nested_complex_type_after_subquery(async), CosmosStrings.LimitOffsetNotSupportedInSubqueries); + + public override Task Filter_on_required_property_inside_required_complex_type_on_optional_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_required_property_inside_required_complex_type_on_optional_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Customer), nameof(CustomerGroup))); + + public override Task Filter_on_required_property_inside_required_complex_type_on_required_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_required_property_inside_required_complex_type_on_required_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Customer), nameof(CustomerGroup))); + + public override Task Project_complex_type_via_optional_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_complex_type_via_optional_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Customer), nameof(CustomerGroup))); + + public override Task Project_complex_type_via_required_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_complex_type_via_required_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Customer), nameof(CustomerGroup))); + + public override Task Load_complex_type_after_subquery_on_entity_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Load_complex_type_after_subquery_on_entity_type(async), CosmosStrings.LimitOffsetNotSupportedInSubqueries); + + public override Task Select_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +"""); + }); + + public override Task Select_nested_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_nested_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +"""); + }); + + public override Task Select_single_property_on_nested_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_single_property_on_nested_complex_type(async); + + AssertSql( + """ +SELECT VALUE c["ShippingAddressRenamed"]["CountryRenamed"]["FullNameRenamed"] +FROM root c +"""); + }); + + public override Task Select_complex_type_Where(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_complex_type_Where(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"]["ZipCodeRenamed"] = 7728) +"""); + }); + + public override async Task Select_complex_type_Distinct(bool async) + => await AssertTranslationFailed(async () => await base.Select_complex_type_Distinct(async)); // Cosmos: Projecting out nested documents retrieves the entire document #34067 + + public override Task Complex_type_equals_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Complex_type_equals_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"] = c["BillingAddressRenamed"]) +"""); + }); + + public override Task Complex_type_equals_constant(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Complex_type_equals_constant(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"] = {"AddressLine1Renamed":"804 S. Lakeshore Road","AddressLine2Renamed":null,"TagsRenamed":["foo","bar"],"ZipCodeRenamed":38654,"CountryRenamed":{"CodeRenamed":"US","FullNameRenamed":"United States"}}) +"""); + }); + + public override Task Complex_type_equals_parameter(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Complex_type_equals_parameter(async); + + AssertSql( + """ +@entity_equality_address='{"AddressLine1Renamed":"804 S. Lakeshore Road","AddressLine2Renamed":null,"TagsRenamed":["foo","bar"],"ZipCodeRenamed":38654,"CountryRenamed":{"CodeRenamed":"US","FullNameRenamed":"United States"}}' + +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"] = @entity_equality_address) +"""); + }); + + public override Task Subquery_over_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Subquery_over_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Contains_over_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Contains_over_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_entity_type_containing_complex_property(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_entity_type_containing_complex_property(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_entity_type_containing_complex_property(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_entity_type_containing_complex_property(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_property_in_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_property_in_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_property_in_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_property_in_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_two_different_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_two_different_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_two_different_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_two_different_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Filter_on_property_inside_struct_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Filter_on_property_inside_struct_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"]["ZipCodeRenamed"] = 7728) +"""); + }); + + public override Task Filter_on_property_inside_nested_struct_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Filter_on_property_inside_nested_struct_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"]["CountryRenamed"]["CodeRenamed"] = "DE") +"""); + }); + + public override Task Filter_on_property_inside_struct_complex_type_after_subquery(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_property_inside_struct_complex_type_after_subquery(async), CosmosStrings.LimitOffsetNotSupportedInSubqueries); + + public override Task Filter_on_property_inside_nested_struct_complex_type_after_subquery(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_property_inside_nested_struct_complex_type_after_subquery(async), CosmosStrings.LimitOffsetNotSupportedInSubqueries); + + public override Task Filter_on_required_property_inside_required_struct_complex_type_on_optional_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_required_property_inside_required_struct_complex_type_on_optional_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(ValuedCustomer), nameof(ValuedCustomerGroup))); + + public override Task Filter_on_required_property_inside_required_struct_complex_type_on_required_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Filter_on_required_property_inside_required_struct_complex_type_on_required_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(ValuedCustomer), nameof(ValuedCustomerGroup))); + + public override Task Project_struct_complex_type_via_optional_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_struct_complex_type_via_optional_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(ValuedCustomer), nameof(ValuedCustomerGroup))); + + public override Task Project_nullable_struct_complex_type_via_optional_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_nullable_struct_complex_type_via_optional_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(ValuedCustomer), nameof(ValuedCustomerGroup))); + + public override Task Project_struct_complex_type_via_required_navigation(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_struct_complex_type_via_required_navigation(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(ValuedCustomer), nameof(ValuedCustomerGroup))); + + public override Task Load_struct_complex_type_after_subquery_on_entity_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Load_struct_complex_type_after_subquery_on_entity_type(async), CosmosStrings.LimitOffsetNotSupportedInSubqueries); + + public override Task Select_struct_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_struct_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +"""); + }); + + public override Task Select_nested_struct_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_nested_struct_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +"""); + }); + + public override Task Select_single_property_on_nested_struct_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_single_property_on_nested_struct_complex_type(async); + + AssertSql( + """ +SELECT VALUE c["ShippingAddressRenamed"]["CountryRenamed"]["FullNameRenamed"] +FROM root c +"""); + }); + + public override Task Select_struct_complex_type_Where(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Select_struct_complex_type_Where(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"]["ZipCodeRenamed"] = 7728) +"""); + }); + + public override Task Select_struct_complex_type_Distinct(bool async) + => AssertTranslationFailed(() => base.Select_struct_complex_type_Distinct(async)); // #34067 + + public override Task Struct_complex_type_equals_struct_complex_type(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Struct_complex_type_equals_struct_complex_type(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"] = c["BillingAddressRenamed"]) +"""); + }); + + public override Task Struct_complex_type_equals_constant(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Struct_complex_type_equals_constant(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"] = {"AddressLine1Renamed":"804 S. Lakeshore Road","AddressLine2Renamed":null,"ZipCodeRenamed":38654,"CountryRenamed":{"CodeRenamed":"US","FullNameRenamed":"United States"}}) +"""); + }); + + public override Task Struct_complex_type_equals_parameter(bool async) + => CosmosTestHelpers.Instance.NoSyncTest(async, async (async) => + { + await base.Struct_complex_type_equals_parameter(async); + + AssertSql( + """ +@entity_equality_address='{"AddressLine1Renamed":"804 S. Lakeshore Road","AddressLine2Renamed":null,"ZipCodeRenamed":38654,"CountryRenamed":{"CodeRenamed":"US","FullNameRenamed":"United States"}}' + +SELECT VALUE c +FROM root c +WHERE (c["ShippingAddressRenamed"] = @entity_equality_address) +"""); + }); + + public override Task Subquery_over_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Subquery_over_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Contains_over_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Contains_over_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_entity_type_containing_struct_complex_property(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_entity_type_containing_struct_complex_property(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_entity_type_containing_struct_complex_property(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_entity_type_containing_struct_complex_property(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_property_in_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_property_in_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_property_in_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_property_in_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Concat_two_different_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Concat_two_different_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_two_different_struct_complex_type(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_two_different_struct_complex_type(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_entity_with_nested_complex_type_twice_with_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_entity_with_nested_complex_type_twice_with_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_nested_complex_type_twice_with_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_nested_complex_type_twice_with_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_nested_complex_type_twice_with_double_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_struct_nested_complex_type_twice_with_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_struct_nested_complex_type_twice_with_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Project_same_struct_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_same_struct_nested_complex_type_twice_with_double_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_of_same_nested_complex_type_projected_twice_with_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_of_same_nested_complex_type_projected_twice_with_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + public override Task Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + => AssertTranslationFailed(() => base.Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async)); + + public override Task Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + => AssertTranslationFailedWithDetails(() => base.Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async), CosmosStrings.NonCorrelatedSubqueriesNotSupported); + + + #region GroupBy + + [ConditionalTheory(Skip = "#17313 Cosmos: Translate GroupBy")] + public override async Task GroupBy_over_property_in_nested_complex_type(bool async) + { + await base.GroupBy_over_property_in_nested_complex_type(async); + + AssertSql( + """ + +"""); + } + + [ConditionalTheory(Skip = "#17313 Cosmos: Translate GroupBy")] + public override async Task GroupBy_over_complex_type(bool async) + { + await base.GroupBy_over_complex_type(async); + + AssertSql( + """ + +"""); + } + + [ConditionalTheory(Skip = "#17313 Cosmos: Translate GroupBy")] + public override async Task GroupBy_over_nested_complex_type(bool async) + { + await base.GroupBy_over_nested_complex_type(async); + + AssertSql( + """ + +"""); + } + + [ConditionalTheory(Skip = "#17313 Cosmos: Translate GroupBy")] + public override async Task Entity_with_complex_type_with_group_by_and_first(bool async) + { + await base.Entity_with_complex_type_with_group_by_and_first(async); + + AssertSql( + """ + +"""); + } + + #endregion GroupBy + + public override Task Projecting_property_of_complex_type_using_left_join_with_pushdown(bool async) + => AssertTranslationFailedWithDetails(() => base.Projecting_property_of_complex_type_using_left_join_with_pushdown(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Customer), nameof(CustomerGroup))); + + public override Task Projecting_complex_from_optional_navigation_using_conditional(bool async) + => AssertTranslationFailedWithDetails(() => base.Projecting_complex_from_optional_navigation_using_conditional(async), CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Customer), nameof(CustomerGroup))); + + public override Task Project_entity_with_complex_type_pushdown_and_then_left_join(bool async) + => AssertTranslationFailedWithDetails(() => base.Project_entity_with_complex_type_pushdown_and_then_left_join(async), CosmosStrings.LimitOffsetNotSupportedInSubqueries); + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + + public class ComplexTypeToJsonPropertyQueryCosmosFixture : ComplexTypeQueryFixtureBase + { + protected override string StoreName => nameof(ComplexTypeToJsonPropertyQueryCosmosTest); + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => CosmosTestStoreFactory.Instance; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder) + .ConfigureWarnings(w => w.Ignore(CosmosEventId.NoPartitionKeyDefined).Ignore(CoreEventId.MappedEntityTypeIgnoredWarning)); + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + modelBuilder.Entity().ToContainer("Customers"); + modelBuilder.Entity().ToContainer("CustomerGroups"); + modelBuilder.Entity().ToContainer("ValuedCustomers"); + modelBuilder.Entity().ToContainer("ValuedCustomerGroups"); + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + RenameProperties(entityType); + } + } + + private void RenameProperties(IMutableTypeBase typeBase) + { + foreach (var property in typeBase.GetProperties()) + { + if (property.GetJsonPropertyName() == "id") + { + continue; + } + property.SetJsonPropertyName(property.Name + "Renamed"); + } + foreach (var property in typeBase.GetComplexProperties()) + { + property.SetJsonPropertyName(property.Name + "Renamed"); + RenameProperties(property.ComplexType); + } + } + } +}