Improve conflicting member exception message#38222
Improve conflicting member exception message#38222m-x-shokhzod wants to merge 2 commits intodotnet:mainfrom
Conversation
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 dotnet#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 dotnet#36487
5379f2a to
9425fcf
Compare
There was a problem hiding this comment.
Pull request overview
This PR improves the exception thrown when adding a structural type member whose name conflicts with an existing member, by making the message more specific (conflicting member kind), less redundant (declaring type only when different), and more actionable (explicit removal guidance). It updates the core string resources, introduces a formatting helper, rewires the relevant throw sites, and updates unit/spec tests to assert the new messages.
Changes:
- Updated
CoreStrings.ConflictingPropertyOrNavigationand addedConflictingPropertyOrNavigationOnBaseTypeto differentiate same-type vs inherited conflicts and to include removal guidance. - Added
FormatConflictingMemberMessagehelper and updated all relevant throw sites to use it (or the new resource directly). - Updated affected tests to assert against the new, more detailed exception messages.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs | Updates expectations for the new conflict messages (including kind/base-type distinctions). |
| test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs | Updates many conflict-related assertions to include the conflicting member kind. |
| test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs | Updates base-type conflict tests to assert the new base-type-specific resource. |
| test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OneToOne.cs | Updates spec test assertion for duplicate self-referencing navigation conflict message. |
| src/EFCore/Properties/CoreStrings.resx | Rewords conflict message, adds base-type-specific resource, and adds removal guidance sentence. |
| src/EFCore/Properties/CoreStrings.Designer.cs | Regenerates resource accessors for updated/new string resources (including signature changes). |
| src/EFCore/Metadata/Internal/TypeBase.cs | Switches conflict throws to use the new formatting helper. |
| src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs | Adds FormatConflictingMemberMessage helper to centralize conflict message formatting. |
| src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs | Routes conflict throw through the new helper. |
| src/EFCore/Metadata/Internal/EntityType.cs | Routes multiple conflict throws through the new helper. |
| src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs | Updates conflict throw to use the new resource signature (supplying kind). |
| src/EFCore/EFCore.baseline.json | Updates API baseline for the modified/added CoreStrings members. |
Files not reviewed (1)
- src/EFCore/Properties/CoreStrings.Designer.cs: Language not supported
| var declaringTypeDisplayName = ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName(); | ||
|
|
||
| return declaringTypeDisplayName == owningTypeDisplayName | ||
| ? CoreStrings.ConflictingPropertyOrNavigation(newMemberName, owningTypeDisplayName, conflictingMemberKind) | ||
| : CoreStrings.ConflictingPropertyOrNavigationOnBaseType( | ||
| newMemberName, owningTypeDisplayName, conflictingMemberKind, declaringTypeDisplayName); |
| var conflictingMemberKind = conflictingMember.GetType().Name; | ||
| var owningTypeDisplayName = owningType.DisplayName(); | ||
| var declaringTypeDisplayName = ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName(); |
There was a problem hiding this comment.
The exception text now includes {conflictingMemberKind}, but conflictingMemberKind is populated via conflictingMember.GetType().Name. This couples a user-facing message to internal implementation type names (and would produce values like RuntimeProperty/RuntimeSkipNavigation if a runtime-model member ever flows here), and also yields non-humanized labels like SkipNavigation/ComplexProperty. Consider implementing the planned kind-label helper (e.g. map by interface/type to "property", "complex property", "navigation", "skip navigation", "service property") and using that instead of GetType().Name.
- 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.
f4c9993 to
3f0bb65
Compare
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:
Add two helpers in PropertyBaseExtensions:
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.