From 270f20925d44be5c9e8109edc7bcd59cf65ad27f Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 12 Jul 2023 16:29:34 -0700 Subject: [PATCH] Add NativeAOT support for Change Tracking --- All.sln | 8 + eng/common/tools.ps1 | 4 +- eng/helix.proj | 3 +- .../Internal/InternalEntityEntry.cs | 59 +- .../ChangeTracking/Internal/SidecarValues.cs | 4 +- .../ChangeTracking/Internal/Snapshot.cs | 660 +++++++++++++++--- src/EFCore/ChangeTracking/ValueComparer.cs | 39 +- .../Metadata/Internal/IRuntimePropertyBase.cs | 8 +- src/EFCore/Metadata/Internal/PropertyBase.cs | 2 +- src/EFCore/Metadata/RuntimeEntityType.cs | 12 + src/EFCore/Metadata/RuntimeKey.cs | 38 +- src/EFCore/Metadata/RuntimePropertyBase.cs | 48 +- src/EFCore/Metadata/RuntimeTypeBase.cs | 61 ++ .../Query/Internal/EntityQueryProvider.cs | 21 +- src/dotnet-ef/Project.cs | 1 + .../CompiledModels/NativeAotContextModel.cs | 29 + .../NativeAotContextModelBuilder.cs | 24 + .../CompiledModels/UserEntityType.cs | 102 +++ .../EFCore.NativeAotTests.csproj | 20 + .../EFCore.NativeAotTests/NativeAotContext.cs | 24 + test/EFCore.NativeAotTests/Program.cs | 14 + test/EFCore.NativeAotTests/User.cs | 11 + 22 files changed, 1013 insertions(+), 179 deletions(-) create mode 100644 test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModel.cs create mode 100644 test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModelBuilder.cs create mode 100644 test/EFCore.NativeAotTests/CompiledModels/UserEntityType.cs create mode 100644 test/EFCore.NativeAotTests/EFCore.NativeAotTests.csproj create mode 100644 test/EFCore.NativeAotTests/NativeAotContext.cs create mode 100644 test/EFCore.NativeAotTests/Program.cs create mode 100644 test/EFCore.NativeAotTests/User.cs diff --git a/All.sln b/All.sln index 645fd0c59fd..b7fad66baf8 100644 --- a/All.sln +++ b/All.sln @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution azure-pipelines.yml = azure-pipelines.yml Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets + global.json = global.json tools\Resources.tt = tools\Resources.tt eng\Versions.props = eng\Versions.props EndProjectSection @@ -125,6 +126,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Trimming.Tests", "te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Templates", "src\EFCore.Templates\EFCore.Templates.csproj", "{1FE385D8-8F8B-4EC9-A1A9-AFCC38B8546C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.NativeAotTests", "test\EFCore.NativeAotTests\EFCore.NativeAotTests.csproj", "{2487950B-403A-482C-8ED3-CCF31E9E677F}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.SqlServer.HierarchyId", "src\EFCore.SqlServer.HierarchyId\EFCore.SqlServer.HierarchyId.csproj", "{8F722A02-71A4-4787-ACD8-FB7D5B7AE648}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.SqlServer.HierarchyId.Tests", "test\EFCore.SqlServer.HierarchyId.Tests\EFCore.SqlServer.HierarchyId.Tests.csproj", "{01F86E65-6448-424C-AAB5-9C6427EF6FD4}" @@ -339,6 +342,10 @@ Global {1FE385D8-8F8B-4EC9-A1A9-AFCC38B8546C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1FE385D8-8F8B-4EC9-A1A9-AFCC38B8546C}.Release|Any CPU.ActiveCfg = Release|Any CPU {1FE385D8-8F8B-4EC9-A1A9-AFCC38B8546C}.Release|Any CPU.Build.0 = Release|Any CPU + {2487950B-403A-482C-8ED3-CCF31E9E677F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2487950B-403A-482C-8ED3-CCF31E9E677F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2487950B-403A-482C-8ED3-CCF31E9E677F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2487950B-403A-482C-8ED3-CCF31E9E677F}.Release|Any CPU.Build.0 = Release|Any CPU {8F722A02-71A4-4787-ACD8-FB7D5B7AE648}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8F722A02-71A4-4787-ACD8-FB7D5B7AE648}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F722A02-71A4-4787-ACD8-FB7D5B7AE648}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -410,6 +417,7 @@ Global {F1B2E5A0-8C74-414A-B262-353FEE325E9F} = {258D5057-81B9-40EC-A872-D21E27452749} {933C8662-817C-4F45-B98B-6557E28F7BB1} = {258D5057-81B9-40EC-A872-D21E27452749} {1FE385D8-8F8B-4EC9-A1A9-AFCC38B8546C} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} + {2487950B-403A-482C-8ED3-CCF31E9E677F} = {258D5057-81B9-40EC-A872-D21E27452749} {8F722A02-71A4-4787-ACD8-FB7D5B7AE648} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} {01F86E65-6448-424C-AAB5-9C6427EF6FD4} = {258D5057-81B9-40EC-A872-D21E27452749} {3D935B7D-80BD-49AD-BDC9-E1B0C9D9494F} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index c9eced9f7df..6a2ada63064 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -760,9 +760,9 @@ function MSBuild() { (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')), (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')), (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.ArcadeLogging.dll')), - (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.Arcade.Sdk.dll')), (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.ArcadeLogging.dll')), - (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.Arcade.Sdk.dll')), (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.ArcadeLogging.dll')), (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.Arcade.Sdk.dll')) ) diff --git a/eng/helix.proj b/eng/helix.proj index 2e45ac424d3..4195e894e36 100644 --- a/eng/helix.proj +++ b/eng/helix.proj @@ -29,8 +29,9 @@ - + + diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 568df9bde78..fdfa9112ba3 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -5,7 +5,6 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -185,8 +184,9 @@ public async Task SetEntityStateAsync( CancellationToken cancellationToken = default) { var oldState = _stateData.EntityState; - var adding = false; - await SetupAsync().ConfigureAwait(false); + bool adding = PrepareForAdd(entityState); + entityState = await PropagateToUnknownKeyAsync( + oldState, entityState, adding, forceStateWhenUnknownKey, cancellationToken).ConfigureAwait(false); if ((adding || oldState is EntityState.Detached) && await StateManager.ValueGenerationManager @@ -194,17 +194,12 @@ public async Task SetEntityStateAsync( && fallbackState.HasValue) { entityState = fallbackState.Value; - await SetupAsync().ConfigureAwait(false); - } - - SetEntityState(oldState, entityState, acceptChanges, modifyProperties); - - async Task SetupAsync() - { adding = PrepareForAdd(entityState); entityState = await PropagateToUnknownKeyAsync( oldState, entityState, adding, forceStateWhenUnknownKey, cancellationToken).ConfigureAwait(false); } + + SetEntityState(oldState, entityState, acceptChanges, modifyProperties); } private EntityState PropagateToUnknownKey( @@ -829,8 +824,13 @@ private static readonly MethodInfo ReadOriginalValueMethod internal static MethodInfo MakeReadOriginalValueMethod(Type type) => ReadOriginalValueMethod.MakeGenericMethod(type); - [UsedImplicitly] - private T ReadOriginalValue(IProperty property, int originalValueIndex) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + public T ReadOriginalValue(IProperty property, int originalValueIndex) => _originalValues.GetValue(this, property, originalValueIndex); private static readonly MethodInfo ReadRelationshipSnapshotValueMethod @@ -842,8 +842,13 @@ private static readonly MethodInfo ReadRelationshipSnapshotValueMethod internal static MethodInfo MakeReadRelationshipSnapshotValueMethod(Type type) => ReadRelationshipSnapshotValueMethod.MakeGenericMethod(type); - [UsedImplicitly] - private T ReadRelationshipSnapshotValue(IPropertyBase propertyBase, int relationshipSnapshotIndex) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + public T ReadRelationshipSnapshotValue(IPropertyBase propertyBase, int relationshipSnapshotIndex) => _relationshipsSnapshot.GetValue(this, propertyBase, relationshipSnapshotIndex); [UnconditionalSuppressMessage( @@ -855,12 +860,17 @@ internal static MethodInfo MakeReadStoreGeneratedValueMethod(Type type) private static readonly MethodInfo ReadStoreGeneratedValueMethod = typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethod(nameof(ReadStoreGeneratedValue))!; - [UsedImplicitly] - private T ReadStoreGeneratedValue(int storeGeneratedIndex) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + public T ReadStoreGeneratedValue(int storeGeneratedIndex) => _storeGeneratedValues.GetValue(storeGeneratedIndex); private static readonly MethodInfo ReadTemporaryValueMethod - = typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethod(nameof(ReadTemporaryValue))!; + = typeof(InternalEntityEntry).GetMethod(nameof(ReadTemporaryValue))!; [UnconditionalSuppressMessage( "ReflectionAnalysis", "IL2060", @@ -868,8 +878,13 @@ private static readonly MethodInfo ReadTemporaryValueMethod internal static MethodInfo MakeReadTemporaryValueMethod(Type type) => ReadTemporaryValueMethod.MakeGenericMethod(type); - [UsedImplicitly] - private T ReadTemporaryValue(int storeGeneratedIndex) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + public T ReadTemporaryValue(int storeGeneratedIndex) => _temporaryValues.GetValue(storeGeneratedIndex); private static readonly MethodInfo GetCurrentValueMethod @@ -942,7 +957,7 @@ private void WritePropertyValue( var setter = forMaterialization ? concretePropertyBase.MaterializationSetter - : concretePropertyBase.Setter; + : concretePropertyBase.GetSetter(); setter.SetClrValue(Entity, value); } @@ -1124,7 +1139,7 @@ public void EnsureTemporaryValues() { if (_temporaryValues.IsEmpty) { - _temporaryValues = new SidecarValues(((IRuntimeEntityType)EntityType).TemporaryValuesFactory(this)); + _temporaryValues = new SidecarValues(EntityType.TemporaryValuesFactory(this)); } } @@ -1138,7 +1153,7 @@ public void EnsureStoreGeneratedValues() { if (_storeGeneratedValues.IsEmpty) { - _storeGeneratedValues = new SidecarValues(((IRuntimeEntityType)EntityType).StoreGeneratedValuesFactory()); + _storeGeneratedValues = new SidecarValues(EntityType.StoreGeneratedValuesFactory()); } } diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValues.cs b/src/EFCore/ChangeTracking/Internal/SidecarValues.cs index 88d531092ca..f4e2ec834af 100644 --- a/src/EFCore/ChangeTracking/Internal/SidecarValues.cs +++ b/src/EFCore/ChangeTracking/Internal/SidecarValues.cs @@ -9,9 +9,9 @@ private readonly struct SidecarValues { private readonly ISnapshot _values; - public SidecarValues(ISnapshot valuesFactory) + public SidecarValues(ISnapshot values) { - _values = valuesFactory; + _values = values; } public bool TryGetValue(int index, out object? value) diff --git a/src/EFCore/ChangeTracking/Internal/Snapshot.cs b/src/EFCore/ChangeTracking/Internal/Snapshot.cs index 1c447b29add..aac2c05085d 100644 --- a/src/EFCore/ChangeTracking/Internal/Snapshot.cs +++ b/src/EFCore/ChangeTracking/Internal/Snapshot.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using static Microsoft.EntityFrameworkCore.Metadata.Internal.EntityType; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -54,30 +55,6 @@ public object? this[int index] public T GetValue(int index) => throw new IndexOutOfRangeException(); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// - public static Delegate[] CreateReaders() - { - var genericArguments = typeof(TSnapshot).GenericTypeArguments; - var delegates = new Delegate[genericArguments.Length]; - - for (var i = 0; i < genericArguments.Length; ++i) - { - var snapshotParameter = Expression.Parameter(typeof(TSnapshot), "snapshot"); - - delegates[i] = Expression.Lambda( - typeof(Func<,>).MakeGenericType(typeof(TSnapshot), genericArguments[i]), - Expression.Field(snapshotParameter, "_value" + i), snapshotParameter) - .Compile(); - } - - return delegates; - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -132,10 +109,39 @@ public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22, + (Snapshot e) => e._value23, + (Snapshot e) => e._value24, + (Snapshot e) => e._value25, + (Snapshot e) => e._value26, + (Snapshot e) => e._value27, + (Snapshot e) => e._value28, + (Snapshot e) => e._value29 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -401,10 +407,38 @@ public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22, + (Snapshot e) => e._value23, + (Snapshot e) => e._value24, + (Snapshot e) => e._value25, + (Snapshot e) => e._value26, + (Snapshot e) => e._value27, + (Snapshot e) => e._value28 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -663,10 +697,38 @@ public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22, + (Snapshot e) => e._value23, + (Snapshot e) => e._value24, + (Snapshot e) => e._value25, + (Snapshot e) => e._value26, + (Snapshot e) => e._value27 + }; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -918,10 +980,36 @@ public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22, + (Snapshot e) => e._value23, + (Snapshot e) => e._value24, + (Snapshot e) => e._value25, + (Snapshot e) => e._value26 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1166,10 +1254,35 @@ public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22, + (Snapshot e) => e._value23, + (Snapshot e) => e._value24, + (Snapshot e) => e._value25 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1407,10 +1520,34 @@ public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22, + (Snapshot e) => e._value23, + (Snapshot e) => e._value24 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1641,10 +1778,33 @@ public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22, + (Snapshot e) => e._value23 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1867,10 +2027,32 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21, + (Snapshot e) => e._value22 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2086,10 +2268,31 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders - >(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20, + (Snapshot e) => e._value21 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2298,9 +2501,30 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot - .CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19, + (Snapshot e) => e._value20 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2502,8 +2726,29 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18, + (Snapshot e) => e._value19 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2698,8 +2943,28 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17, + (Snapshot e) => e._value18 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2887,8 +3152,27 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16, + (Snapshot e) => e._value17 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3069,8 +3353,26 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15, + (Snapshot e) => e._value16 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3243,8 +3545,25 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14, + (Snapshot e) => e._value15 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3410,8 +3729,24 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13, + (Snapshot e) => e._value14 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3570,8 +3905,23 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12, + (Snapshot e) => e._value13 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3723,8 +4073,22 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11, + (Snapshot e) => e._value12 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3869,8 +4233,21 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10, + (Snapshot e) => e._value11 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4008,8 +4385,20 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9, + (Snapshot e) => e._value10 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4140,8 +4529,19 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8, + (Snapshot e) => e._value9 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4265,8 +4665,18 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7, + (Snapshot e) => e._value8 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4383,8 +4793,17 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6, + (Snapshot e) => e._value7 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4494,8 +4913,16 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5, + (Snapshot e) => e._value6 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4598,8 +5025,15 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4, + (Snapshot e) => e._value5 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4695,8 +5129,14 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3, + (Snapshot e) => e._value4 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4785,8 +5225,13 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2, + (Snapshot e) => e._value3 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4868,8 +5313,12 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1, + (Snapshot e) => e._value2 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4944,8 +5393,11 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0, + (Snapshot e) => e._value1 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5013,8 +5465,10 @@ public object? this[int index] public sealed class Snapshot : ISnapshot { - private static readonly Delegate[] ValueReaders - = Snapshot.CreateReaders>(); + private static readonly Delegate[] ValueReaders = new Delegate[] + { + (Snapshot e) => e._value0 + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/ValueComparer.cs b/src/EFCore/ChangeTracking/ValueComparer.cs index 39c7a668b6e..df7d6c46914 100644 --- a/src/EFCore/ChangeTracking/ValueComparer.cs +++ b/src/EFCore/ChangeTracking/ValueComparer.cs @@ -189,8 +189,28 @@ public static ValueComparer CreateDefault( | DynamicallyAccessedMemberTypes.PublicProperties)] Type type, bool favorStructuralComparisons) + => (ValueComparer)CreateDefaultMethod.MakeGenericMethod(type).Invoke(null, new object[] { favorStructuralComparisons })!; + + private static readonly MethodInfo CreateDefaultMethod = typeof(ValueComparer).GetMethod( + nameof(CreateDefault), + genericParameterCount: 1, + BindingFlags.Static | BindingFlags.Public, + null, + new[] {typeof(bool)}, + null )!; + + /// + /// Creates a default for the given type. + /// + /// + /// If , then EF will use if the type + /// implements it. This is usually used when byte arrays act as keys. + /// + /// The type. + /// The . + public static ValueComparer CreateDefault(bool favorStructuralComparisons) { - var nonNullableType = type.UnwrapNullableType(); + var nonNullableType = typeof(T).UnwrapNullableType(); // The equality operator returns false for NaNs, but the Equals methods returns true if (nonNullableType == typeof(double)) @@ -208,7 +228,7 @@ public static ValueComparer CreateDefault( return new DefaultDateTimeOffsetValueComparer(favorStructuralComparisons); } - var comparerType = nonNullableType.IsInteger() + return nonNullableType.IsInteger() || nonNullableType == typeof(decimal) || nonNullableType == typeof(bool) || nonNullableType == typeof(string) @@ -217,19 +237,8 @@ public static ValueComparer CreateDefault( || nonNullableType == typeof(Guid) || nonNullableType == typeof(TimeSpan) || nonNullableType == typeof(TimeOnly) - ? typeof(DefaultValueComparer<>) - : typeof(ValueComparer<>); - - return CreateInstance(); - - [UnconditionalSuppressMessage( - "ReflectionAnalysis", "IL2055", Justification = - "We only create ValueComparer or DefaultValueComparer whose generic type parameter requires Methods/Properties, " - + "and our type argument is properly annotated for those.")] - ValueComparer CreateInstance() - => (ValueComparer)Activator.CreateInstance( - comparerType.MakeGenericType(type), - new object[] { favorStructuralComparisons })!; + ? new DefaultValueComparer(favorStructuralComparisons) + : new ValueComparer(favorStructuralComparisons); } // PublicMethods is required to preserve e.g. GetHashCode diff --git a/src/EFCore/Metadata/Internal/IRuntimePropertyBase.cs b/src/EFCore/Metadata/Internal/IRuntimePropertyBase.cs index 7f391af40e1..1049126ca13 100644 --- a/src/EFCore/Metadata/Internal/IRuntimePropertyBase.cs +++ b/src/EFCore/Metadata/Internal/IRuntimePropertyBase.cs @@ -17,7 +17,7 @@ public interface IRuntimePropertyBase : IPropertyBase /// 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. /// - IClrPropertySetter Setter { get; } + IClrPropertySetter MaterializationSetter { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -25,7 +25,7 @@ public interface IRuntimePropertyBase : IPropertyBase /// 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. /// - IClrPropertySetter MaterializationSetter { get; } + PropertyAccessors Accessors { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -33,7 +33,7 @@ public interface IRuntimePropertyBase : IPropertyBase /// 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. /// - PropertyAccessors Accessors { get; } + PropertyIndexes PropertyIndexes { get; set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -41,7 +41,7 @@ public interface IRuntimePropertyBase : IPropertyBase /// 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. /// - PropertyIndexes PropertyIndexes { get; set; } + IClrPropertySetter GetSetter(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/PropertyBase.cs b/src/EFCore/Metadata/Internal/PropertyBase.cs index 96b056bde49..ad165e6cced 100644 --- a/src/EFCore/Metadata/Internal/PropertyBase.cs +++ b/src/EFCore/Metadata/Internal/PropertyBase.cs @@ -364,7 +364,7 @@ public virtual IClrPropertyGetter Getter /// 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. /// - public virtual IClrPropertySetter Setter + public virtual IClrPropertySetter GetSetter() => NonCapturingLazyInitializer.EnsureInitialized( ref _setter, this, static property => { diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index 326ca9a959d..d7b656d77d6 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -736,6 +736,18 @@ private IEnumerable GetTriggers() ? BaseType.GetTriggers().Concat(GetDeclaredTriggers()) : GetDeclaredTriggers(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetRelationshipSnapshotFactory(Func factory) + { + _relationshipSnapshotFactory = factory; + } + /// /// Gets or sets the for the preferred constructor. /// diff --git a/src/EFCore/Metadata/RuntimeKey.cs b/src/EFCore/Metadata/RuntimeKey.cs index 50ff0f8a1ef..26dd7f56161 100644 --- a/src/EFCore/Metadata/RuntimeKey.cs +++ b/src/EFCore/Metadata/RuntimeKey.cs @@ -56,6 +56,30 @@ public virtual RuntimeEntityType DeclaringEntityType [EntityFrameworkInternal] public virtual ISet? ReferencingForeignKeys { get; set; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetPrincipalKeyValueFactory(IPrincipalKeyValueFactory factory) + { + _principalKeyValueFactory = factory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetIdentityMapFactory(Func factory) + { + _identityMapFactory = factory; + } + /// /// Returns a string that represents the current object. /// @@ -108,20 +132,6 @@ IEntityType IKey.DeclaringEntityType IEnumerable IReadOnlyKey.GetReferencingForeignKeys() => ReferencingForeignKeys ?? Enumerable.Empty(); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// - public virtual Func IdentityMapFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _identityMapFactory, this, static key => - { - key.EnsureReadOnly(); - return new IdentityMapFactoryFactory().Create(key); - }); - /// IPrincipalKeyValueFactory IKey.GetPrincipalKeyValueFactory() => (IPrincipalKeyValueFactory)NonCapturingLazyInitializer.EnsureInitialized( diff --git a/src/EFCore/Metadata/RuntimePropertyBase.cs b/src/EFCore/Metadata/RuntimePropertyBase.cs index 511aba6daf3..37b972b70c1 100644 --- a/src/EFCore/Metadata/RuntimePropertyBase.cs +++ b/src/EFCore/Metadata/RuntimePropertyBase.cs @@ -92,11 +92,6 @@ IReadOnlyTypeBase IReadOnlyPropertyBase.DeclaringType get => DeclaringType; } - /// - IClrPropertySetter IRuntimePropertyBase.Setter - => NonCapturingLazyInitializer.EnsureInitialized( - ref _setter, this, static property => new ClrPropertySetterFactory().Create(property)); - /// IClrPropertySetter IRuntimePropertyBase.MaterializationSetter => NonCapturingLazyInitializer.EnsureInitialized( @@ -129,6 +124,49 @@ Type IReadOnlyPropertyBase.ClrType get => ClrType; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetAccessors(PropertyAccessors accessors) + { + _accessors = accessors; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetSetter(Action setter) + where TEntity : class + { + _setter = new ClrPropertySetter(setter); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetGetter(Func getter, Func hasDefaultValue) + where TEntity : class + { + _getter = new ClrPropertyGetter(getter, hasDefaultValue); + } + + /// + IClrPropertySetter IRuntimePropertyBase.GetSetter() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _setter, this, static property => new ClrPropertySetterFactory().Create(property)); + /// [DebuggerStepThrough] IClrPropertyGetter IPropertyBase.GetGetter() diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index 7802b06ebe4..63c5c60230a 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -483,6 +483,67 @@ private IEnumerable FindDerivedComplexProperties(string /// public abstract IEnumerable FindMembersInHierarchy(string name); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetOriginalValuesFactory(Func factory) + { + _originalValuesFactory = factory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetStoreGeneratedValuesFactory(Func factory) + { + _storeGeneratedValuesFactory = factory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetTemporaryValuesFactory(Func factory) + { + _temporaryValuesFactory = factory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [EntityFrameworkInternal] + public virtual void SetShadowValuesFactory(Func factory) + { + _shadowValuesFactory = factory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + + [EntityFrameworkInternal] + public virtual void SetEmptyShadowValuesFactory(Func factory) + { + _emptyShadowValuesFactory = factory; + } + /// /// Gets or sets the for the preferred constructor. /// diff --git a/src/EFCore/Query/Internal/EntityQueryProvider.cs b/src/EFCore/Query/Internal/EntityQueryProvider.cs index 8b304646ffe..6c683a0986b 100644 --- a/src/EFCore/Query/Internal/EntityQueryProvider.cs +++ b/src/EFCore/Query/Internal/EntityQueryProvider.cs @@ -11,12 +11,8 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal; /// public class EntityQueryProvider : IAsyncQueryProvider { - private static readonly MethodInfo GenericCreateQueryMethod - = typeof(EntityQueryProvider).GetRuntimeMethods() - .Single(m => m is { Name: "CreateQuery", IsGenericMethod: true }); - - private readonly MethodInfo _genericExecuteMethod; - + private static MethodInfo? _genericCreateQueryMethod; + private MethodInfo? _genericExecuteMethod; private readonly IQueryCompiler _queryCompiler; /// @@ -28,11 +24,16 @@ private static readonly MethodInfo GenericCreateQueryMethod public EntityQueryProvider(IQueryCompiler queryCompiler) { _queryCompiler = queryCompiler; - _genericExecuteMethod = queryCompiler.GetType() - .GetRuntimeMethods() - .Single(m => m is { Name: "Execute", IsGenericMethod: true }); } + private static MethodInfo GenericCreateQueryMethod + => _genericCreateQueryMethod ??= typeof(EntityQueryProvider) + .GetMethod("CreateQuery", 1, BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(Expression) }, null)!; + + private MethodInfo GenericExecuteMethod + => _genericExecuteMethod ??= _queryCompiler.GetType() + .GetMethod("Execute", 1, BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(Expression) }, null)!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -69,7 +70,7 @@ public virtual TResult Execute(Expression expression) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual object Execute(Expression expression) - => _genericExecuteMethod.MakeGenericMethod(expression.Type) + => GenericExecuteMethod.MakeGenericMethod(expression.Type) .Invoke(_queryCompiler, new object[] { expression })!; /// diff --git a/src/dotnet-ef/Project.cs b/src/dotnet-ef/Project.cs index dfe78a6f61b..3a0db18d2f2 100644 --- a/src/dotnet-ef/Project.cs +++ b/src/dotnet-ef/Project.cs @@ -164,6 +164,7 @@ public void Build() args.Add("/verbosity:quiet"); args.Add("/nologo"); + args.Add("/p:PublishAot=false"); // Avoid NativeAOT warnings var exitCode = Exe.Run("dotnet", args, interceptOutput: true); if (exitCode != 0) diff --git a/test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModel.cs b/test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModel.cs new file mode 100644 index 00000000000..b4b6769fede --- /dev/null +++ b/test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModel.cs @@ -0,0 +1,29 @@ +// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.NativeAotTests; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace Microsoft.EntityFrameworkCore.NativeAotTests.CompiledModels +{ + [DbContext(typeof(NativeAotContext))] + public partial class NativeAotContextModel : RuntimeModel + { + static NativeAotContextModel() + { + var model = new NativeAotContextModel(); + model.Initialize(); + model.Customize(); + _instance = model; + } + + private static NativeAotContextModel _instance; + public static IModel Instance => _instance; + + partial void Initialize(); + + partial void Customize(); + } +} diff --git a/test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModelBuilder.cs b/test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModelBuilder.cs new file mode 100644 index 00000000000..fecbfbc380b --- /dev/null +++ b/test/EFCore.NativeAotTests/CompiledModels/NativeAotContextModelBuilder.cs @@ -0,0 +1,24 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace Microsoft.EntityFrameworkCore.NativeAotTests.CompiledModels +{ + public partial class NativeAotContextModel + { + partial void Initialize() + { + var user = UserEntityType.Create(this); + + UserEntityType.CreateAnnotations(user); + + AddAnnotation("ProductVersion", "8.0.0-dev"); + AddAnnotation("Relational:MaxIdentifierLength", 128); + AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + } + } +} diff --git a/test/EFCore.NativeAotTests/CompiledModels/UserEntityType.cs b/test/EFCore.NativeAotTests/CompiledModels/UserEntityType.cs new file mode 100644 index 00000000000..1e2a5ad0b14 --- /dev/null +++ b/test/EFCore.NativeAotTests/CompiledModels/UserEntityType.cs @@ -0,0 +1,102 @@ +// +using System.Reflection; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace Microsoft.EntityFrameworkCore.NativeAotTests.CompiledModels +{ + internal partial class UserEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.NativeAotTests.User", + typeof(User), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + "Id", + typeof(int), + propertyInfo: typeof(User).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(User).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw, + valueComparer: ValueComparer.CreateDefault(favorStructuralComparisons: false), + providerValueComparer: ValueComparer.CreateDefault(favorStructuralComparisons: true)); + id.SetSetter((User e, int v) => e.Id = v); + id.SetGetter((User e) => e.Id, e => e.Id == default(int)); + id.SetAccessors(new PropertyAccessors( + (InternalEntityEntry e) => + ((User)e.Entity).Id == 0 + ? e.FlaggedAsStoreGenerated(0) + ? e.ReadStoreGeneratedValue(0) + : e.FlaggedAsTemporary(0) + ? e.ReadTemporaryValue(0) + : ((User)e.Entity).Id + : ((User)e.Entity).Id, + (InternalEntityEntry e) => + ((User)e.Entity).Id == 0 + ? e.ReadTemporaryValue(0) == 0 + ? ((User)e.Entity).Id + : e.ReadTemporaryValue(0) + : ((User)e.Entity).Id, + (InternalEntityEntry e) => e.ReadOriginalValue(id, 0), + (InternalEntityEntry e) => e.ReadRelationshipSnapshotValue(id, 0), + valueBuffer => valueBuffer[0]!)); + id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + var name = runtimeEntityType.AddProperty( + "Name", + typeof(string), + propertyInfo: typeof(User).GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(User).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueComparer: ValueComparer.CreateDefault(favorStructuralComparisons: false), + providerValueComparer: ValueComparer.CreateDefault(favorStructuralComparisons: true)); + name.SetSetter((User e, string v) => e.Name = v); + name.SetGetter((User e) => e.Name, e => e.Name == default(string)); + name.SetAccessors(new PropertyAccessors( + (InternalEntityEntry e) => ((User)e.Entity).Name, + (InternalEntityEntry e) => ((User)e.Entity).Name, + (InternalEntityEntry e) => e.ReadOriginalValue(name, 1), + (InternalEntityEntry e) => e.ReadRelationshipSnapshotValue(name, 1), + valueBuffer => valueBuffer[1]!)); + name.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + // TODO: Also set DependentKeyValueFactory on the referencing FKs + key.SetPrincipalKeyValueFactory(new SimplePrincipalKeyValueFactory(key)); + key.SetIdentityMapFactory(sensitiveLoggingEnabled => + new IdentityMap(key, ((IRuntimeKey)key).GetPrincipalKeyValueFactory(), sensitiveLoggingEnabled)); + runtimeEntityType.SetPrimaryKey(key); + + runtimeEntityType.SetEmptyShadowValuesFactory(() => Snapshot.Empty); + runtimeEntityType.SetShadowValuesFactory(_ => Snapshot.Empty); + runtimeEntityType.SetOriginalValuesFactory(e => new Snapshot(e.GetCurrentValue(id), e.GetCurrentValue(name))); + runtimeEntityType.SetStoreGeneratedValuesFactory(() => new Snapshot(default(int))); + runtimeEntityType.SetRelationshipSnapshotFactory(e => new Snapshot(e.GetCurrentValue(id), e.GetCurrentValue(name))); + runtimeEntityType.SetTemporaryValuesFactory(e => new Snapshot(default(int))); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "Users"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} diff --git a/test/EFCore.NativeAotTests/EFCore.NativeAotTests.csproj b/test/EFCore.NativeAotTests/EFCore.NativeAotTests.csproj new file mode 100644 index 00000000000..7dac82da3e4 --- /dev/null +++ b/test/EFCore.NativeAotTests/EFCore.NativeAotTests.csproj @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + true + $(WarningsNotAsErrors);CS1591;SA1636;SA0001;SA1402;SA1027;SA1309;SA1201;SA1200;SA1512;SA1101 + + false + false + + + + + + + + diff --git a/test/EFCore.NativeAotTests/NativeAotContext.cs b/test/EFCore.NativeAotTests/NativeAotContext.cs new file mode 100644 index 00000000000..d3f6746d458 --- /dev/null +++ b/test/EFCore.NativeAotTests/NativeAotContext.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.NativeAotTests.CompiledModels; + +namespace Microsoft.EntityFrameworkCore.NativeAotTests; + +public class NativeAotContext : DbContext +{ +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + public NativeAotContext() : base() { } +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + + public DbSet Users { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .UseModel(NativeAotContextModel.Instance) + .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TestDb;Trusted_Connection=True;"); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + } +} diff --git a/test/EFCore.NativeAotTests/Program.cs b/test/EFCore.NativeAotTests/Program.cs new file mode 100644 index 00000000000..0c56ea36789 --- /dev/null +++ b/test/EFCore.NativeAotTests/Program.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.NativeAotTests; + +using var context = new NativeAotContext(); + +context.Database.EnsureDeleted(); +context.Database.EnsureCreated(); + +context.Add(new User { Name = "Andriy" }); +context.SaveChanges(); + +Console.WriteLine(context.Users.First().Id); diff --git a/test/EFCore.NativeAotTests/User.cs b/test/EFCore.NativeAotTests/User.cs new file mode 100644 index 00000000000..60f645676bf --- /dev/null +++ b/test/EFCore.NativeAotTests/User.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.NativeAotTests; + +public class User +{ + public int Id { get; set; } + + public string Name { get; set; } = null!; +}