diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index d06a982a176..d2d8f723f72 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -1254,9 +1254,10 @@ private void Create( var keyValueComparer = property.GetKeyValueComparer(); var typeMappingKeyComparer = property.GetTypeMapping().KeyComparer; - if (valueComparer != keyValueComparer - && (!parameters.ForNativeAot || keyValueComparer != typeMappingKeyComparer) - && (parameters.ForNativeAot || property[CoreAnnotationNames.ValueComparer] != null)) + if ((valueComparer != keyValueComparer + && (!parameters.ForNativeAot || keyValueComparer != typeMappingKeyComparer) + && (parameters.ForNativeAot || property[CoreAnnotationNames.ValueComparer] != null)) + ) { SetValueComparer(keyValueComparer, typeMappingKeyComparer, nameof(CoreTypeMapping.KeyComparer), propertyParameters); } diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 7b888e559ec..9caf24ac49c 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -2483,8 +2483,42 @@ public override bool Create( return false; } - var defaultInstance = (RelationalTypeMapping?)CreateDefaultTypeMapping(relationalTypeMapping, parameters); - if (defaultInstance == null) + var defaultInstance = (RelationalTypeMapping)CreateDefaultTypeMapping(relationalTypeMapping, parameters); + var comparer = valueComparer ?? relationalTypeMapping.Comparer; + var keyComparer = keyValueComparer ?? relationalTypeMapping.KeyComparer; + var providerComparer = providerValueComparer ?? relationalTypeMapping.ProviderValueComparer; + var storeTypeDifferent = relationalTypeMapping.StoreType != defaultInstance.StoreType; + var sizeDifferent = relationalTypeMapping.Size != null + && relationalTypeMapping.Size != defaultInstance.Size; + var precisionDifferent = relationalTypeMapping.Precision != null + && relationalTypeMapping.Precision != defaultInstance.Precision; + var scaleDifferent = relationalTypeMapping.Scale != null + && relationalTypeMapping.Scale != defaultInstance.Scale; + var dbTypeDifferent = relationalTypeMapping.DbType != null + && relationalTypeMapping.DbType != defaultInstance.DbType; + var isUnicodeDifferent = relationalTypeMapping.IsUnicode != defaultInstance.IsUnicode; + var isFixedLengthDifferent = relationalTypeMapping.IsFixedLength != defaultInstance.IsFixedLength; + var typeDifferent = relationalTypeMapping.Converter == null + && relationalTypeMapping.ClrType != defaultInstance.ClrType; + var storeTypePostfixDifferent = relationalTypeMapping.StoreTypePostfix != defaultInstance.StoreTypePostfix; + if (valueComparer == null + && keyValueComparer == null + && providerValueComparer == null + && comparer == defaultInstance.Comparer + && keyComparer == defaultInstance.KeyComparer + && providerComparer == defaultInstance.ProviderValueComparer + && !storeTypeDifferent + && !sizeDifferent + && !precisionDifferent + && !scaleDifferent + && !dbTypeDifferent + && !isUnicodeDifferent + && !isFixedLengthDifferent + && relationalTypeMapping.Converter == defaultInstance.Converter + && !typeDifferent + && !storeTypePostfixDifferent + && relationalTypeMapping.JsonValueReaderWriter == defaultInstance.JsonValueReaderWriter + && relationalTypeMapping.ElementTypeMapping == defaultInstance.ElementTypeMapping) { return true; } @@ -2495,27 +2529,15 @@ public override bool Create( mainBuilder .Append("comparer: "); - Create(valueComparer ?? relationalTypeMapping.Comparer, parameters, code); + Create(comparer, parameters, code); mainBuilder.AppendLine(",") .Append("keyComparer: "); - Create(keyValueComparer ?? relationalTypeMapping.KeyComparer, parameters, code); + Create(keyComparer, parameters, code); mainBuilder.AppendLine(",") .Append("providerValueComparer: "); - Create(providerValueComparer ?? relationalTypeMapping.ProviderValueComparer, parameters, code); - - var storeTypeDifferent = relationalTypeMapping.StoreType != defaultInstance.StoreType; - var sizeDifferent = relationalTypeMapping.Size != null - && relationalTypeMapping.Size != defaultInstance.Size; - var precisionDifferent = relationalTypeMapping.Precision != null - && relationalTypeMapping.Precision != defaultInstance.Precision; - var scaleDifferent = relationalTypeMapping.Scale != null - && relationalTypeMapping.Scale != defaultInstance.Scale; - var dbTypeDifferent = relationalTypeMapping.DbType != null - && relationalTypeMapping.DbType != defaultInstance.DbType; - var isUnicodeDifferent = relationalTypeMapping.IsUnicode != defaultInstance.IsUnicode; - var isFixedLengthDifferent = relationalTypeMapping.IsFixedLength != defaultInstance.IsFixedLength; + Create(providerComparer, parameters, code); if (storeTypeDifferent || sizeDifferent || precisionDifferent @@ -2586,15 +2608,12 @@ public override bool Create( Create(relationalTypeMapping.Converter, parameters, code); } - var typeDifferent = relationalTypeMapping.Converter == null - && relationalTypeMapping.ClrType != defaultInstance.ClrType; if (typeDifferent) { mainBuilder.AppendLine(",") .Append($"clrType: {code.Literal(relationalTypeMapping.ClrType)}"); } - var storeTypePostfixDifferent = relationalTypeMapping.StoreTypePostfix != defaultInstance.StoreTypePostfix; if (storeTypePostfixDifferent) { mainBuilder.AppendLine(",") diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs index 65daf72ad25..8172bb96bed 100644 --- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs @@ -586,6 +586,27 @@ public static void CreateJsonValueReaderWriter( } } + /// + public virtual bool Create( + CoreTypeMapping typeMapping, + IProperty property, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var keyValueComparer = property.GetKeyValueComparer(); + var typeMappingKeyComparer = property.GetTypeMapping().KeyComparer; + keyValueComparer = parameters.ForNativeAot + && (property.IsKey() + || property.IsForeignKey() + || property.IsUniqueIndex()) + && keyValueComparer == typeMappingKeyComparer + && keyValueComparer.IsDefault() + && keyValueComparer.Type == property.ClrType + ? keyValueComparer + : null; + + return Create(typeMapping, parameters, keyValueComparer: keyValueComparer); + } + /// public virtual bool Create( CoreTypeMapping typeMapping, @@ -597,7 +618,21 @@ public virtual bool Create( var mainBuilder = parameters.MainBuilder; var code = Dependencies.CSharpHelper; var defaultInstance = CreateDefaultTypeMapping(typeMapping, parameters); - if (defaultInstance == null) + var comparer = valueComparer ?? typeMapping.Comparer; + var keyComparer = keyValueComparer ?? typeMapping.KeyComparer; + var providerComparer = providerValueComparer ?? typeMapping.ProviderValueComparer; + var typeDifferent = typeMapping.Converter == null + && typeMapping.ClrType != defaultInstance.ClrType; + if (valueComparer == null + && keyValueComparer == null + && providerValueComparer == null + && comparer == defaultInstance.Comparer + && keyComparer == defaultInstance.KeyComparer + && providerComparer == defaultInstance.ProviderValueComparer + && typeMapping.Converter == defaultInstance.Converter + && !typeDifferent + && typeMapping.JsonValueReaderWriter == defaultInstance.JsonValueReaderWriter + && typeMapping.ElementTypeMapping == defaultInstance.ElementTypeMapping) { return true; } @@ -608,15 +643,15 @@ public virtual bool Create( mainBuilder .Append("comparer: "); - Create(valueComparer ?? typeMapping.Comparer, parameters, code); + Create(comparer, parameters, code); mainBuilder.AppendLine(",") .Append("keyComparer: "); - Create(keyValueComparer ?? typeMapping.KeyComparer, parameters, code); + Create(keyComparer, parameters, code); mainBuilder.AppendLine(",") .Append("providerValueComparer: "); - Create(providerValueComparer ?? typeMapping.ProviderValueComparer, parameters, code); + Create(providerComparer, parameters, code); if (typeMapping.Converter != null && typeMapping.Converter != defaultInstance.Converter) @@ -627,8 +662,6 @@ public virtual bool Create( Create(typeMapping.Converter, parameters, code); } - var typeDifferent = typeMapping.Converter == null - && typeMapping.ClrType != defaultInstance.ClrType; if (typeDifferent) { mainBuilder.AppendLine(",") @@ -666,7 +699,7 @@ public virtual bool Create( /// 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. /// - protected virtual CoreTypeMapping? CreateDefaultTypeMapping( + protected virtual CoreTypeMapping CreateDefaultTypeMapping( CoreTypeMapping typeMapping, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { @@ -689,7 +722,6 @@ public virtual bool Create( .Append(code.Reference(typeMappingType)) .Append(".Default"); - var defaultInstance = (CoreTypeMapping)defaultProperty.GetValue(null)!; - return typeMapping == defaultInstance ? null : defaultInstance; + return (CoreTypeMapping)defaultProperty.GetValue(null)!; } } diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextAssemblyAttributes.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextAssemblyAttributes.cs new file mode 100644 index 00000000000..c32d233bbdb --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextAssemblyAttributes.cs @@ -0,0 +1,9 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using TestNamespace; + +#pragma warning disable 219, 612, 618 +#nullable disable + +[assembly: DbContextModel(typeof(DbContext), typeof(DbContextModel), ProviderName = "Microsoft.EntityFrameworkCore.InMemory")] diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextModel.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextModel.cs new file mode 100644 index 00000000000..e5896994b93 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextModel.cs @@ -0,0 +1,47 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace; + +[DbContext(typeof(DbContext))] +public partial class DbContextModel : RuntimeModel +{ + private static readonly bool _useOldBehavior31751 = + System.AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31751", out var enabled31751) && enabled31751; + + static DbContextModel() + { + var model = new DbContextModel(); + + if (_useOldBehavior31751) + { + model.Initialize(); + } + else + { + var thread = new System.Threading.Thread(RunInitialization, 10 * 1024 * 1024); + thread.Start(); + thread.Join(); + + void RunInitialization() + { + model.Initialize(); + } + } + + model.Customize(); + _instance = (DbContextModel)model.FinalizeModel(); + } + + private static DbContextModel _instance; + public static IModel Instance => _instance; + + partial void Initialize(); + + partial void Customize(); +} diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextModelBuilder.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextModelBuilder.cs new file mode 100644 index 00000000000..bddee0d2e7d --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/DbContextModelBuilder.cs @@ -0,0 +1,25 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace; + +public partial class DbContextModel +{ + private DbContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + + partial void Initialize() + { + var index = IndexEntityType.Create(this); + + IndexEntityType.CreateAnnotations(index); + + } +} diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/IndexEntityType.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/IndexEntityType.cs new file mode 100644 index 00000000000..b04289b7f44 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/IndexEntityType.cs @@ -0,0 +1,129 @@ +// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Scaffolding; +using Microsoft.EntityFrameworkCore.Storage.Json; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace; + +[EntityFrameworkInternal] +public partial class IndexEntityType +{ + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelInMemoryTest+Index", + typeof(CompiledModelInMemoryTest.Index), + baseEntityType, + propertyCount: 1, + keyCount: 1); + + var id = runtimeEntityType.AddProperty( + "Id", + typeof(Guid), + propertyInfo: typeof(CompiledModelInMemoryTest.Index).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CompiledModelInMemoryTest.Index).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw, + sentinel: new Guid("00000000-0000-0000-0000-000000000000")); + id.SetGetter( + Guid (CompiledModelInMemoryTest.Index instance) => IndexUnsafeAccessors.Id(instance), + bool (CompiledModelInMemoryTest.Index instance) => IndexUnsafeAccessors.Id(instance) == new Guid("00000000-0000-0000-0000-000000000000")); + id.SetSetter( + CompiledModelInMemoryTest.Index (CompiledModelInMemoryTest.Index instance, Guid value) => + { + IndexUnsafeAccessors.Id(instance) = value; + return instance; + }); + id.SetMaterializationSetter( + CompiledModelInMemoryTest.Index (CompiledModelInMemoryTest.Index instance, Guid value) => + { + IndexUnsafeAccessors.Id(instance) = value; + return instance; + }); + id.SetAccessors( + Guid (IInternalEntry entry) => (entry.FlaggedAsStoreGenerated(0) ? entry.ReadStoreGeneratedValue(0) : (entry.FlaggedAsTemporary(0) && IndexUnsafeAccessors.Id(((CompiledModelInMemoryTest.Index)(entry.Entity))) == new Guid("00000000-0000-0000-0000-000000000000") ? entry.ReadTemporaryValue(0) : IndexUnsafeAccessors.Id(((CompiledModelInMemoryTest.Index)(entry.Entity))))), + Guid (IInternalEntry entry) => IndexUnsafeAccessors.Id(((CompiledModelInMemoryTest.Index)(entry.Entity))), + Guid (IInternalEntry entry) => entry.ReadOriginalValue(id, 0), + Guid (IInternalEntry entry) => ((InternalEntityEntry)entry).ReadRelationshipSnapshotValue(id, 0)); + id.SetPropertyIndexes( + index: 0, + originalValueIndex: 0, + shadowIndex: -1, + relationshipIndex: 0, + storeGenerationIndex: 0); + id.TypeMapping = InMemoryTypeMapping.Default.Clone( + comparer: new ValueComparer( + bool (Guid v1, Guid v2) => v1 == v2, + int (Guid v) => ((object)v).GetHashCode(), + Guid (Guid v) => v), + keyComparer: new ValueComparer( + bool (Guid v1, Guid v2) => v1 == v2, + int (Guid v) => ((object)v).GetHashCode(), + Guid (Guid v) => v), + providerValueComparer: new ValueComparer( + bool (Guid v1, Guid v2) => v1 == v2, + int (Guid v) => ((object)v).GetHashCode(), + Guid (Guid v) => v), + clrType: typeof(Guid), + jsonValueReaderWriter: JsonGuidReaderWriter.Instance); + id.SetCurrentValueComparer(new EntryCurrentValueComparer(id)); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + var id = runtimeEntityType.FindProperty("Id"); + var key = runtimeEntityType.FindKey(new[] { id }); + key.SetPrincipalKeyValueFactory(KeyValueFactoryFactory.CreateSimpleNonNullableFactory(key)); + key.SetIdentityMapFactory(IdentityMapFactoryFactory.CreateFactory(key)); + runtimeEntityType.SetOriginalValuesFactory( + ISnapshot (IInternalEntry source) => + { + var structuralType = ((CompiledModelInMemoryTest.Index)(source.Entity)); + return ((ISnapshot)(new Snapshot(((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id))))); + }); + runtimeEntityType.SetStoreGeneratedValuesFactory( + ISnapshot () => ((ISnapshot)(new Snapshot(((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(default(Guid)))))); + runtimeEntityType.SetTemporaryValuesFactory( + ISnapshot (IInternalEntry source) => ((ISnapshot)(new Snapshot(default(Guid))))); + runtimeEntityType.SetShadowValuesFactory( + ISnapshot (IDictionary source) => Snapshot.Empty); + runtimeEntityType.SetEmptyShadowValuesFactory( + ISnapshot () => Snapshot.Empty); + runtimeEntityType.SetRelationshipSnapshotFactory( + ISnapshot (IInternalEntry source) => + { + var structuralType = ((CompiledModelInMemoryTest.Index)(source.Entity)); + return ((ISnapshot)(new Snapshot(((ValueComparer)(((IProperty)id).GetKeyValueComparer())).Snapshot(source.GetCurrentValue(id))))); + }); + runtimeEntityType.SetCounts(new PropertyCounts( + propertyCount: 1, + navigationCount: 0, + complexPropertyCount: 0, + complexCollectionCount: 0, + originalValueCount: 1, + shadowCount: 0, + relationshipCount: 1, + storeGeneratedCount: 1)); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); +} diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/IndexUnsafeAccessors.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/IndexUnsafeAccessors.cs new file mode 100644 index 00000000000..0b6a51075fe --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/NativeAOT_default_key_comparer_is_emitted/IndexUnsafeAccessors.cs @@ -0,0 +1,15 @@ +// +using System; +using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.Scaffolding; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace; + +public static class IndexUnsafeAccessors +{ + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] + public static extern ref Guid Id(CompiledModelInMemoryTest.Index @this); +} diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs index 254cecb2d74..1547bdb056c 100644 --- a/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs @@ -60,6 +60,21 @@ public virtual Task Self_referential_property() } ); + [Fact] + public virtual Task NativeAOT_default_key_comparer_is_emitted() + => Test( + modelBuilder => modelBuilder.Entity(), + model => + { + var id = model.FindEntityType(typeof(Index))!.FindProperty(nameof(Index.Id))!; + var comparer = Assert.IsAssignableFrom>(id.GetKeyValueComparer()); + var value = Guid.NewGuid(); + + Assert.True(comparer.Equals(value, value)); + Assert.False(comparer.Equals(value, Guid.NewGuid())); + }, + options: new CompiledModelCodeGenerationOptions { UseNullableReferenceTypes = true, ForNativeAot = true }); + public class SelfReferentialEntity where T : struct {