Make primitive collection element type a creation-time concern#38484
Draft
Copilot wants to merge 6 commits into
Draft
Make primitive collection element type a creation-time concern#38484Copilot wants to merge 6 commits into
Copilot wants to merge 6 commits into
Conversation
… and convention Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…solution, add clarifying comments Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot created this pull request from a session on behalf of
AndriySvyryd
June 23, 2026 21:33
View session
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…e; keep ElementMappingConvention Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot
AI
changed the title
Make primitive collection element type a creation-time concern
Reconcile stale element type at model finalization for converter-backed properties
Jun 24, 2026
…emove ElementMappingConvention Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot
AI
changed the title
Reconcile stale element type at model finalization for converter-backed properties
Make primitive collection element type a creation-time concern
Jun 24, 2026
There was a problem hiding this comment.
Pull request overview
Refactors EF Core’s primitive-collection element type handling to be a creation-time concern (set during discovery/configuration) and removes finalization-time promotion, while adding a finalization-time cleanup to demote stale convention-set element types when the resolved mapping is scalar (e.g., due to an inherited value converter).
Changes:
- Remove
ElementMappingConventionand the property element-type-changed convention pipeline; element types are no longer assigned at model finalization. - Introduce primitive-collection-specific configuration APIs (
PrimitiveCollection(...),AddProperty(..., elementType)) and flow the element type into property creation. - Update conventions and tests to configure primitive collections explicitly (and update API baselines accordingly).
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/EFCore.Tests/Metadata/Internal/PropertyTest.cs | Updates tests to set element type at property creation time via new overload. |
| test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs | Removes coverage for the removed property-element-type-changed convention dispatch; updates element-type tests to use PrimitiveCollection. |
| test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs | Updates property-bag mappings to use PrimitiveCollection for collection properties. |
| src/EFCore/Metadata/MemberClassifier.cs | Extends primitive property candidate classification to also output discovered collection element type when applicable. |
| src/EFCore/Metadata/Internal/TypeBase.cs | Threads optional element type through internal AddProperty creation path and adds an IMutableTypeBase.AddProperty(..., elementType) implementation. |
| src/EFCore/Metadata/Internal/Property.cs | Allows element type annotation to be set during Property construction; adjusts SetElementType semantics and removes old convention hook. |
| src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs | Adds internal element-type setting helper and convention-level PrimitiveCollection plumbing; adds IConventionTypeBaseBuilder implementations. |
| src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs | Ensures setting scalar conversions clears element type; removes obsolete element-type APIs. |
| src/EFCore/Metadata/IMutableTypeBase.cs | Adds public mutable metadata API for adding a primitive collection property with explicit element type. |
| src/EFCore/Metadata/IMutableProperty.cs | Removes mutable property SetElementType API. |
| src/EFCore/Metadata/IMemberClassifier.cs | Updates signature/docs to include out Type? elementType. |
| src/EFCore/Metadata/IConventionProperty.cs | Removes convention property SetElementType API. |
| src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs | Switches discovery to create primitive collections via PrimitiveCollection(...) rather than setting element type later. |
| src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs | Removes dependency on the removed property element-type-changed convention. |
| src/EFCore/Metadata/Conventions/IPropertyElementTypeChangedConvention.cs | Deletes the property element-type-changed convention interface. |
| src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs | Removes immediate-scope dispatch for property element type changes. |
| src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs | Removes delayed-node plumbing for property element type changes. |
| src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs | Removes dispatcher entry point for property element type changes. |
| src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs | Removes abstract convention-scope method for property element type changes. |
| src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs | Removes ElementMappingConvention from the default convention set. |
| src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs | Reworks element-type propagation to FKs without the removed public convention API; adds finalization-time demotion of stale convention-set element types. |
| src/EFCore/Metadata/Conventions/ElementMappingConvention.cs | Deletes the finalization-time element-type assignment convention. |
| src/EFCore/Metadata/Conventions/ConventionSet.cs | Removes storage/registration plumbing for property element-type-changed conventions. |
| src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs | Adds convention-builder APIs for PrimitiveCollection and CanHavePrimitiveCollection. |
| src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs | Removes convention property builder element-type APIs. |
| src/EFCore/EFCore.baseline.json | Updates API baseline for removed conventions/APIs and added primitive-collection APIs. |
| src/EFCore.Relational/Metadata/RelationalMemberClassifier.cs | Updates override signature for IsCandidatePrimitiveProperty to include out elementType. |
| src/EFCore.Relational/EFCore.Relational.baseline.json | Updates relational API baseline due to signature changes and baseline regeneration effects. |
Comment on lines
+2337
to
+2343
| bool IConventionTypeBaseBuilder.CanHavePrimitiveCollection(MemberInfo memberInfo, Type? elementType, bool fromDataAnnotation) | ||
| => CanHaveProperty( | ||
| memberInfo.GetMemberType(), | ||
| memberInfo.Name, | ||
| memberInfo, | ||
| fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, | ||
| fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The element-type-as-creation-time refactoring eagerly assigns an element type during property discovery whenever the member's resolved mapping has an
ElementTypeMapping. A property whose CLR type implementsIEnumerable<>but later resolves to a scalar via a value converter kept that stale element type, since the converter is inherited on-demand from the principal key (Property.GetConversion()) with no set-time hook to remove it. At finalization this caused anInvalidCastExceptionwhen building the provider value comparer.The element type is now purely a creation-time concern: it is derived from the property type when the property is configured as a primitive collection (via
PrimitiveCollection/discovery). A property is a primitive collection only if configured as one — there is no finalization-time promotion. The only remaining finalization work is to demote a stale element type that was speculatively assigned during discovery but turns out not to be a collection once the mapping resolves (e.g. an inherited value converter makes the property scalar).Changes
ElementMappingConvention: removed. The convention no longer assigns element types at finalization.ElementTypeChangedConvention: now also anIModelFinalizingConvention. InProcessModelFinalizingit removes a convention-set element type when the resolved mapping has noElementTypeMapping(e.g. a value converter applies). Removal usesConfigurationSource.Convention, so explicitly configured primitive collections are left untouched (Convention does not override Explicit). Because it only acts on theElementTypeMapping is nullcase and never compares element CLR types, the nullable-inner-element case (e.g.List<int?[]>) is a non-issue.Property<List<string>>(...)toPrimitiveCollection<List<string>>(...), consistent with element types coming from the configured primitive collection rather than a finalization convention.EFCore.baseline.jsonregenerated (removedElementMappingConvention;ElementTypeChangedConventionnow implementsIModelFinalizingConvention).Note: removing
RuntimeProperty.SetElementTypeand threading the element type through the constructor is being tracked separately, pending confirmation of the constructor shape, as it requires regenerating the compiled-model baselines across all providers.