Skip to content

Make primitive collection element type a creation-time concern#38484

Draft
Copilot wants to merge 6 commits into
mainfrom
copilot/add-imemberclassifier-elementtype
Draft

Make primitive collection element type a creation-time concern#38484
Copilot wants to merge 6 commits into
mainfrom
copilot/add-imemberclassifier-elementtype

Conversation

Copilot AI commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

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 implements IEnumerable<> 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 an InvalidCastException when building the provider value comparer.

// CustomId : IEnumerable<byte>, with a key value converter CustomId -> int
modelBuilder.Entity<ValueCategory>().Property(x => x.Id)
    .HasConversion(x => x.Id, x => new CustomId { Id = x });

// The owned FK property inherits the converter from the principal key.
// It was discovered as a primitive collection of byte, and the element type
// was never removed -> ValueConverter<CustomId,int> cast to
// ValueConverter<IEnumerable<byte>,int> at model finalization.
modelBuilder.Entity<QueryResult>().OwnsOne(q => q.Value).Property(x => x.CategoryId);

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 an IModelFinalizingConvention. In ProcessModelFinalizing it removes a convention-set element type when the resolved mapping has no ElementTypeMapping (e.g. a value converter applies). Removal uses ConfigurationSource.Convention, so explicitly configured primitive collections are left untouched (Convention does not override Explicit). Because it only acts on the ElementTypeMapping is null case and never compares element CLR types, the nullable-inner-element case (e.g. List<int?[]>) is a non-issue.
  • Tests: property-bag collection declarations updated from Property<List<string>>(...) to PrimitiveCollection<List<string>>(...), consistent with element types coming from the configured primitive collection rather than a finalization convention.
  • API baseline: EFCore.baseline.json regenerated (removed ElementMappingConvention; ElementTypeChangedConvention now implements IModelFinalizingConvention).

Note: removing RuntimeProperty.SetElementType and 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.

Copilot AI and others added 2 commits June 23, 2026 20:39
… 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>
Comment thread src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
Comment thread src/EFCore/Metadata/Internal/Property.cs Outdated
Comment thread src/EFCore/Metadata/IMemberClassifier.cs Outdated
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Comment thread src/EFCore/Metadata/Conventions/ElementMappingConvention.cs Outdated
Comment thread src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs Outdated
Comment thread src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs Outdated
Comment thread src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs Outdated
Comment thread src/EFCore/Metadata/Conventions/ElementMappingConvention.cs Outdated
…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
Comment thread src/EFCore/EFCore.baseline.json Outdated
Comment thread src/EFCore/Metadata/Conventions/ElementMappingConvention.cs Outdated
Comment thread src/EFCore/Metadata/Conventions/ElementMappingConvention.cs Outdated
Comment thread src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs Outdated
…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
Copilot AI requested a review from AndriySvyryd June 24, 2026 05:26
@AndriySvyryd AndriySvyryd requested a review from Copilot June 24, 2026 07:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ElementMappingConvention and 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 thread src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs
Comment thread src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs
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);
Comment thread src/EFCore.Relational/EFCore.Relational.baseline.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants