From b2e366bf1d1955903c75c06288bad5bc5b8deda0 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 3 Sep 2024 13:44:59 -0700 Subject: [PATCH] [release/8.0] Update FK ElementType when the FK properties change. (#34561) Fixes #33704 --- .../Internal/CosmosTypeMappingSource.cs | 24 +++++ .../ElementTypeChangedConvention.cs | 23 ++++- .../Internal/InternalPropertyBuilder.cs | 8 ++ .../CosmosModelBuilderGenericTest.cs | 94 +++++++++---------- .../ModelBuilding/OwnedTypesTestBase.cs | 45 +++++++-- test/EFCore.Tests/ModelBuilding/TestModel.cs | 6 +- 6 files changed, 144 insertions(+), 56 deletions(-) diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs index 8f7cc37bd56..78dd77f4f3a 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; using Newtonsoft.Json.Linq; namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; @@ -17,6 +18,9 @@ public class CosmosTypeMappingSource : TypeMappingSource { private readonly Dictionary _clrTypeMappings; + internal static readonly bool UseOldBehavior33704 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue33704", out var enabled33704) && enabled33704; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -69,6 +73,26 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies) return null; } + /// + protected override bool TryFindJsonCollectionMapping( + TypeMappingInfo mappingInfo, + Type modelClrType, + Type? providerClrType, + ref CoreTypeMapping? elementMapping, + out ValueComparer? elementComparer, + out JsonValueReaderWriter? collectionReaderWriter) + { + if (UseOldBehavior33704) + { + return base.TryFindJsonCollectionMapping( + mappingInfo, modelClrType, providerClrType, ref elementMapping, out elementComparer, out collectionReaderWriter); + } + + elementComparer = null; + collectionReaderWriter = null; + return false; + } + private CoreTypeMapping? FindCollectionMapping(in TypeMappingInfo mappingInfo) { var clrType = mappingInfo.ClrType!; diff --git a/src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs b/src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs index 3e40ea9269e..7f1f12b35a5 100644 --- a/src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs +++ b/src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs @@ -9,11 +9,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class ElementTypeChangedConvention : IPropertyElementTypeChangedConvention, IForeignKeyAddedConvention +public class ElementTypeChangedConvention : + IPropertyElementTypeChangedConvention, IForeignKeyAddedConvention, IForeignKeyPropertiesChangedConvention { internal static readonly bool UseOldBehavior32411 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32411", out var enabled32411) && enabled32411; + internal static readonly bool UseOldBehavior33704 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue33704", out var enabled33704) && enabled33704; + /// /// Creates a new instance of . /// @@ -50,6 +54,23 @@ public void ProcessPropertyElementTypeChanged( /// public void ProcessForeignKeyAdded( IConventionForeignKeyBuilder foreignKeyBuilder, IConventionContext context) + => ProcessForeignKey(foreignKeyBuilder); + + /// + public void ProcessForeignKeyPropertiesChanged( + IConventionForeignKeyBuilder relationshipBuilder, + IReadOnlyList oldDependentProperties, + IConventionKey oldPrincipalKey, + IConventionContext> context) + { + if (relationshipBuilder.Metadata.IsInModel + && !UseOldBehavior33704) + { + ProcessForeignKey(relationshipBuilder); + } + } + + private static void ProcessForeignKey(IConventionForeignKeyBuilder foreignKeyBuilder) { var foreignKeyProperties = foreignKeyBuilder.Metadata.Properties; var principalKeyProperties = foreignKeyBuilder.Metadata.PrincipalKey.Properties; diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index 63443cb2bef..e3e43e88dff 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -802,6 +802,14 @@ public virtual bool CanSetProviderValueComparer( { Metadata.SetValueConverter((Type?)null, configurationSource); } + + if (elementType == null + && CanSetConversion((Type?)null, configurationSource) + && !ElementTypeChangedConvention.UseOldBehavior33704) + { + Metadata.RemoveAnnotation(CoreAnnotationNames.ValueConverter); + } + return new InternalElementTypeBuilder(Metadata.GetElementType()!, ModelBuilder); } diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index a528c3d2df6..4a1fd8ecb06 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -13,181 +13,181 @@ public class CosmosGenericNonRelationship : GenericNonRelationship { public override void Can_set_composite_key_for_primitive_collection_on_an_entity_with_fields() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(EntityWithFields), "CollectionCompanyId"), + CoreStrings.PropertyNotMapped("int[]", nameof(EntityWithFields), "CollectionCompanyId"), Assert.Throws( () => base.Can_set_composite_key_for_primitive_collection_on_an_entity_with_fields()).Message); public override void Access_mode_can_be_overridden_at_entity_and_primitive_collection_levels() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Down"), + CoreStrings.PropertyNotMapped("ObservableCollection", nameof(CollectionQuarks), "Down"), Assert.Throws( () => base.Access_mode_can_be_overridden_at_entity_and_primitive_collection_levels()).Message); public override void Can_set_custom_value_generator_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Bottom"), Assert.Throws( () => base.Can_set_custom_value_generator_for_primitive_collections()).Message); public override void Can_set_element_type_annotation() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(Customer), "Notes"), + CoreStrings.PropertyNotMapped("List", nameof(Customer), "Notes"), Assert.Throws( () => base.Can_set_element_type_annotation()).Message); public override void Can_set_max_length_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("string[]", nameof(CollectionQuarks), "Bottom"), Assert.Throws( () => base.Can_set_max_length_for_primitive_collections()).Message); public override void Can_set_primitive_collection_annotation() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(Customer), "Notes"), + CoreStrings.PropertyNotMapped("List", nameof(Customer), "Notes"), Assert.Throws( () => base.Can_set_primitive_collection_annotation()).Message); public override void Can_set_primitive_collection_annotation_by_type() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(Customer), "Notes"), + CoreStrings.PropertyNotMapped("List", nameof(Customer), "Notes"), Assert.Throws( () => base.Can_set_primitive_collection_annotation_by_type()).Message); public override void Can_set_primitive_collection_annotation_when_no_clr_property() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(Customer), "Notes"), + CoreStrings.PropertyNotMapped("List", nameof(Customer), "Notes"), Assert.Throws( () => base.Can_set_primitive_collection_annotation_when_no_clr_property()).Message); public override void Can_set_sentinel_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Bottom"), Assert.Throws( () => base.Can_set_sentinel_for_primitive_collections()).Message); public override void Can_set_unicode_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Bottom"), Assert.Throws( () => base.Can_set_unicode_for_primitive_collections()).Message); public override void Element_types_are_nullable_by_default_if_the_type_is_nullable() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_are_nullable_by_default_if_the_type_is_nullable()).Message); public override void Element_types_can_be_made_required() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_can_be_made_required()).Message); public override void Element_types_can_have_custom_type_value_converter_type_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("int[]", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_can_have_custom_type_value_converter_type_set()).Message); public override void Element_types_can_have_max_length() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_can_have_max_length()).Message); public override void Element_types_can_have_non_generic_value_converter_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("int[]", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_can_have_non_generic_value_converter_set()).Message); public override void Element_types_can_have_precision_and_scale() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_can_have_precision_and_scale()).Message); public override void Element_types_can_have_provider_type_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_can_have_provider_type_set()).Message); public override void Element_types_can_have_unicode_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_can_have_unicode_set()).Message); public override void Element_types_have_default_precision_and_scale() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_have_default_precision_and_scale()).Message); public override void Element_types_have_default_unicode() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_have_default_unicode()).Message); public override void Element_types_have_no_max_length_by_default() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Element_types_have_no_max_length_by_default()).Message); public override void Primitive_collections_are_required_by_default_only_if_CLR_type_is_nullable() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Primitive_collections_are_required_by_default_only_if_CLR_type_is_nullable()).Message); public override void Primitive_collections_can_be_made_optional() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Primitive_collections_can_be_made_optional()).Message); public override void Primitive_collections_can_be_made_required() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Primitive_collections_can_be_made_required()).Message); public override void Primitive_collections_can_be_set_to_generate_values_on_Add() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Bottom"), Assert.Throws( () => base.Primitive_collections_can_be_set_to_generate_values_on_Add()).Message); public override void Primitive_collections_can_have_access_mode_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("ObservableCollection", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Primitive_collections_can_have_access_mode_set()).Message); public override void Primitive_collections_can_have_field_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Down"), + CoreStrings.PropertyNotMapped("ObservableCollection", nameof(CollectionQuarks), "Down"), Assert.Throws( () => base.Primitive_collections_can_have_field_set()).Message); public override void Primitive_collections_can_have_value_converter_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Primitive_collections_can_have_value_converter_set()).Message); public override void Primitive_collections_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("ObservableCollection", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Primitive_collections_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties()).Message); public override void Value_converter_type_on_primitive_collection_is_checked() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Up"), + CoreStrings.PropertyNotMapped("ObservableCollection", nameof(CollectionQuarks), "Up"), Assert.Throws( () => base.Value_converter_type_on_primitive_collection_is_checked()).Message); @@ -425,7 +425,7 @@ public virtual void No_alternate_key_is_created_if_id_is_partition_key() public override void Primitive_collections_can_be_made_concurrency_tokens() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", nameof(CollectionQuarks), "Charm"), Assert.Throws( () => base.Primitive_collections_can_be_made_concurrency_tokens()).Message); @@ -437,97 +437,97 @@ public class CosmosGenericComplexType : GenericComplexType { public override void Access_mode_can_be_overridden_at_entity_and_property_levels() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Down"), + CoreStrings.PropertyNotMapped("ObservableCollection", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Down"), Assert.Throws( () => base.Access_mode_can_be_overridden_at_entity_and_property_levels()).Message); public override void Can_add_shadow_primitive_collections_when_they_have_been_ignored() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(Customer), "Shadow"), + CoreStrings.PropertyNotMapped("string[]", "ComplexProperties.Customer#Customer", "Shadow"), Assert.Throws( () => base.Can_add_shadow_primitive_collections_when_they_have_been_ignored()).Message); public override void Can_call_PrimitiveCollection_on_a_field() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(EntityWithFields), "CollectionId"), + CoreStrings.PropertyNotMapped("long[]", "ComplexProperties.EntityWithFields#EntityWithFields", "CollectionId"), Assert.Throws( () => base.Can_call_PrimitiveCollection_on_a_field()).Message); public override void Can_set_custom_value_generator_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Bottom"), Assert.Throws( () => base.Can_set_custom_value_generator_for_primitive_collections()).Message); public override void Can_set_max_length_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("string[]", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Bottom"), Assert.Throws( () => base.Can_set_max_length_for_primitive_collections()).Message); public override void Can_set_primitive_collection_annotation_when_no_clr_property() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(Customer), "Ints"), + CoreStrings.PropertyNotMapped("int[]", "ComplexProperties.Customer#Customer", "Ints"), Assert.Throws( () => base.Can_set_primitive_collection_annotation_when_no_clr_property()).Message); public override void Can_set_sentinel_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Bottom"), Assert.Throws( () => base.Can_set_sentinel_for_primitive_collections()).Message); public override void Can_set_unicode_for_primitive_collections() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Bottom"), Assert.Throws( () => base.Can_set_unicode_for_primitive_collections()).Message); public override void Primitive_collections_are_required_by_default_only_if_CLR_type_is_nullable() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Charm"), Assert.Throws( () => base.Primitive_collections_are_required_by_default_only_if_CLR_type_is_nullable()).Message); public override void Primitive_collections_can_be_made_concurrency_tokens() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Charm"), Assert.Throws( () => base.Primitive_collections_can_be_made_concurrency_tokens()).Message); public override void Primitive_collections_can_be_made_optional() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Charm"), Assert.Throws( () => base.Primitive_collections_can_be_made_optional()).Message); public override void Primitive_collections_can_be_made_required() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Charm"), Assert.Throws( () => base.Primitive_collections_can_be_made_required()).Message); public override void Primitive_collections_can_be_set_to_generate_values_on_Add() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Bottom"), + CoreStrings.PropertyNotMapped("List", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Bottom"), Assert.Throws( () => base.Primitive_collections_can_be_set_to_generate_values_on_Add()).Message); public override void Primitive_collections_can_have_field_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Down"), + CoreStrings.PropertyNotMapped("ObservableCollection", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Down"), Assert.Throws( () => base.Primitive_collections_can_have_field_set()).Message); public override void Primitive_collections_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Charm"), + CoreStrings.PropertyNotMapped("ObservableCollection", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Charm"), Assert.Throws( () => base.Primitive_collections_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties()).Message); public override void Properties_can_have_access_mode_set() => Assert.Equal( - CosmosStrings.PrimitiveCollectionsNotSupported(nameof(CollectionQuarks), "Down"), + CoreStrings.PropertyNotMapped("ObservableCollection", "ComplexProperties.CollectionQuarks#CollectionQuarks", "Down"), Assert.Throws( () => base.Properties_can_have_access_mode_set()).Message); diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index 095825c8dd0..fa421eb73b7 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -1076,11 +1076,12 @@ public virtual void Can_map_derived_of_owned_type_first() } [ConditionalFact] - public virtual void Can_configure_relationship_with_PK_ValueConverter() + public virtual void Can_configure_relationship_with_PK_ValueConverter_shadow_FK() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity().Property(x => x.Id) + modelBuilder.Entity() + .Property(x => x.Id) .HasConversion(x => x.Id, x => new CustomId { Id = x }); modelBuilder.Entity() @@ -1089,8 +1090,7 @@ public virtual void Can_configure_relationship_with_PK_ValueConverter() modelBuilder.Entity() .OwnsOne(q => q.Value) - .Property(x => x.CategoryId) - .HasConversion(x => x.Id, x => new CustomId { Id = x }); + .Property(x => x.CategoryId); var model = modelBuilder.FinalizeModel(); @@ -1106,14 +1106,45 @@ public virtual void Can_configure_relationship_with_PK_ValueConverter() var category = model.FindEntityType(typeof(ValueCategory)); Assert.Null(category.FindProperty("TempId")); - var barNavigation = owned.GetDeclaredNavigations().Single(n => !n.ForeignKey.IsOwnership); - Assert.Same(category, barNavigation.TargetEntityType); - var fkProperty = barNavigation.ForeignKey.Properties.Single(); + var categoryNavigation = owned.GetDeclaredNavigations().Single(n => !n.ForeignKey.IsOwnership); + Assert.Same(category, categoryNavigation.TargetEntityType); + var fkProperty = categoryNavigation.ForeignKey.Properties.Single(); Assert.Equal("CategoryId", fkProperty.Name); Assert.Equal(3, model.GetEntityTypes().Count()); } + [ConditionalFact] + public virtual void Can_configure_relationship_with_PK_ValueConverter() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(eb => + { + eb.Property(x => x.Id) + .HasConversion(x => x.Id, x => new CustomId { Id = x }); + eb.OwnsOne(q => q.Value) + .WithOwner() + .HasForeignKey(q => q.CategoryId); + }); + + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var result = model.FindEntityType(typeof(QueryResult)); + Assert.Null(result.FindProperty("TempId")); + + var owned = result.GetDeclaredNavigations().Single().TargetEntityType; + Assert.Null(owned.FindProperty("TempId")); + + var ownedPkProperty = owned.FindPrimaryKey().Properties.Single(); + Assert.NotNull(ownedPkProperty.GetValueConverter()); + + Assert.Empty(owned.GetDeclaredNavigations().Where(n => !n.ForeignKey.IsOwnership)); + Assert.Equal(2, model.GetEntityTypes().Count()); + } + [ConditionalFact] public virtual void Throws_on_FK_matching_two_relationships() { diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 215ec576aaf..cc1a81e459e 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Collections; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; @@ -809,9 +810,12 @@ protected class Value public ValueCategory? Category { get; set; } } - protected class CustomId + protected class CustomId : IEnumerable { public int Id { get; set; } + + public IEnumerator GetEnumerator() => throw new NotImplementedException(); + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); } protected class ValueCategory