From 9425fcf70d1b7c353ce2d356a2ff04bda834584b Mon Sep 17 00:00:00 2001 From: m_axwel_l Date: Tue, 5 May 2026 15:11:36 +0500 Subject: [PATCH 1/3] Improve conflicting member exception message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When adding a member with a name that already exists, the previous exception only stated "a property or navigation with the same name already exists" without distinguishing what kind of member the conflict was with, and unconditionally included the declaring type even when it was the same as the type being added to. Address the three improvements requested in #36487: 1. Specify the kind of the conflicting member (property, complex property, navigation, skip navigation, or service property). 2. Include the declaring type only when it differs from the current type — split into two resources, ConflictingPropertyOrNavigation for same-type conflicts and ConflictingPropertyOrNavigationOnBaseType for conflicts inherited from a base type. 3. Add explicit removal guidance: "Remove the existing X first." Add two helpers in PropertyBaseExtensions: - GetMemberKindString returns the human-readable kind label. - FormatConflictingMemberMessage picks the right resource based on declaring-type-vs-current-type and assembles the message. Update all eight throw sites to use the helper (or the new resource directly when the conflicting member is not in scope). Update affected tests to assert against the new messages. Fixes #36487 --- src/EFCore/EFCore.baseline.json | 5 +++- .../Builders/ReferenceNavigationBuilder.cs | 3 +- src/EFCore/Metadata/Internal/EntityType.cs | 13 +++------ .../Internal/InternalEntityTypeBuilder.cs | 3 +- .../Internal/PropertyBaseExtensions.cs | 28 +++++++++++++++++++ src/EFCore/Metadata/Internal/TypeBase.cs | 8 ++---- src/EFCore/Properties/CoreStrings.Designer.cs | 16 ++++++++--- src/EFCore/Properties/CoreStrings.resx | 5 +++- .../ModelBuilderTest.OneToOne.cs | 2 +- .../Internal/EntityTypeTest.BaseType.cs | 8 +++--- .../Metadata/Internal/EntityTypeTest.cs | 28 +++++++++---------- .../Internal/InternalEntityTypeBuilderTest.cs | 20 +++++++++++-- 12 files changed, 93 insertions(+), 46 deletions(-) diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index 8ae23aac4cd..e65aa1a3317 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -3845,7 +3845,10 @@ "Member": "static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity);" }, { - "Member": "static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType);" + "Member": "static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingMemberKind);" + }, + { + "Member": "static string ConflictingPropertyOrNavigationOnBaseType(object? member, object? type, object? conflictingMemberKind, object? conflictingType);" }, { "Member": "static string ConflictingRelationshipConversions(object? entityType, object? property, object? valueConversion, object? conflictingValueConversion);" diff --git a/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs b/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs index dcb060b5d62..6437c4e5cb9 100644 --- a/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs @@ -239,7 +239,8 @@ private InternalForeignKeyBuilder WithOneBuilder(MemberIdentity reference) { throw new InvalidOperationException( CoreStrings.ConflictingPropertyOrNavigation( - referenceName, RelatedEntityType.DisplayName(), RelatedEntityType.DisplayName())); + referenceName, RelatedEntityType.DisplayName(), + conflictingMemberKind: nameof(Navigation))); } var pointsToPrincipal = !foreignKey.IsSelfReferencing() diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 6cc6fcf2bef..a5b15b86883 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -1451,16 +1451,14 @@ public virtual Navigation AddNavigation(MemberIdentity navigationMember, Foreign } throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), duplicateNavigation.DeclaringEntityType.DisplayName())); + duplicateNavigation.FormatConflictingMemberMessage(name, this)); } var duplicateProperty = FindMembersInHierarchy(name).FirstOrDefault(); if (duplicateProperty != null) { throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), ((IReadOnlyTypeBase)duplicateProperty.DeclaringType).DisplayName())); + duplicateProperty.FormatConflictingMemberMessage(name, this)); } Check.DebugAssert( @@ -1633,8 +1631,7 @@ public virtual IEnumerable GetNavigations() if (duplicateProperty != null) { throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), duplicateProperty.DeclaringType.DisplayName())); + duplicateProperty.FormatConflictingMemberMessage(name, this)); } if (memberInfo != null) @@ -2286,9 +2283,7 @@ public virtual ServiceProperty AddServiceProperty( if (duplicateMember != null) { throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), - ((IReadOnlyTypeBase)duplicateMember.DeclaringType).DisplayName())); + duplicateMember.FormatConflictingMemberMessage(name, this)); } ValidateClrMember(name, memberInfo, false); diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 72f06816d57..b7f507e130d 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -541,8 +541,7 @@ public override void RemoveMembersInHierarchy(string propertyName, Configuration if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) { throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - propertyName, Metadata.DisplayName(), conflictingNavigation.DeclaringEntityType.DisplayName())); + conflictingNavigation.FormatConflictingMemberMessage(propertyName, Metadata)); } var foreignKey = conflictingNavigation.ForeignKey; diff --git a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs index 19b27dace99..af13a7edb9a 100644 --- a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs @@ -338,6 +338,34 @@ or PropertyAccessMode.FieldDuringConstruction return false; } + /// + /// Builds the message for the diagnostic that fires when a member conflicts with an existing + /// member on the structural type or one of its base types. The kind of the conflicting member + /// is taken straight from the runtime type name (e.g. Property, Navigation, + /// SkipNavigation, ComplexProperty, ServiceProperty) so it stays in sync + /// with the codebase without an extra mapping table. + /// + /// + /// 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 + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static string FormatConflictingMemberMessage( + this IReadOnlyPropertyBase conflictingMember, + string newMemberName, + IReadOnlyTypeBase owningType) + { + var conflictingMemberKind = conflictingMember.GetType().Name; + var owningTypeDisplayName = owningType.DisplayName(); + var declaringTypeDisplayName = ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName(); + + return declaringTypeDisplayName == owningTypeDisplayName + ? CoreStrings.ConflictingPropertyOrNavigation(newMemberName, owningTypeDisplayName, conflictingMemberKind) + : CoreStrings.ConflictingPropertyOrNavigationOnBaseType( + newMemberName, owningTypeDisplayName, conflictingMemberKind, declaringTypeDisplayName); + } + private static string GetNoFieldErrorMessage(IPropertyBase propertyBase) => propertyBase.DeclaringType switch { diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index 0cfe3f25afc..3b3fe513e32 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -634,9 +634,7 @@ private void CheckDiscriminatorProperty(Property? property) if (conflictingMember != null) { throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), - ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName())); + conflictingMember.FormatConflictingMemberMessage(name, this)); } if (memberInfo != null) @@ -1082,9 +1080,7 @@ public virtual IReadOnlyDictionary GetRuntimeFields() if (conflictingMember != null) { throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), - conflictingMember.DeclaringType.DisplayName())); + conflictingMember.FormatConflictingMemberMessage(name, this)); } if (memberInfo != null) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 5ce3819559d..00f7d15f43b 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -877,12 +877,20 @@ public static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity) entity); /// - /// The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. + /// The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. /// - public static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType) + public static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingMemberKind) => string.Format( - GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(type), nameof(conflictingType)), - member, type, conflictingType); + GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(type), nameof(conflictingMemberKind)), + member, type, conflictingMemberKind); + + /// + /// The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. + /// + public static string ConflictingPropertyOrNavigationOnBaseType(object? member, object? type, object? conflictingMemberKind, object? conflictingType) + => string.Format( + GetString("ConflictingPropertyOrNavigationOnBaseType", nameof(member), nameof(type), nameof(conflictingMemberKind), nameof(conflictingType)), + member, type, conflictingMemberKind, conflictingType); /// /// The property '{entityType}.{property}' participates in several relationship chains that have conflicting conversions: '{valueConversion}' and '{conflictingValueConversion}'. diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index ab03f9b8cab..846eedfc491 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -439,7 +439,10 @@ The entity type '{entity}' has both [Keyless] and [PrimaryKey] attributes; one must be removed. - The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. + The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. + + + The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. The property '{entityType}.{property}' participates in several relationship chains that have conflicting conversions: '{valueConversion}' and '{conflictingValueConversion}'. diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs index 5c446e28541..05a6c5a4a20 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs @@ -3050,7 +3050,7 @@ public virtual void Throws_on_duplicate_navigation_when_self_referencing() var modelBuilder = CreateModelBuilder(); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("SelfRef1", nameof(SelfRef), nameof(SelfRef)), + CoreStrings.ConflictingPropertyOrNavigation("SelfRef1", nameof(SelfRef), nameof(Navigation)), Assert.Throws(() => modelBuilder.Entity().HasOne(e => e.SelfRef1).WithOne(e => e.SelfRef1)).Message); } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs index 55a33981625..05b9bf42fc9 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs @@ -370,7 +370,7 @@ public void Adding_property_throws_when_parent_type_has_property_with_same_name( b.BaseType = a; Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("G", typeof(B).Name, typeof(A).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(B).Name, nameof(Property), typeof(A).Name), Assert.Throws(() => b.AddProperty("G")).Message); } @@ -389,7 +389,7 @@ public void Adding_property_throws_when_grandparent_type_has_property_with_same_ d.BaseType = c; Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("G", typeof(D).Name, typeof(A).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(D).Name, nameof(Property), typeof(A).Name), Assert.Throws(() => d.AddProperty("G")).Message); } @@ -406,7 +406,7 @@ public void Adding_property_throws_when_child_type_has_property_with_same_name() b.AddProperty(A.GProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("G", typeof(A).Name, typeof(B).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(A).Name, nameof(Property), typeof(B).Name), Assert.Throws(() => a.AddProperty(A.GProperty)).Message); } @@ -426,7 +426,7 @@ public void Adding_property_throws_when_grandchild_type_has_property_with_same_n d.AddProperty(A.GProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("G", typeof(A).Name, typeof(D).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(A).Name, nameof(Property), typeof(D).Name), Assert.Throws(() => a.AddProperty(A.GProperty)).Message); } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index d2669fa1be2..88e439b7d1d 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -952,7 +952,7 @@ public void Adding_a_new_navigation_with_a_name_that_conflicts_with_a_property_t orderType.AddProperty("Customer"); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, typeof(Order).Name), + CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, nameof(Property)), Assert.Throws(() => customerForeignKey.SetDependentToPrincipal("Customer")).Message); } @@ -970,7 +970,7 @@ public void Adding_a_new_navigation_with_a_name_that_conflicts_with_a_service_pr orderType.AddServiceProperty(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(Order)), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(ServiceProperty)), Assert.Throws(() => customerForeignKey.SetDependentToPrincipal(nameof(Order.Customer))).Message); } @@ -1154,7 +1154,7 @@ public void Throws_when_adding_same_self_referencing_navigation_twice() fk.SetPrincipalToDependent(SelfRef.SelfRef1Property); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(SelfRef.SelfRef1), typeof(SelfRef).Name, typeof(SelfRef).Name), + CoreStrings.ConflictingPropertyOrNavigation(nameof(SelfRef.SelfRef1), typeof(SelfRef).Name, nameof(Navigation)), Assert.Throws(() => fk.SetDependentToPrincipal(SelfRef.SelfRef1Property)).Message); } @@ -1363,7 +1363,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_another_skip_ navigation.SetForeignKey(orderProductForeignKey); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, typeof(Order).Name), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(SkipNavigation)), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -1388,7 +1388,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_navigation_ customerForeignKey.SetPrincipalToDependent(nameof(Order.Products)); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, typeof(Order).Name), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(Navigation)), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -1410,7 +1410,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_property_th orderEntity.AddProperty(nameof(Order.Products)); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, typeof(Order).Name), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(Property)), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -1432,7 +1432,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_service_pro orderEntity.AddServiceProperty(Order.ProductsProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, typeof(Order).Name), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(ServiceProperty)), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -2046,7 +2046,7 @@ public void Adding_a_new_property_with_a_name_that_already_exists_throws() entityType.AddProperty(Customer.IdProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Id", typeof(Customer).Name, typeof(Customer).Name), + CoreStrings.ConflictingPropertyOrNavigation("Id", typeof(Customer).Name, nameof(Property)), Assert.Throws(() => entityType.AddProperty("Id")).Message); } @@ -2064,7 +2064,7 @@ public void Adding_a_new_property_with_a_name_that_conflicts_with_a_navigation_t customerForeignKey.SetDependentToPrincipal(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, typeof(Order).Name), + CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, nameof(Navigation)), Assert.Throws(() => orderType.AddProperty("Customer")).Message); } @@ -2082,7 +2082,7 @@ public void Adding_a_new_property_with_a_name_that_conflicts_with_a_service_prop customerForeignKey.SetDependentToPrincipal(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(Order)), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(Navigation)), Assert.Throws(() => orderType.AddServiceProperty(Order.CustomerProperty)).Message); } @@ -2094,7 +2094,7 @@ public void Adding_a_new_service_property_with_a_name_that_conflicts_with_a_prop entityType.AddProperty(Customer.OrdersProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Customer.Orders), nameof(Customer), nameof(Customer)), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Customer.Orders), nameof(Customer), nameof(Property)), Assert.Throws(() => entityType.AddServiceProperty(Customer.OrdersProperty)).Message); } @@ -2112,7 +2112,7 @@ public void Adding_a_new_service_property_with_a_name_that_conflicts_with_a_navi customerForeignKey.SetDependentToPrincipal(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(Order)), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(Navigation)), Assert.Throws(() => orderType.AddServiceProperty(Order.CustomerProperty)).Message); } @@ -2124,7 +2124,7 @@ public void Adding_a_new_service_property_with_a_name_that_already_exists_throws entityType.AddServiceProperty(Customer.OrdersProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Customer.Orders), nameof(Customer), nameof(Customer)), + CoreStrings.ConflictingPropertyOrNavigation(nameof(Customer.Orders), nameof(Customer), nameof(ServiceProperty)), Assert.Throws(() => entityType.AddServiceProperty(Customer.OrdersProperty)).Message); } @@ -2202,7 +2202,7 @@ public void AddIndexerProperty_throws_when_entitytype_have_property_with_same_na entityType.AddProperty("Nation", typeof(string)); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Nation", entityType.DisplayName(), entityType.DisplayName()), + CoreStrings.ConflictingPropertyOrNavigation("Nation", entityType.DisplayName(), nameof(Property)), Assert.Throws(() => entityType.AddIndexerProperty("Nation", typeof(string))).Message); Assert.Equal( diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index e96a0e82c7f..15363f4403a 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -1668,7 +1668,7 @@ public void Property_throws_for_navigation() Assert.Equal( CoreStrings.ConflictingPropertyOrNavigation( - nameof(Order.Customer), nameof(Order), nameof(Order)), + nameof(Order.Customer), nameof(Order), nameof(Navigation)), Assert.Throws(() => dependentEntityBuilder .Property(Order.CustomerProperty, ConfigurationSource.Explicit)).Message); } @@ -2487,9 +2487,23 @@ private void VerifyOverrideMembers( } else { + var conflictingKind = firstMemberType switch + { + MemberType.Property => nameof(Property), + MemberType.ComplexProperty => nameof(ComplexProperty), + MemberType.ServiceProperty => nameof(ServiceProperty), + MemberType.Navigation => nameof(Navigation), + MemberType.SkipNavigation => nameof(SkipNavigation), + _ => throw new InvalidOperationException() + }; + var declaringTypeName = firstEntityTypeBuilder.Metadata.DisplayName(); + Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation( - nameof(Order.Products), nameof(SpecialOrder), firstEntityTypeBuilder.Metadata.DisplayName()), + declaringTypeName == nameof(SpecialOrder) + ? CoreStrings.ConflictingPropertyOrNavigation( + nameof(Order.Products), nameof(SpecialOrder), conflictingKind) + : CoreStrings.ConflictingPropertyOrNavigationOnBaseType( + nameof(Order.Products), nameof(SpecialOrder), conflictingKind, declaringTypeName), Assert.Throws(() => ConfigureMember(secondEntityTypeBuilder, secondMemberType, secondSource)) .Message); From 3f0bb6562da17bf093083e2d86d34bfc44c92465 Mon Sep 17 00:00:00 2001 From: m_axwel_l Date: Thu, 7 May 2026 10:43:42 +0500 Subject: [PATCH 2/3] Address review feedback for conflicting member exception - Preserve the public CoreStrings.ConflictingPropertyOrNavigation API by restoring its original (member, type, conflictingType) signature and message, and marking it [Obsolete]. Introduce a new ConflictingPropertyOrNavigationWithKind resource for same-type conflicts so existing extensions are not broken. - Use metadata instance equality (DeclaringType == owningType) instead of comparing DisplayName() strings to decide between the same-type and base-type messages, avoiding misclassification when two distinct types share a simple name. - Replace conflictingMember.GetType().Name with a humanized GetMemberKindString helper (property, complex property, navigation, skip navigation, service property) so user-facing messages do not leak internal CLR class names like RuntimeProperty/SkipNavigation. --- src/EFCore/EFCore.baseline.json | 5 ++- .../Builders/ReferenceNavigationBuilder.cs | 5 +-- .../Internal/PropertyBaseExtensions.cs | 38 +++++++++++++++---- src/EFCore/Properties/CoreStrings.Designer.cs | 17 +++++++-- src/EFCore/Properties/CoreStrings.resx | 5 ++- .../ModelBuilderTest.OneToOne.cs | 2 +- .../Internal/EntityTypeTest.BaseType.cs | 8 ++-- .../Metadata/Internal/EntityTypeTest.cs | 28 +++++++------- .../Internal/InternalEntityTypeBuilderTest.cs | 16 ++++---- 9 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index e65aa1a3317..d5c29b4e872 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -3845,11 +3845,14 @@ "Member": "static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity);" }, { - "Member": "static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingMemberKind);" + "Member": "static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType);" }, { "Member": "static string ConflictingPropertyOrNavigationOnBaseType(object? member, object? type, object? conflictingMemberKind, object? conflictingType);" }, + { + "Member": "static string ConflictingPropertyOrNavigationWithKind(object? member, object? type, object? conflictingMemberKind);" + }, { "Member": "static string ConflictingRelationshipConversions(object? entityType, object? property, object? valueConversion, object? conflictingValueConversion);" }, diff --git a/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs b/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs index 6437c4e5cb9..f9d66346752 100644 --- a/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs @@ -238,9 +238,8 @@ private InternalForeignKeyBuilder WithOneBuilder(MemberIdentity reference) && ReferenceName == referenceName) { throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - referenceName, RelatedEntityType.DisplayName(), - conflictingMemberKind: nameof(Navigation))); + CoreStrings.ConflictingPropertyOrNavigationWithKind( + referenceName, RelatedEntityType.DisplayName(), "navigation")); } var pointsToPrincipal = !foreignKey.IsSelfReferencing() diff --git a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs index af13a7edb9a..0588611cc57 100644 --- a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs @@ -341,9 +341,10 @@ or PropertyAccessMode.FieldDuringConstruction /// /// Builds the message for the diagnostic that fires when a member conflicts with an existing /// member on the structural type or one of its base types. The kind of the conflicting member - /// is taken straight from the runtime type name (e.g. Property, Navigation, - /// SkipNavigation, ComplexProperty, ServiceProperty) so it stays in sync - /// with the codebase without an extra mapping table. + /// is humanized via so the user-facing message uses stable + /// labels like "property", "complex property", "navigation", "skip navigation", or + /// "service property" regardless of whether the conflicting member came from a model or a + /// runtime model. /// /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -356,16 +357,37 @@ public static string FormatConflictingMemberMessage( string newMemberName, IReadOnlyTypeBase owningType) { - var conflictingMemberKind = conflictingMember.GetType().Name; + var conflictingMemberKind = GetMemberKindString(conflictingMember); var owningTypeDisplayName = owningType.DisplayName(); - var declaringTypeDisplayName = ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName(); - return declaringTypeDisplayName == owningTypeDisplayName - ? CoreStrings.ConflictingPropertyOrNavigation(newMemberName, owningTypeDisplayName, conflictingMemberKind) + // Compare the actual metadata instances rather than display names to avoid false positives when + // two distinct types share a simple name (e.g. same name in different namespaces or hierarchies). + return conflictingMember.DeclaringType == owningType + ? CoreStrings.ConflictingPropertyOrNavigationWithKind(newMemberName, owningTypeDisplayName, conflictingMemberKind) : CoreStrings.ConflictingPropertyOrNavigationOnBaseType( - newMemberName, owningTypeDisplayName, conflictingMemberKind, declaringTypeDisplayName); + newMemberName, + owningTypeDisplayName, + conflictingMemberKind, + ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName()); } + /// + /// Returns a human-readable label for the kind of the given member (e.g. "property", + /// "complex property", "navigation", "skip navigation", "service property"). Used to build + /// user-facing diagnostic messages without coupling the message text to internal CLR class + /// names (such as RuntimeProperty or SkipNavigation). + /// + private static string GetMemberKindString(IReadOnlyPropertyBase member) + => member switch + { + IReadOnlyComplexProperty => "complex property", + IReadOnlySkipNavigation => "skip navigation", + IReadOnlyNavigation => "navigation", + IReadOnlyServiceProperty => "service property", + IReadOnlyProperty => "property", + _ => member.GetType().Name + }; + private static string GetNoFieldErrorMessage(IPropertyBase propertyBase) => propertyBase.DeclaringType switch { diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 00f7d15f43b..73157204d98 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -877,12 +877,13 @@ public static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity) entity); /// - /// The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. + /// The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. /// - public static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingMemberKind) + [Obsolete("Use ConflictingPropertyOrNavigationWithKind or ConflictingPropertyOrNavigationOnBaseType instead.")] + public static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType) => string.Format( - GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(type), nameof(conflictingMemberKind)), - member, type, conflictingMemberKind); + GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(type), nameof(conflictingType)), + member, type, conflictingType); /// /// The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. @@ -892,6 +893,14 @@ public static string ConflictingPropertyOrNavigationOnBaseType(object? member, o GetString("ConflictingPropertyOrNavigationOnBaseType", nameof(member), nameof(type), nameof(conflictingMemberKind), nameof(conflictingType)), member, type, conflictingMemberKind, conflictingType); + /// + /// The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. + /// + public static string ConflictingPropertyOrNavigationWithKind(object? member, object? type, object? conflictingMemberKind) + => string.Format( + GetString("ConflictingPropertyOrNavigationWithKind", nameof(member), nameof(type), nameof(conflictingMemberKind)), + member, type, conflictingMemberKind); + /// /// The property '{entityType}.{property}' participates in several relationship chains that have conflicting conversions: '{valueConversion}' and '{conflictingValueConversion}'. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 846eedfc491..cf2c9259443 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -439,11 +439,14 @@ The entity type '{entity}' has both [Keyless] and [PrimaryKey] attributes; one must be removed. - The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. + The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. + + The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. + The property '{entityType}.{property}' participates in several relationship chains that have conflicting conversions: '{valueConversion}' and '{conflictingValueConversion}'. diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs index 05a6c5a4a20..9cedb5df661 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs @@ -3050,7 +3050,7 @@ public virtual void Throws_on_duplicate_navigation_when_self_referencing() var modelBuilder = CreateModelBuilder(); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("SelfRef1", nameof(SelfRef), nameof(Navigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind("SelfRef1", nameof(SelfRef), "navigation"), Assert.Throws(() => modelBuilder.Entity().HasOne(e => e.SelfRef1).WithOne(e => e.SelfRef1)).Message); } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs index 05b9bf42fc9..13d94f1e9ab 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs @@ -370,7 +370,7 @@ public void Adding_property_throws_when_parent_type_has_property_with_same_name( b.BaseType = a; Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(B).Name, nameof(Property), typeof(A).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(B).Name, "property", typeof(A).Name), Assert.Throws(() => b.AddProperty("G")).Message); } @@ -389,7 +389,7 @@ public void Adding_property_throws_when_grandparent_type_has_property_with_same_ d.BaseType = c; Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(D).Name, nameof(Property), typeof(A).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(D).Name, "property", typeof(A).Name), Assert.Throws(() => d.AddProperty("G")).Message); } @@ -406,7 +406,7 @@ public void Adding_property_throws_when_child_type_has_property_with_same_name() b.AddProperty(A.GProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(A).Name, nameof(Property), typeof(B).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(A).Name, "property", typeof(B).Name), Assert.Throws(() => a.AddProperty(A.GProperty)).Message); } @@ -426,7 +426,7 @@ public void Adding_property_throws_when_grandchild_type_has_property_with_same_n d.AddProperty(A.GProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(A).Name, nameof(Property), typeof(D).Name), + CoreStrings.ConflictingPropertyOrNavigationOnBaseType("G", typeof(A).Name, "property", typeof(D).Name), Assert.Throws(() => a.AddProperty(A.GProperty)).Message); } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 88e439b7d1d..0ba53de357e 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -952,7 +952,7 @@ public void Adding_a_new_navigation_with_a_name_that_conflicts_with_a_property_t orderType.AddProperty("Customer"); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, nameof(Property)), + CoreStrings.ConflictingPropertyOrNavigationWithKind("Customer", typeof(Order).Name, "property"), Assert.Throws(() => customerForeignKey.SetDependentToPrincipal("Customer")).Message); } @@ -970,7 +970,7 @@ public void Adding_a_new_navigation_with_a_name_that_conflicts_with_a_service_pr orderType.AddServiceProperty(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(ServiceProperty)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Order.Customer), nameof(Order), "service property"), Assert.Throws(() => customerForeignKey.SetDependentToPrincipal(nameof(Order.Customer))).Message); } @@ -1154,7 +1154,7 @@ public void Throws_when_adding_same_self_referencing_navigation_twice() fk.SetPrincipalToDependent(SelfRef.SelfRef1Property); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(SelfRef.SelfRef1), typeof(SelfRef).Name, nameof(Navigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(SelfRef.SelfRef1), typeof(SelfRef).Name, "navigation"), Assert.Throws(() => fk.SetDependentToPrincipal(SelfRef.SelfRef1Property)).Message); } @@ -1363,7 +1363,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_another_skip_ navigation.SetForeignKey(orderProductForeignKey); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(SkipNavigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Order.Products), typeof(Order).Name, "skip navigation"), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -1388,7 +1388,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_navigation_ customerForeignKey.SetPrincipalToDependent(nameof(Order.Products)); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(Navigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Order.Products), typeof(Order).Name, "navigation"), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -1410,7 +1410,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_property_th orderEntity.AddProperty(nameof(Order.Products)); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(Property)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Order.Products), typeof(Order).Name, "property"), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -1432,7 +1432,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_service_pro orderEntity.AddServiceProperty(Order.ProductsProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Products), typeof(Order).Name, nameof(ServiceProperty)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Order.Products), typeof(Order).Name, "service property"), Assert.Throws(() => orderEntity.AddSkipNavigation( nameof(Order.Products), null, null, productEntity, true, false)).Message); @@ -2046,7 +2046,7 @@ public void Adding_a_new_property_with_a_name_that_already_exists_throws() entityType.AddProperty(Customer.IdProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Id", typeof(Customer).Name, nameof(Property)), + CoreStrings.ConflictingPropertyOrNavigationWithKind("Id", typeof(Customer).Name, "property"), Assert.Throws(() => entityType.AddProperty("Id")).Message); } @@ -2064,7 +2064,7 @@ public void Adding_a_new_property_with_a_name_that_conflicts_with_a_navigation_t customerForeignKey.SetDependentToPrincipal(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, nameof(Navigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind("Customer", typeof(Order).Name, "navigation"), Assert.Throws(() => orderType.AddProperty("Customer")).Message); } @@ -2082,7 +2082,7 @@ public void Adding_a_new_property_with_a_name_that_conflicts_with_a_service_prop customerForeignKey.SetDependentToPrincipal(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(Navigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Order.Customer), nameof(Order), "navigation"), Assert.Throws(() => orderType.AddServiceProperty(Order.CustomerProperty)).Message); } @@ -2094,7 +2094,7 @@ public void Adding_a_new_service_property_with_a_name_that_conflicts_with_a_prop entityType.AddProperty(Customer.OrdersProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Customer.Orders), nameof(Customer), nameof(Property)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Customer.Orders), nameof(Customer), "property"), Assert.Throws(() => entityType.AddServiceProperty(Customer.OrdersProperty)).Message); } @@ -2112,7 +2112,7 @@ public void Adding_a_new_service_property_with_a_name_that_conflicts_with_a_navi customerForeignKey.SetDependentToPrincipal(Order.CustomerProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Order.Customer), nameof(Order), nameof(Navigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Order.Customer), nameof(Order), "navigation"), Assert.Throws(() => orderType.AddServiceProperty(Order.CustomerProperty)).Message); } @@ -2124,7 +2124,7 @@ public void Adding_a_new_service_property_with_a_name_that_already_exists_throws entityType.AddServiceProperty(Customer.OrdersProperty); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation(nameof(Customer.Orders), nameof(Customer), nameof(ServiceProperty)), + CoreStrings.ConflictingPropertyOrNavigationWithKind(nameof(Customer.Orders), nameof(Customer), "service property"), Assert.Throws(() => entityType.AddServiceProperty(Customer.OrdersProperty)).Message); } @@ -2202,7 +2202,7 @@ public void AddIndexerProperty_throws_when_entitytype_have_property_with_same_na entityType.AddProperty("Nation", typeof(string)); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation("Nation", entityType.DisplayName(), nameof(Property)), + CoreStrings.ConflictingPropertyOrNavigationWithKind("Nation", entityType.DisplayName(), "property"), Assert.Throws(() => entityType.AddIndexerProperty("Nation", typeof(string))).Message); Assert.Equal( diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index 15363f4403a..7b56e19f017 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -1667,8 +1667,8 @@ public void Property_throws_for_navigation() ConfigurationSource.Explicit); Assert.Equal( - CoreStrings.ConflictingPropertyOrNavigation( - nameof(Order.Customer), nameof(Order), nameof(Navigation)), + CoreStrings.ConflictingPropertyOrNavigationWithKind( + nameof(Order.Customer), nameof(Order), "navigation"), Assert.Throws(() => dependentEntityBuilder .Property(Order.CustomerProperty, ConfigurationSource.Explicit)).Message); } @@ -2489,18 +2489,18 @@ private void VerifyOverrideMembers( { var conflictingKind = firstMemberType switch { - MemberType.Property => nameof(Property), - MemberType.ComplexProperty => nameof(ComplexProperty), - MemberType.ServiceProperty => nameof(ServiceProperty), - MemberType.Navigation => nameof(Navigation), - MemberType.SkipNavigation => nameof(SkipNavigation), + MemberType.Property => "property", + MemberType.ComplexProperty => "complex property", + MemberType.ServiceProperty => "service property", + MemberType.Navigation => "navigation", + MemberType.SkipNavigation => "skip navigation", _ => throw new InvalidOperationException() }; var declaringTypeName = firstEntityTypeBuilder.Metadata.DisplayName(); Assert.Equal( declaringTypeName == nameof(SpecialOrder) - ? CoreStrings.ConflictingPropertyOrNavigation( + ? CoreStrings.ConflictingPropertyOrNavigationWithKind( nameof(Order.Products), nameof(SpecialOrder), conflictingKind) : CoreStrings.ConflictingPropertyOrNavigationOnBaseType( nameof(Order.Products), nameof(SpecialOrder), conflictingKind, declaringTypeName), From 730b0ba5d06492c6c4fdf2bf94123558a83a239c Mon Sep 17 00:00:00 2001 From: m_axwel_l Date: Fri, 8 May 2026 11:04:58 +0500 Subject: [PATCH 3/3] Apply maintainer feedback to conflicting member message - Reword the two new resources to lead with "The member '{member}'" instead of "The property or navigation '{member}'", since the message can describe any kind of member (property, complex property, navigation, skip navigation, service property), so the original lead was misleading when paired with a non-property/navigation kind. - Drop the ConflictingPropertyOrNavigation resource and its accessor outright. The previous commit kept it [Obsolete] for extension back-compat, but the maintainer asked to remove it; nothing inside the codebase calls it after the previous commit. --- src/EFCore/EFCore.baseline.json | 3 --- src/EFCore/Properties/CoreStrings.Designer.cs | 13 ++----------- src/EFCore/Properties/CoreStrings.resx | 7 ++----- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index d5c29b4e872..404810b7d4a 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -3844,9 +3844,6 @@ { "Member": "static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity);" }, - { - "Member": "static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType);" - }, { "Member": "static string ConflictingPropertyOrNavigationOnBaseType(object? member, object? type, object? conflictingMemberKind, object? conflictingType);" }, diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 73157204d98..3bc51954412 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -877,16 +877,7 @@ public static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity) entity); /// - /// The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. - /// - [Obsolete("Use ConflictingPropertyOrNavigationWithKind or ConflictingPropertyOrNavigationOnBaseType instead.")] - public static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType) - => string.Format( - GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(type), nameof(conflictingType)), - member, type, conflictingType); - - /// - /// The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. + /// The member '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. /// public static string ConflictingPropertyOrNavigationOnBaseType(object? member, object? type, object? conflictingMemberKind, object? conflictingType) => string.Format( @@ -894,7 +885,7 @@ public static string ConflictingPropertyOrNavigationOnBaseType(object? member, o member, type, conflictingMemberKind, conflictingType); /// - /// The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. + /// The member '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. /// public static string ConflictingPropertyOrNavigationWithKind(object? member, object? type, object? conflictingMemberKind) => string.Format( diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index cf2c9259443..eaa732c05d5 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -438,14 +438,11 @@ The entity type '{entity}' has both [Keyless] and [PrimaryKey] attributes; one must be removed. - - The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. - - The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. + The member '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists on the '{conflictingType}' type. Remove the existing {conflictingMemberKind} first. - The property or navigation '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. + The member '{member}' cannot be added to the '{type}' type because a {conflictingMemberKind} with the same name already exists. Remove the existing {conflictingMemberKind} first. The property '{entityType}.{property}' participates in several relationship chains that have conflicting conversions: '{valueConversion}' and '{conflictingValueConversion}'.