Avoid reflective default key comparer creation in compiled models (NativeAOT)#38427
Avoid reflective default key comparer creation in compiled models (NativeAOT)#38427Copilot wants to merge 4 commits into
Conversation
…on (AOT) Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a NativeAOT incompatibility in compiled models by avoiding reflection-based default ValueComparer creation for key comparers. It does so by flowing the generic type argument (TKey / TProperty) into key-comparer construction so that ValueComparer.CreateDefault<T>(...) can be used instead of the [RequiresDynamicCode] ValueComparer.CreateDefault(Type, ...).
Changes:
- Add
CoreTypeMapping.FindKeyComparer()to access an already-configured/created key comparer without triggering reflective default creation. - Add
RuntimeProperty.GetKeyValueComparer(Func<ValueComparer>)to allow supplying an AOT-safe generic default factory while still honoring any comparer configured in the model/type mapping. - Update key comparer creation paths (
IProperty.CreateKeyEqualityComparer<TProperty>(),SimplePrincipalKeyValueFactory<TKey>) and add focused unit tests (RuntimePropertyTest).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/EFCore.Tests/Metadata/RuntimePropertyTest.cs | Adds tests validating the new factory-based comparer path doesn’t force creation of CoreTypeMapping.KeyComparer and still honors model-provided comparers. |
| src/EFCore/Storage/CoreTypeMapping.cs | Introduces FindKeyComparer() to read an existing key comparer without creating a default one. |
| src/EFCore/Metadata/RuntimeProperty.cs | Adds a GetKeyValueComparer(Func<ValueComparer>) overload to support a generic default factory and avoid reflection-based comparer creation. |
| src/EFCore/Metadata/IProperty.cs | Updates CreateKeyEqualityComparer<TProperty>() to use the new factory-based path for RuntimeProperty. |
| src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs | Updates key factory construction to use the new factory-based key comparer path for RuntimeProperty. |
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
| && (property.IsKey() | ||
| || property.IsForeignKey() | ||
| || property.IsUniqueIndex()) | ||
| && keyValueComparer == typeMappingKeyComparer |
There was a problem hiding this comment.
@copilot If the keyValueComparer is the same as the typeMappingKeyComparer then why would we need to generate code to call SetKeyComparer? We already generate code for the comparer inside the type mapping, so GetKeyValueComparer would just fallback to property.TypeMapping.KeyComparer
So, CreateDefaultTypeMapping should be changed to never return null.
There was a problem hiding this comment.
Addressed in 6a5387f. The NativeAOT default key-comparer case now flows through type-mapping generation, CreateDefaultTypeMapping always returns the default mapping instance, and the redundant property-level SetKeyComparer(...) emission was removed.
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
|
Superseded by #38440 |
Compiled models crash under NativeAOT when constructing key value factories for key types whose generic instantiation isn't statically present (e.g.
Guid). Resolving the key comparer flowed throughCoreTypeMapping.KeyComparer, which uses the reflectiveValueComparer.CreateDefault(Type, bool)→MethodInfo.MakeGenericMethod()— a[RequiresDynamicCode]API that throws at runtime under AOT (inthappened to work only becauseCreateDefault<int>is statically referenced elsewhere).Changes
ValueComparer.CreateDefault<T>(favorStructuralComparisons: true)path instead of the reflective lazy fallback.CreateDefaultTypeMappingnow always returns the default mapping instance.SetKeyComparer(...)emission is removed in that case, soGetKeyValueComparer()falls back toproperty.TypeMapping.KeyComparer.SimplePrincipalKeyValueFactory<TKey>andIProperty.CreateKeyEqualityComparer<TProperty>()go back to the normalGetKeyValueComparer()codepath for all property implementations.RuntimeProperty.GetKeyValueComparer(Func<ValueComparer>)overload andCoreTypeMapping.FindKeyComparer()accessor are removed.Notes
MakeGenericTypepath inToNullableComparer(nullable-FK dependent factories) remains out of scope and unaffected behaviorally.RuntimePropertyTest.