From 56df35f2d182dc5d2073f04dd2b22f3805ca61a4 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 18 Oct 2023 22:08:19 -0700 Subject: [PATCH] Change IStateManager.StartTrackingFromQuery to use ISnapshot instead of ValueBuffer Fixes #31117 Part of #26544 --- ...sitor.ShaperProcessingExpressionVisitor.cs | 35 +++--- .../IDependentKeyValueFactory`.cs | 1 + .../IPrincipalKeyValueFactory`.cs | 1 + .../Internal/CurrentValueComparerFactory.cs | 79 +++++++------- .../EmptyShadowValuesFactoryFactory.cs | 16 ++- .../EntryCurrentProviderValueComparer.cs | 2 +- .../Internal/EntryCurrentValueComparer.cs | 6 +- .../ChangeTracking/Internal/IIdentityMap.cs | 16 --- .../ChangeTracking/Internal/ISnapshot.cs | 9 ++ .../ChangeTracking/Internal/IStateManager.cs | 2 +- .../ChangeTracking/Internal/IdentityMap.cs | 22 ---- .../Internal/InternalEntityEntry.cs | 40 ++++++- .../Internal/OriginalValuesFactoryFactory.cs | 12 +++ .../RelationshipSnapshotFactoryFactory.cs | 12 +++ .../Internal/ShadowValuesFactoryFactory.cs | 53 +++++++-- .../Internal/SidecarValuesFactoryFactory.cs | 18 ++++ ...leFullyNullableDependentKeyValueFactory.cs | 1 + ...mpleNonNullableDependentKeyValueFactory.cs | 1 + .../SimpleNullableDependentKeyValueFactory.cs | 1 + ...llablePrincipalDependentKeyValueFactory.cs | 1 + .../SimplePrincipalKeyValueFactory.cs | 1 + .../ChangeTracking/Internal/Snapshot.cs | 23 ++++ .../Internal/SnapshotFactoryFactory.cs | 79 +++++++------- .../Internal/SnapshotFactoryFactory`.cs | 17 +-- .../ChangeTracking/Internal/StateManager.cs | 30 +----- .../StoreGeneratedValuesFactoryFactory.cs | 16 ++- ...cturalEntryCurrentProviderValueComparer.cs | 2 +- .../StructuralEntryCurrentValueComparer.cs | 2 +- .../Internal/TemporaryValuesFactoryFactory.cs | 14 ++- src/EFCore/Metadata/IProperty.cs | 6 ++ src/EFCore/Metadata/IPropertyBase.cs | 6 -- src/EFCore/Metadata/Internal/EntityType.cs | 17 +-- .../Metadata/Internal/IRuntimeEntityType.cs | 2 +- src/EFCore/Metadata/Internal/Property.cs | 25 +++++ .../Metadata/Internal/PropertyAccessors.cs | 3 + src/EFCore/Metadata/Internal/PropertyBase.cs | 30 +----- src/EFCore/Metadata/RuntimeEntityType.cs | 102 ++++++------------ src/EFCore/Metadata/RuntimeProperty.cs | 14 ++- src/EFCore/Metadata/RuntimePropertyBase.cs | 9 -- src/EFCore/Query/QueryContext.cs | 4 +- .../ShapedQueryCompilingExpressionVisitor.cs | 35 +++--- .../Query/StructuralTypeShaperExpression.cs | 4 +- .../Internal/StringNumberConverter.cs | 2 +- .../ComplexTypesTrackingTestBase.cs | 5 +- .../ChangeTracking/Internal/FixupTest.cs | 26 ++--- .../Internal/StateManagerTest.cs | 6 +- .../TestUtilities/FakeStateManager.cs | 2 +- 47 files changed, 452 insertions(+), 358 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 6ae1fcfc83c..0ced6ea5a96 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -1204,10 +1204,13 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp if (property!.IsPrimaryKey()) { - return MakeIndex( + var valueExpression = MakeIndex( keyPropertyValuesParameter, ObjectArrayIndexerPropertyInfo, new[] { Constant(index) }); + return methodCallExpression.Type != valueExpression.Type + ? Convert(valueExpression, methodCallExpression.Type) + : valueExpression; } var jsonReaderManagerParameter = _jsonReaderDataToJsonReaderManagerParameterMapping[jsonReaderDataParameter]; @@ -1266,6 +1269,7 @@ private Expression CreateJsonShapers( INavigation? navigation) { var jsonReaderDataShaperLambdaParameter = Parameter(typeof(JsonReaderData)); + // TODO: Use ISnapshot instead #26544 var keyValuesShaperLambdaParameter = Parameter(typeof(object[])); var shaperBlockVariables = new List(); var shaperBlockExpressions = new List(); @@ -1554,7 +1558,7 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression) new ValueBufferTryReadValueMethodsFinder(_entityType).FindValueBufferTryReadValueMethods(body); BlockExpression jsonEntityTypeInitializerBlock; - //sometimes we have shadow value buffer and sometimes not, but type initializer always comes last + //sometimes we have shadow snapshot and sometimes not, but type initializer always comes last switch (body.Expressions[^1]) { case UnaryExpression { Operand: BlockExpression innerBlock } jsonEntityTypeInitializerUnary @@ -1665,7 +1669,7 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression) // we can't use simple ExpressionReplacingVisitor, because there could be multiple instances of MethodCallExpression for given property // using dedicated mini-visitor that looks for MCEs with a given shape and compare the IProperty inside // order is: - // - shadow value buffer (if there was one) + // - shadow snapshot (if there was one) // - entity construction / property assignments // - navigation fixups // - entity instance variable that is returned as end result @@ -1675,14 +1679,17 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression) if (body.Expressions[0] is BinaryExpression { NodeType: ExpressionType.Assign, - Right: NewExpression + Right: UnaryExpression { - Arguments: [NewArrayExpression] + NodeType: ExpressionType.Convert, + Operand: NewExpression } - } shadowValueBufferAssignment - && shadowValueBufferAssignment.Type == typeof(ValueBuffer)) + } shadowSnapshotAssignment +#pragma warning disable EF1001 // Internal EF Core API usage. + && shadowSnapshotAssignment.Type == typeof(ISnapshot)) +#pragma warning restore EF1001 // Internal EF Core API usage. { - finalBlockExpressions.Add(propertyAssignmentReplacer.Visit(shadowValueBufferAssignment)); + finalBlockExpressions.Add(propertyAssignmentReplacer.Visit(shadowSnapshotAssignment)); } foreach (var jsonEntityTypeInitializerBlockExpression in jsonEntityTypeInitializerBlock.Expressions.ToArray()[..^1]) @@ -1881,7 +1888,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional { Assign(entityAlreadyTrackedVariable, Constant(false)), - // shadowValueBuffer = ValueBuffer; + // shadowSnapshot = Snapshot.Empty; ifFalseBlock.Expressions[0], // entityType = EntityType; @@ -1904,12 +1911,12 @@ protected override Expression VisitConditional(ConditionalExpression conditional var newInstanceAssignmentVariables = instanceAssignmentBody.Variables.ToList(); var newInstanceAssignmentExpressions = new List(); - // we only need to generate shadowValueBuffer if the entity isn't already tracked - // shadow value buffer can be generated early in the block (default) + // we only need to generate shadowSnapshot if the entity isn't already tracked + // shadow snapshot can be generated early in the block (default) // or after we read all the values from JSON (case when the entity has some shadow properties) - // so we loop through the existing expressions and add the condition to value buffer assignment when we find it + // so we loop through the existing expressions and add the condition to snapshot assignment when we find it // expressions processed here: - // shadowValueBuffer = new ValueBuffer(...) + // shadowSnapshot = new Snapshot(...) // jsonManagerPrm = new Utf8JsonReaderManager(jsonReaderDataPrm); // tokenType = jsonManagerPrm.TokenType; // property_reading_loop(...) @@ -1917,7 +1924,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional for (var i = 0; i < 5; i++) { newInstanceAssignmentExpressions.Add( - instanceAssignmentBody.Expressions[i].Type == typeof(ValueBuffer) + instanceAssignmentBody.Expressions[i].Type == typeof(ISnapshot) ? IfThen( Not(entityAlreadyTrackedVariable), instanceAssignmentBody.Expressions[i]) diff --git a/src/EFCore/ChangeTracking/IDependentKeyValueFactory`.cs b/src/EFCore/ChangeTracking/IDependentKeyValueFactory`.cs index 1dc4295a225..3022ba1739d 100644 --- a/src/EFCore/ChangeTracking/IDependentKeyValueFactory`.cs +++ b/src/EFCore/ChangeTracking/IDependentKeyValueFactory`.cs @@ -29,6 +29,7 @@ public interface IDependentKeyValueFactory : IDependentKeyValueFactory /// The key instance. /// if the key instance was created; otherwise. [ContractAnnotation("=>true, key:notnull; =>false, key:null")] + [Obsolete] bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen(true)] out TKey? key); /// diff --git a/src/EFCore/ChangeTracking/IPrincipalKeyValueFactory`.cs b/src/EFCore/ChangeTracking/IPrincipalKeyValueFactory`.cs index 0d9e90429a1..73093c43137 100644 --- a/src/EFCore/ChangeTracking/IPrincipalKeyValueFactory`.cs +++ b/src/EFCore/ChangeTracking/IPrincipalKeyValueFactory`.cs @@ -31,6 +31,7 @@ public interface IPrincipalKeyValueFactory : IPrincipalKeyValueFactory /// /// The buffer containing key values. /// The key object, or null if any of the key values were null. + [Obsolete] object? CreateFromBuffer(ValueBuffer valueBuffer); /// diff --git a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs index cf00bc77dec..ab29fa3d622 100644 --- a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs @@ -19,73 +19,70 @@ public class CurrentValueComparerFactory /// 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 IComparer Create(IPropertyBase propertyBase) + public virtual IComparer Create(IProperty property) { - var modelType = propertyBase.ClrType; + var modelType = property.ClrType; var nonNullableModelType = modelType.UnwrapNullableType(); if (IsGenericComparable(modelType, nonNullableModelType)) { return (IComparer)Activator.CreateInstance( typeof(EntryCurrentValueComparer<>).MakeGenericType(modelType), - propertyBase)!; + property)!; } if (typeof(IStructuralComparable).IsAssignableFrom(nonNullableModelType)) { - return new StructuralEntryCurrentValueComparer(propertyBase); + return new StructuralEntryCurrentValueComparer(property); } if (typeof(IComparable).IsAssignableFrom(nonNullableModelType)) { - return new EntryCurrentValueComparer(propertyBase); + return new EntryCurrentValueComparer(property); } - if (propertyBase is IProperty property) + var converter = property.GetTypeMapping().Converter; + if (converter != null) { - var converter = property.GetTypeMapping().Converter; - if (converter != null) + var providerType = converter.ProviderClrType; + var nonNullableProviderType = providerType.UnwrapNullableType(); + if (IsGenericComparable(providerType, nonNullableProviderType)) { - var providerType = converter.ProviderClrType; - var nonNullableProviderType = providerType.UnwrapNullableType(); - if (IsGenericComparable(providerType, nonNullableProviderType)) - { - var elementType = property.GetElementType(); - var modelBaseType = elementType != null - ? typeof(IEnumerable<>).MakeGenericType(elementType.ClrType) - : modelType; - var comparerType = modelType.IsClass - ? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType) - : modelType == converter.ModelClrType - ? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType) - : typeof(NullableStructCurrentProviderValueComparer<,>).MakeGenericType( - nonNullableModelType, providerType); + var elementType = property.GetElementType(); + var modelBaseType = elementType != null + ? typeof(IEnumerable<>).MakeGenericType(elementType.ClrType) + : modelType; + var comparerType = modelType.IsClass + ? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType) + : modelType == converter.ModelClrType + ? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType) + : typeof(NullableStructCurrentProviderValueComparer<,>).MakeGenericType( + nonNullableModelType, providerType); - return (IComparer)Activator.CreateInstance(comparerType, propertyBase, converter)!; - } - - if (typeof(IStructuralComparable).IsAssignableFrom(nonNullableProviderType)) - { - return new StructuralEntryCurrentProviderValueComparer(propertyBase, converter); - } + return (IComparer)Activator.CreateInstance(comparerType, property, converter)!; + } - if (typeof(IComparable).IsAssignableFrom(nonNullableProviderType)) - { - return new EntryCurrentProviderValueComparer(propertyBase, converter); - } + if (typeof(IStructuralComparable).IsAssignableFrom(nonNullableProviderType)) + { + return new StructuralEntryCurrentProviderValueComparer(property, converter); + } - throw new InvalidOperationException( - CoreStrings.NonComparableKeyTypes( - propertyBase.DeclaringType.DisplayName(), - propertyBase.Name, - modelType.ShortDisplayName(), - providerType.ShortDisplayName())); + if (typeof(IComparable).IsAssignableFrom(nonNullableProviderType)) + { + return new EntryCurrentProviderValueComparer(property, converter); } + + throw new InvalidOperationException( + CoreStrings.NonComparableKeyTypes( + property.DeclaringType.DisplayName(), + property.Name, + modelType.ShortDisplayName(), + providerType.ShortDisplayName())); } throw new InvalidOperationException( CoreStrings.NonComparableKeyType( - propertyBase.DeclaringType.DisplayName(), - propertyBase.Name, + property.DeclaringType.DisplayName(), + property.Name, modelType.ShortDisplayName())); static bool IsGenericComparable(Type type, Type nonNullableType) diff --git a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs index b30f0f8ba22..1771645c961 100644 --- a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs @@ -13,6 +13,18 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public class EmptyShadowValuesFactoryFactory : SnapshotFactoryFactory { + private EmptyShadowValuesFactoryFactory() + { + } + + /// + /// 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 readonly EmptyShadowValuesFactoryFactory Instance = new(); + /// /// 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 @@ -55,7 +67,7 @@ protected override bool UseEntityVariable /// 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 override Expression CreateReadShadowValueExpression(ParameterExpression? parameter, IPropertyBase property) + protected override Expression CreateReadShadowValueExpression(Expression? parameter, IPropertyBase property) => Expression.Default(property.ClrType); /// @@ -64,6 +76,6 @@ protected override Expression CreateReadShadowValueExpression(ParameterExpressio /// 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 override Expression CreateReadValueExpression(ParameterExpression? parameter, IPropertyBase property) + protected override Expression CreateReadValueExpression(Expression? parameter, IPropertyBase property) => Expression.Default(property.ClrType); } diff --git a/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs b/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs index a493a04edc6..2d095cb7cae 100644 --- a/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs +++ b/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs @@ -20,7 +20,7 @@ public class EntryCurrentProviderValueComparer : EntryCurrentValueComparer /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public EntryCurrentProviderValueComparer( - IPropertyBase property, + IProperty property, ValueConverter converter) : base(property) { diff --git a/src/EFCore/ChangeTracking/Internal/EntryCurrentValueComparer.cs b/src/EFCore/ChangeTracking/Internal/EntryCurrentValueComparer.cs index fc3d909480e..97ccd1d78c2 100644 --- a/src/EFCore/ChangeTracking/Internal/EntryCurrentValueComparer.cs +++ b/src/EFCore/ChangeTracking/Internal/EntryCurrentValueComparer.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public class EntryCurrentValueComparer : IComparer, IEqualityComparer { - private readonly IPropertyBase _property; + private readonly IProperty _property; private readonly IComparer _underlyingComparer; /// @@ -22,7 +22,7 @@ public class EntryCurrentValueComparer : IComparer, IEqualityCompa /// 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 EntryCurrentValueComparer(IPropertyBase property) + public EntryCurrentValueComparer(IProperty property) : this(property, Comparer.Default) { } @@ -33,7 +33,7 @@ public EntryCurrentValueComparer(IPropertyBase property) /// 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 EntryCurrentValueComparer(IPropertyBase property, IComparer underlyingComparer) + public EntryCurrentValueComparer(IProperty property, IComparer underlyingComparer) { _property = property; _underlyingComparer = underlyingComparer; diff --git a/src/EFCore/ChangeTracking/Internal/IIdentityMap.cs b/src/EFCore/ChangeTracking/Internal/IIdentityMap.cs index 16a6d372c2e..8b4c536350e 100644 --- a/src/EFCore/ChangeTracking/Internal/IIdentityMap.cs +++ b/src/EFCore/ChangeTracking/Internal/IIdentityMap.cs @@ -27,22 +27,6 @@ public interface IIdentityMap /// IEnumerable All(); - /// - /// 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. - /// - bool Contains(in ValueBuffer valueBuffer); - - /// - /// 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. - /// - bool Contains(IForeignKey foreignKey, in ValueBuffer valueBuffer); - /// /// 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 diff --git a/src/EFCore/ChangeTracking/Internal/ISnapshot.cs b/src/EFCore/ChangeTracking/Internal/ISnapshot.cs index a13ac2a0a6a..0936212ee6a 100644 --- a/src/EFCore/ChangeTracking/Internal/ISnapshot.cs +++ b/src/EFCore/ChangeTracking/Internal/ISnapshot.cs @@ -26,4 +26,13 @@ public interface ISnapshot /// doing so can result in application failures when updating to a new Entity Framework Core release. /// T GetValue(int index); + + /// + /// 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. + /// + bool IsEmpty + => false; } diff --git a/src/EFCore/ChangeTracking/Internal/IStateManager.cs b/src/EFCore/ChangeTracking/Internal/IStateManager.cs index 04c4b39b9de..03df8915663 100644 --- a/src/EFCore/ChangeTracking/Internal/IStateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/IStateManager.cs @@ -84,7 +84,7 @@ public interface IStateManager : IResettableService InternalEntityEntry StartTrackingFromQuery( IEntityType baseEntityType, object entity, - in ValueBuffer valueBuffer); + in ISnapshot snapshot); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/IdentityMap.cs b/src/EFCore/ChangeTracking/Internal/IdentityMap.cs index 830a062ac73..1c4e6b8d1c1 100644 --- a/src/EFCore/ChangeTracking/Internal/IdentityMap.cs +++ b/src/EFCore/ChangeTracking/Internal/IdentityMap.cs @@ -69,28 +69,6 @@ public IdentityMap( public virtual IEnumerable All() => _identityMap.Values; - /// - /// 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 bool Contains(in ValueBuffer valueBuffer) - { - var key = PrincipalKeyValueFactory.CreateFromBuffer(valueBuffer); - return key != null && _identityMap.ContainsKey((TKey)key); - } - - /// - /// 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 bool Contains(IForeignKey foreignKey, in ValueBuffer valueBuffer) - => foreignKey.GetDependentKeyValueFactory().TryCreateFromBuffer(valueBuffer, out var key) - && _identityMap.ContainsKey(key); - /// /// 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 diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 846a23acc36..040737495a0 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -61,12 +61,48 @@ public InternalEntityEntry( IStateManager stateManager, IEntityType entityType, object entity, - in ValueBuffer valueBuffer) + in ISnapshot snapshot) { StateManager = stateManager; EntityType = (IRuntimeEntityType)entityType; Entity = entity; - _shadowValues = EntityType.ShadowValuesFactory(valueBuffer); + _shadowValues = snapshot; + _stateData = new StateData(EntityType.PropertyCount, EntityType.NavigationCount); + } + + /// + /// 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 InternalEntityEntry( + IStateManager stateManager, + IEntityType entityType, + IDictionary values, + IEntityMaterializerSource entityMaterializerSource) + { + StateManager = stateManager; + EntityType = (IRuntimeEntityType)entityType; + + var valuesArray = new object?[EntityType.PropertyCount]; + var shadowPropertyValuesArray = EntityType.ShadowValuesFactory(values); + foreach (var property in entityType.GetFlattenedProperties()) + { + var index = property.GetIndex(); + if (index < 0) + { + continue; + } + + valuesArray[index] = values.TryGetValue(property.Name, out var value) + ? value + : property.Sentinel; + } + + Entity = entityType.GetOrCreateMaterializer(entityMaterializerSource)( + new MaterializationContext(new ValueBuffer(valuesArray), stateManager.Context)); + _shadowValues = EntityType.ShadowValuesFactory(values); _stateData = new StateData(EntityType.PropertyCount, EntityType.NavigationCount); } diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs index cb38d9f6878..5f7c67314f2 100644 --- a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs @@ -13,6 +13,18 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public class OriginalValuesFactoryFactory : SnapshotFactoryFactory { + private OriginalValuesFactoryFactory() + { + } + + /// + /// 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 readonly OriginalValuesFactoryFactory Instance = new(); + /// /// 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 diff --git a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs index 548aceb5267..4c7b7b5d8b1 100644 --- a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs @@ -13,6 +13,18 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public class RelationshipSnapshotFactoryFactory : SnapshotFactoryFactory { + private RelationshipSnapshotFactoryFactory() + { + } + + /// + /// 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 readonly RelationshipSnapshotFactoryFactory Instance = new(); + /// /// 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 diff --git a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs index 579768ad387..6b8e153dd89 100644 --- a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs @@ -11,8 +11,20 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// 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 class ShadowValuesFactoryFactory : SnapshotFactoryFactory +public class ShadowValuesFactoryFactory : SnapshotFactoryFactory> { + private ShadowValuesFactoryFactory() + { + } + + /// + /// 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 readonly ShadowValuesFactoryFactory Instance = new(); + /// /// 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 @@ -49,6 +61,12 @@ protected override int GetPropertyCount(IRuntimeEntityType entityType) protected override bool UseEntityVariable => false; + internal static readonly MethodInfo ContainsKeyMethod = + typeof(IDictionary).GetMethod(nameof(IDictionary.ContainsKey), new[] { typeof(string) })!; + + private static readonly PropertyInfo DictionaryIndexer + = typeof(IDictionary).GetRuntimeProperties().Single(p => p.GetIndexParameters().Length > 0); + /// /// 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 @@ -56,14 +74,33 @@ protected override bool UseEntityVariable /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override Expression CreateReadShadowValueExpression( - ParameterExpression? parameter, + Expression? parameter, IPropertyBase property) - => Expression.Convert( - Expression.Call( + { + if (parameter == null) + { + return Expression.Default(property.ClrType); + } + + if (parameter is NewArrayExpression newArrayExpression) + { + var valueExpression = newArrayExpression.Expressions[property.GetShadowIndex()]; + valueExpression = ((UnaryExpression)valueExpression).Operand; // Unwrap cast + return valueExpression.Type == property.ClrType + ? valueExpression + : Expression.Convert( + valueExpression, + property.ClrType); + } + + return Expression.Condition(Expression.Call(parameter, ContainsKeyMethod, Expression.Constant(property.Name)), + Expression.Convert(Expression.MakeIndex( parameter, - ValueBuffer.GetValueMethod, - Expression.Constant(property.GetShadowIndex())), - property.ClrType); + DictionaryIndexer, + new[] { Expression.Constant(property.Name) }), + property.ClrType), + Expression.Constant(property.Sentinel, property.ClrType)); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -72,7 +109,7 @@ protected override Expression CreateReadShadowValueExpression( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override Expression CreateReadValueExpression( - ParameterExpression? parameter, + Expression? parameter, IPropertyBase property) => CreateReadShadowValueExpression(parameter, property); } diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs index c66736bdad9..728fc48d83e 100644 --- a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs @@ -13,6 +13,24 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public class SidecarValuesFactoryFactory : SnapshotFactoryFactory { + /// + /// 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. + /// + protected SidecarValuesFactoryFactory() + { + } + + /// + /// 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 readonly SidecarValuesFactoryFactory Instance = new(); + /// /// 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 diff --git a/src/EFCore/ChangeTracking/Internal/SimpleFullyNullableDependentKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimpleFullyNullableDependentKeyValueFactory.cs index 7ac738c4966..03bea964589 100644 --- a/src/EFCore/ChangeTracking/Internal/SimpleFullyNullableDependentKeyValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SimpleFullyNullableDependentKeyValueFactory.cs @@ -47,6 +47,7 @@ public SimpleFullyNullableDependentKeyValueFactory( /// 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. /// + [Obsolete] public virtual bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen(true)] out TKey? key) { key = (TKey)_propertyAccessors.ValueBufferGetter!(valueBuffer); diff --git a/src/EFCore/ChangeTracking/Internal/SimpleNonNullableDependentKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimpleNonNullableDependentKeyValueFactory.cs index 2c9162b6ab5..dffbf8b6efb 100644 --- a/src/EFCore/ChangeTracking/Internal/SimpleNonNullableDependentKeyValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SimpleNonNullableDependentKeyValueFactory.cs @@ -47,6 +47,7 @@ public SimpleNonNullableDependentKeyValueFactory( /// 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. /// + [Obsolete] public virtual bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen(true)] out TKey? key) { var value = _propertyAccessors.ValueBufferGetter!(valueBuffer); diff --git a/src/EFCore/ChangeTracking/Internal/SimpleNullableDependentKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimpleNullableDependentKeyValueFactory.cs index 292804ef50d..a71f44bd3a4 100644 --- a/src/EFCore/ChangeTracking/Internal/SimpleNullableDependentKeyValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SimpleNullableDependentKeyValueFactory.cs @@ -46,6 +46,7 @@ public SimpleNullableDependentKeyValueFactory( /// 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. /// + [Obsolete] public virtual bool TryCreateFromBuffer(in ValueBuffer valueBuffer, out TKey key) { var value = _propertyAccessors.ValueBufferGetter!(valueBuffer); diff --git a/src/EFCore/ChangeTracking/Internal/SimpleNullablePrincipalDependentKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimpleNullablePrincipalDependentKeyValueFactory.cs index ee59ca03cf1..822b015ecb8 100644 --- a/src/EFCore/ChangeTracking/Internal/SimpleNullablePrincipalDependentKeyValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SimpleNullablePrincipalDependentKeyValueFactory.cs @@ -51,6 +51,7 @@ public SimpleNullablePrincipalDependentKeyValueFactory( /// 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. /// + [Obsolete] public virtual bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen(true)] out TKey? key) { var value = _propertyAccessors.ValueBufferGetter!(valueBuffer); diff --git a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs index 69e9f2a3883..71778671a49 100644 --- a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs @@ -48,6 +48,7 @@ public SimplePrincipalKeyValueFactory(IKey key) /// 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. /// + [Obsolete] public virtual object? CreateFromBuffer(ValueBuffer valueBuffer) => _propertyAccessors.ValueBufferGetter!(valueBuffer); diff --git a/src/EFCore/ChangeTracking/Internal/Snapshot.cs b/src/EFCore/ChangeTracking/Internal/Snapshot.cs index 1109f6ed862..2f5435dbec2 100644 --- a/src/EFCore/ChangeTracking/Internal/Snapshot.cs +++ b/src/EFCore/ChangeTracking/Internal/Snapshot.cs @@ -33,6 +33,15 @@ private Snapshot() /// public static ISnapshot Empty = new Snapshot(); + /// + /// 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 readonly FieldInfo EmptyField + = typeof(Snapshot).GetField(nameof(Empty), BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)!; + /// /// 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 @@ -54,6 +63,16 @@ 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 readonly MethodInfo GetValueMethod + = typeof(ISnapshot).GetMethod(nameof(GetValue), 1, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, CallingConventions.Any, new[] { typeof(int) }, 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 @@ -97,6 +116,10 @@ public static Type CreateSnapshotType(Type[] types) 30 => typeof(Snapshot<,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types), _ => throw new IndexOutOfRangeException() }; + + /// + bool ISnapshot.IsEmpty + => true; } /// diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index aba7539759d..127deb27422 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -22,11 +22,7 @@ public abstract class SnapshotFactoryFactory /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Func CreateEmpty(IRuntimeEntityType entityType) - => GetPropertyCount(entityType) == 0 - ? (() => Snapshot.Empty) - : Expression.Lambda>( - CreateConstructorExpression(entityType, null!)) - .Compile(); + => CreateEmptyExpression(entityType).Compile(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -34,11 +30,24 @@ public virtual Func CreateEmpty(IRuntimeEntityType entityType) /// 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 Expression CreateConstructorExpression( + public virtual Expression> CreateEmptyExpression(IRuntimeEntityType entityType) + => Expression.Lambda>(CreateConstructorExpression(entityType, 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 + /// 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 Expression CreateConstructorExpression( IRuntimeEntityType entityType, - ParameterExpression? parameter) + Expression? parameter) { var count = GetPropertyCount(entityType); + if (count == 0) + { + return Expression.MakeMemberAccess(null, Snapshot.EmptyField); + } var types = new Type[count]; var propertyBases = new IPropertyBase?[count]; @@ -91,7 +100,7 @@ protected virtual Expression CreateConstructorExpression( /// protected virtual Expression CreateSnapshotExpression( Type? entityType, - ParameterExpression? parameter, + Expression? parameter, Type[] types, IList propertyBases) { @@ -180,36 +189,34 @@ protected virtual Expression CreateSnapshotExpression( private Expression CreateSnapshotValueExpression(Expression expression, IPropertyBase propertyBase) { - if (propertyBase is IProperty property) + if (propertyBase is not IProperty property + || GetValueComparer(property) is not ValueComparer comparer) { - var comparer = GetValueComparer(property); + return expression; + } - if (comparer != null) - { - if (expression.Type != comparer.Type) - { - expression = Expression.Convert(expression, comparer.Type); - } - - var snapshotExpression = ReplacingExpressionVisitor.Replace( - comparer.SnapshotExpression.Parameters.Single(), - expression, - comparer.SnapshotExpression.Body); - - if (snapshotExpression.Type != propertyBase.ClrType) - { - snapshotExpression = Expression.Convert(snapshotExpression, propertyBase.ClrType); - } - - expression = propertyBase.ClrType.IsNullableType() - ? Expression.Condition( - Expression.Equal(expression, Expression.Constant(null, propertyBase.ClrType)), - Expression.Constant(null, propertyBase.ClrType), - snapshotExpression) - : snapshotExpression; - } + if (expression.Type != comparer.Type) + { + expression = Expression.Convert(expression, comparer.Type); } + var snapshotExpression = ReplacingExpressionVisitor.Replace( + comparer.SnapshotExpression.Parameters.Single(), + expression, + comparer.SnapshotExpression.Body); + + if (snapshotExpression.Type != propertyBase.ClrType) + { + snapshotExpression = Expression.Convert(snapshotExpression, propertyBase.ClrType); + } + + expression = propertyBase.ClrType.IsNullableType() + ? Expression.Condition( + Expression.Equal(expression, Expression.Constant(null, propertyBase.ClrType)), + Expression.Constant(null, propertyBase.ClrType), + snapshotExpression) + : snapshotExpression; + return expression; } @@ -228,7 +235,7 @@ private Expression CreateSnapshotValueExpression(Expression expression, IPropert /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual Expression CreateReadShadowValueExpression( - ParameterExpression? parameter, + Expression? parameter, IPropertyBase property) => Expression.Call( parameter, @@ -242,7 +249,7 @@ protected virtual Expression CreateReadShadowValueExpression( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual Expression CreateReadValueExpression( - ParameterExpression? parameter, + Expression? parameter, IPropertyBase property) => Expression.Call( parameter, diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs index 08328f92b27..375e5b8fe7a 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs @@ -20,17 +20,20 @@ public abstract class SnapshotFactoryFactory : SnapshotFactoryFactory /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Func Create(IRuntimeEntityType entityType) - { - if (GetPropertyCount(entityType) == 0) - { - return _ => Snapshot.Empty; - } + => CreateExpression(entityType).Compile(); + /// + /// 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 Expression> CreateExpression(IRuntimeEntityType entityType) + { var parameter = Expression.Parameter(typeof(TInput), "source"); return Expression.Lambda>( CreateConstructorExpression(entityType, parameter), - parameter) - .Compile(); + parameter); } } diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index b072ae32de6..62b76884c68 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -274,29 +274,7 @@ public virtual InternalEntityEntry GetOrCreateEntry(object entity, IEntityType? /// public virtual InternalEntityEntry CreateEntry(IDictionary values, IEntityType entityType) { - var i = 0; - var runtimeEntityType = (IRuntimeEntityType)entityType; - var valuesArray = new object?[runtimeEntityType.PropertyCount]; - var shadowPropertyValuesArray = new object?[runtimeEntityType.ShadowPropertyCount]; - foreach (var property in entityType.GetFlattenedProperties()) - { - valuesArray[i++] = values.TryGetValue(property.Name, out var value) - ? value - : property.Sentinel; - - if (property.IsShadowProperty()) - { - shadowPropertyValuesArray[property.GetShadowIndex()] = values.TryGetValue(property.Name, out var shadowValue) - ? shadowValue - : property.Sentinel; - } - } - - var valueBuffer = new ValueBuffer(valuesArray); - var entity = entityType.GetOrCreateMaterializer(EntityMaterializerSource)(new MaterializationContext(valueBuffer, Context)); - - var shadowPropertyValueBuffer = new ValueBuffer(shadowPropertyValuesArray); - var entry = new InternalEntityEntry(this, entityType, entity, shadowPropertyValueBuffer); + var entry = new InternalEntityEntry(this, entityType, values, EntityMaterializerSource); UpdateReferenceMaps(entry, EntityState.Detached, null); @@ -331,7 +309,7 @@ private void UpdateReferenceMaps( public virtual InternalEntityEntry StartTrackingFromQuery( IEntityType baseEntityType, object entity, - in ValueBuffer valueBuffer) + in ISnapshot snapshot) { var existingEntry = TryGetEntry(entity); if (existingEntry != null) @@ -345,9 +323,9 @@ public virtual InternalEntityEntry StartTrackingFromQuery( ? baseEntityType : _model.FindRuntimeEntityType(clrType)!; - var newEntry = valueBuffer.IsEmpty + var newEntry = snapshot.IsEmpty ? new InternalEntityEntry(this, entityType, entity) - : new InternalEntityEntry(this, entityType, entity, valueBuffer); + : new InternalEntityEntry(this, entityType, entity, snapshot); foreach (var key in baseEntityType.GetKeys()) { diff --git a/src/EFCore/ChangeTracking/Internal/StoreGeneratedValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/StoreGeneratedValuesFactoryFactory.cs index 90ee70828b9..884bfce0bb0 100644 --- a/src/EFCore/ChangeTracking/Internal/StoreGeneratedValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/StoreGeneratedValuesFactoryFactory.cs @@ -11,6 +11,18 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public class StoreGeneratedValuesFactoryFactory : SidecarValuesFactoryFactory { + private StoreGeneratedValuesFactoryFactory() + { + } + + /// + /// 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 new readonly StoreGeneratedValuesFactoryFactory Instance = new(); + /// /// 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 @@ -26,7 +38,7 @@ protected override bool UseEntityVariable /// 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 override Expression CreateReadShadowValueExpression(ParameterExpression? parameter, IPropertyBase property) + protected override Expression CreateReadShadowValueExpression(Expression? parameter, IPropertyBase property) => Expression.Default(property.ClrType); /// @@ -35,6 +47,6 @@ protected override Expression CreateReadShadowValueExpression(ParameterExpressio /// 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 override Expression CreateReadValueExpression(ParameterExpression? parameter, IPropertyBase property) + protected override Expression CreateReadValueExpression(Expression? parameter, IPropertyBase property) => Expression.Default(property.ClrType); } diff --git a/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs b/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs index f920932cc00..9a26cd70acf 100644 --- a/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs +++ b/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs @@ -20,7 +20,7 @@ public class StructuralEntryCurrentProviderValueComparer : StructuralEntryCurren /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public StructuralEntryCurrentProviderValueComparer( - IPropertyBase property, + IProperty property, ValueConverter converter) : base(property) { diff --git a/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentValueComparer.cs b/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentValueComparer.cs index 8611a04e02a..0aed52dc2c9 100644 --- a/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentValueComparer.cs +++ b/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentValueComparer.cs @@ -19,7 +19,7 @@ public class StructuralEntryCurrentValueComparer : EntryCurrentValueComparer /// 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 StructuralEntryCurrentValueComparer(IPropertyBase property) + public StructuralEntryCurrentValueComparer(IProperty property) : base(property, StructuralComparisons.StructuralComparer) { } diff --git a/src/EFCore/ChangeTracking/Internal/TemporaryValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/TemporaryValuesFactoryFactory.cs index e422ae610c7..459a815fc28 100644 --- a/src/EFCore/ChangeTracking/Internal/TemporaryValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/TemporaryValuesFactoryFactory.cs @@ -13,6 +13,18 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public class TemporaryValuesFactoryFactory : SidecarValuesFactoryFactory { + private TemporaryValuesFactoryFactory() + { + } + + /// + /// 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 new readonly TemporaryValuesFactoryFactory Instance = new(); + /// /// 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 @@ -21,7 +33,7 @@ public class TemporaryValuesFactoryFactory : SidecarValuesFactoryFactory /// protected override Expression CreateSnapshotExpression( [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type? entityType, - ParameterExpression? parameter, + Expression? parameter, Type[] types, IList propertyBases) { diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 82eb39d9283..bac80203345 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -82,6 +82,12 @@ IEqualityComparer CreateKeyEqualityComparer() /// new IEnumerable GetContainingKeys(); + /// + /// Gets a for comparing values in tracked entries. + /// + /// The comparer. + IComparer GetCurrentValueComparer(); + /// /// Gets the for this property. /// diff --git a/src/EFCore/Metadata/IPropertyBase.cs b/src/EFCore/Metadata/IPropertyBase.cs index 539bdc8e164..a180fa60267 100644 --- a/src/EFCore/Metadata/IPropertyBase.cs +++ b/src/EFCore/Metadata/IPropertyBase.cs @@ -32,12 +32,6 @@ public interface IPropertyBase : IReadOnlyPropertyBase, IAnnotatable /// The accessor. IClrPropertyGetter GetGetter(); - /// - /// Gets a for comparing values in tracked entries. - /// - /// The comparer. - IComparer GetCurrentValueComparer(); - /// /// Gets the or that should be used to /// get or set a value for the given property. diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index b4dda1a09c1..99c31f332dc 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -64,7 +65,7 @@ private readonly SortedDictionary _triggers private Func? _originalValuesFactory; private Func? _temporaryValuesFactory; private Func? _storeGeneratedValuesFactory; - private Func? _shadowValuesFactory; + private Func, ISnapshot>? _shadowValuesFactory; private Func? _emptyShadowValuesFactory; private IProperty[]? _foreignKeyProperties; private IProperty[]? _valueGeneratingProperties; @@ -2276,7 +2277,7 @@ public virtual Func RelationshipSnapshotFactory static entityType => { entityType.EnsureReadOnly(); - return new RelationshipSnapshotFactoryFactory().Create(entityType); + return RelationshipSnapshotFactoryFactory.Instance.Create(entityType); }); /// @@ -2291,7 +2292,7 @@ public virtual Func OriginalValuesFactory static entityType => { entityType.EnsureReadOnly(); - return new OriginalValuesFactoryFactory().Create(entityType); + return OriginalValuesFactoryFactory.Instance.Create(entityType); }); /// @@ -2306,7 +2307,7 @@ public virtual Func StoreGeneratedValuesFactory static entityType => { entityType.EnsureReadOnly(); - return new StoreGeneratedValuesFactoryFactory().CreateEmpty(entityType); + return StoreGeneratedValuesFactoryFactory.Instance.CreateEmpty(entityType); }); /// @@ -2321,7 +2322,7 @@ public virtual Func TemporaryValuesFactory static entityType => { entityType.EnsureReadOnly(); - return new TemporaryValuesFactoryFactory().Create(entityType); + return TemporaryValuesFactoryFactory.Instance.Create(entityType); }); /// @@ -2330,13 +2331,13 @@ public virtual Func TemporaryValuesFactory /// 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 ShadowValuesFactory + public virtual Func, ISnapshot> ShadowValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _shadowValuesFactory, this, static entityType => { entityType.EnsureReadOnly(); - return new ShadowValuesFactoryFactory().Create(entityType); + return ShadowValuesFactoryFactory.Instance.Create(entityType); }); /// @@ -2351,7 +2352,7 @@ public virtual Func EmptyShadowValuesFactory static entityType => { entityType.EnsureReadOnly(); - return new EmptyShadowValuesFactoryFactory().CreateEmpty(entityType); + return EmptyShadowValuesFactoryFactory.Instance.CreateEmpty(entityType); }); /// diff --git a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs index 30c960c2dfe..e449e843dff 100644 --- a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs +++ b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs @@ -129,7 +129,7 @@ int ComplexPropertyCount /// 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. /// - Func ShadowValuesFactory { get; } + Func, ISnapshot> ShadowValuesFactory { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 6cc5ee1dbaf..35b3cfe445c 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -23,6 +23,7 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IPr private object? _sentinel; private ValueGenerated? _valueGenerated; private CoreTypeMapping? _typeMapping; + private IComparer? _currentValueComparer; private ConfigurationSource? _typeConfigurationSource; private ConfigurationSource? _isNullableConfigurationSource; @@ -936,6 +937,20 @@ public virtual CoreTypeMapping? TypeMapping public virtual ConfigurationSource? GetTypeMappingConfigurationSource() => _typeMappingConfigurationSource; + /// + /// 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 IComparer CurrentValueComparer + => NonCapturingLazyInitializer.EnsureInitialized( + ref _currentValueComparer, this, static property => + { + property.EnsureReadOnly(); + return new CurrentValueComparerFactory().Create(property); + }); + /// /// 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 @@ -1871,6 +1886,16 @@ void IMutableProperty.SetProviderClrType(Type? providerClrType) providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// 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. + /// + [DebuggerStepThrough] + IComparer IProperty.GetCurrentValueComparer() + => CurrentValueComparer; + /// /// 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 diff --git a/src/EFCore/Metadata/Internal/PropertyAccessors.cs b/src/EFCore/Metadata/Internal/PropertyAccessors.cs index 4767a15f672..4a40497da83 100644 --- a/src/EFCore/Metadata/Internal/PropertyAccessors.cs +++ b/src/EFCore/Metadata/Internal/PropertyAccessors.cs @@ -29,7 +29,9 @@ public PropertyAccessors( PreStoreGeneratedCurrentValueGetter = preStoreGeneratedCurrentValueGetter; OriginalValueGetter = originalValueGetter; RelationshipSnapshotGetter = relationshipSnapshotGetter; +#pragma warning disable CS0612 // Type or member is obsolete ValueBufferGetter = valueBufferGetter; +#pragma warning restore CS0612 // Type or member is obsolete } /// @@ -70,5 +72,6 @@ public PropertyAccessors( /// 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. /// + [Obsolete] public Func? ValueBufferGetter { get; } } diff --git a/src/EFCore/Metadata/Internal/PropertyBase.cs b/src/EFCore/Metadata/Internal/PropertyBase.cs index ac81bb041fa..c1a3321f53c 100644 --- a/src/EFCore/Metadata/Internal/PropertyBase.cs +++ b/src/EFCore/Metadata/Internal/PropertyBase.cs @@ -25,7 +25,6 @@ public abstract class PropertyBase : ConventionAnnotatable, IMutablePropertyBase private IClrPropertySetter? _materializationSetter; private PropertyAccessors? _accessors; private PropertyIndexes? _indexes; - private IComparer? _currentValueComparer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -400,23 +399,6 @@ public virtual PropertyAccessors Accessors return new PropertyAccessorsFactory().Create(property); }); - /// - /// 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 IComparer CurrentValueComparer - => NonCapturingLazyInitializer.EnsureInitialized( - ref _currentValueComparer, this, static property => - { - property.EnsureReadOnly(); - return new CurrentValueComparerFactory().Create(property); - }); - - private static readonly MethodInfo ContainsKeyMethod = - typeof(IDictionary).GetMethod(nameof(IDictionary.ContainsKey), new[] { typeof(string) })!; - /// /// 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 @@ -438,7 +420,7 @@ public static Expression CreateMemberAccess( { expression = Expression.Condition( Expression.Call( - instanceExpression, ContainsKeyMethod, new List { Expression.Constant(property.Name) }), + instanceExpression, ShadowValuesFactoryFactory.ContainsKeyMethod, new List { Expression.Constant(property.Name) }), expression, expression.Type.GetDefaultValueConstant()); } @@ -571,16 +553,6 @@ void IMutablePropertyBase.SetPropertyAccessMode(PropertyAccessMode? propertyAcce IClrPropertyGetter IPropertyBase.GetGetter() => Getter; - /// - /// 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. - /// - [DebuggerStepThrough] - IComparer IPropertyBase.GetCurrentValueComparer() - => CurrentValueComparer; - /// /// Gets the sentinel value that indicates that this property is not set. /// diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index 79bbd12c4ee..be62a254d74 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -56,7 +56,7 @@ private readonly SortedDictionary _triggers private Func? _originalValuesFactory; private Func? _temporaryValuesFactory; private Func? _storeGeneratedValuesFactory; - private Func? _shadowValuesFactory; + private Func, ISnapshot>? _shadowValuesFactory; private Func? _emptyShadowValuesFactory; private RuntimePropertyBase[]? _snapshottableProperties; private Func? _materializer; @@ -1245,27 +1245,19 @@ IEnumerable IReadOnlyEntityType.GetDeclaredTriggers() IEnumerable IEntityType.GetDeclaredTriggers() => GetDeclaredTriggers(); - /// - Func IRuntimeEntityType.RelationshipSnapshotFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _relationshipSnapshotFactory, this, - static entityType => RuntimeFeature.IsDynamicCodeSupported - ? new RelationshipSnapshotFactoryFactory().Create(entityType) - : throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)); - /// [DebuggerStepThrough] IEnumerable IEntityType.GetForeignKeyProperties() => NonCapturingLazyInitializer.EnsureInitialized( ref _foreignKeyProperties, this, - static entityType => { return entityType.GetProperties().Where(p => ((IReadOnlyProperty)p).IsForeignKey()).ToArray(); }); + static entityType => entityType.GetProperties().Where(p => ((IReadOnlyProperty)p).IsForeignKey()).ToArray()); /// [DebuggerStepThrough] IEnumerable IEntityType.GetValueGeneratingProperties() => NonCapturingLazyInitializer.EnsureInitialized( ref _valueGeneratingProperties, this, - static entityType => { return entityType.GetProperties().Where(p => p.RequiresValueGenerator()).ToArray(); }); + static entityType => entityType.GetProperties().Where(p => p.RequiresValueGenerator()).ToArray()); /// [DebuggerStepThrough] @@ -1343,16 +1335,6 @@ public virtual void SetStoreGeneratedValuesFactory(Func factory) 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 @@ -1363,73 +1345,51 @@ public virtual void SetShadowValuesFactory(Func factory) public virtual void SetEmptyShadowValuesFactory(Func factory) => _emptyShadowValuesFactory = 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 Func OriginalValuesFactory + /// + Func IRuntimeEntityType.OriginalValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _originalValuesFactory, this, - static complexType => RuntimeFeature.IsDynamicCodeSupported - ? new OriginalValuesFactoryFactory().Create(complexType) + static entityType => RuntimeFeature.IsDynamicCodeSupported + ? OriginalValuesFactoryFactory.Instance.Create(entityType) : throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)); - /// - /// 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 Func StoreGeneratedValuesFactory + /// + Func IRuntimeEntityType.StoreGeneratedValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _storeGeneratedValuesFactory, this, - static complexType => RuntimeFeature.IsDynamicCodeSupported - ? new StoreGeneratedValuesFactoryFactory().CreateEmpty(complexType) + static entityType => RuntimeFeature.IsDynamicCodeSupported + ? StoreGeneratedValuesFactoryFactory.Instance.CreateEmpty(entityType) : throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)); - /// - /// 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 Func TemporaryValuesFactory + /// + Func IRuntimeEntityType.TemporaryValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _temporaryValuesFactory, this, - static complexType => RuntimeFeature.IsDynamicCodeSupported - ? new TemporaryValuesFactoryFactory().Create(complexType) + static entityType => RuntimeFeature.IsDynamicCodeSupported + ? TemporaryValuesFactoryFactory.Instance.Create(entityType) : throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)); - /// - /// 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 Func ShadowValuesFactory + /// + Func, ISnapshot> IRuntimeEntityType.ShadowValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _shadowValuesFactory, this, - static complexType => RuntimeFeature.IsDynamicCodeSupported - ? new ShadowValuesFactoryFactory().Create(complexType) + static entityType => RuntimeFeature.IsDynamicCodeSupported + ? ShadowValuesFactoryFactory.Instance.Create(entityType) : throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)); - /// - /// 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 Func EmptyShadowValuesFactory + /// + Func IRuntimeEntityType.EmptyShadowValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _emptyShadowValuesFactory, this, - static complexType => RuntimeFeature.IsDynamicCodeSupported - ? new EmptyShadowValuesFactoryFactory().CreateEmpty(complexType) + static entityType => RuntimeFeature.IsDynamicCodeSupported + ? EmptyShadowValuesFactoryFactory.Instance.CreateEmpty(entityType) + : throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)); + + /// + Func IRuntimeEntityType.RelationshipSnapshotFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _relationshipSnapshotFactory, this, + static entityType => RuntimeFeature.IsDynamicCodeSupported + ? RelationshipSnapshotFactoryFactory.Instance.Create(entityType) : throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)); } diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs index 0fd2201f7c0..449816d4d01 100644 --- a/src/EFCore/Metadata/RuntimeProperty.cs +++ b/src/EFCore/Metadata/RuntimeProperty.cs @@ -28,9 +28,10 @@ public class RuntimeProperty : RuntimePropertyBase, IProperty private readonly ValueConverter? _valueConverter; private ValueComparer? _valueComparer; private ValueComparer? _keyValueComparer; - private readonly ValueComparer? _providerValueComparer; + private ValueComparer? _providerValueComparer; private readonly JsonValueReaderWriter? _jsonValueReaderWriter; private CoreTypeMapping? _typeMapping; + private IComparer? _currentValueComparer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -238,6 +239,13 @@ public virtual CoreTypeMapping TypeMapping set => _typeMapping = value; } + /// + [DebuggerStepThrough] + IComparer IProperty.GetCurrentValueComparer() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _currentValueComparer, this, static property => + new CurrentValueComparerFactory().Create(property)); + private ValueComparer GetValueComparer() => (GetValueComparer(null) ?? TypeMapping.Comparer) .ToNullableComparer(ClrType)!; @@ -450,12 +458,12 @@ ValueComparer IProperty.GetKeyValueComparer() /// [DebuggerStepThrough] ValueComparer? IReadOnlyProperty.GetProviderValueComparer() - => _providerValueComparer ?? TypeMapping.ProviderValueComparer; + => _providerValueComparer ??= TypeMapping.ProviderValueComparer; /// [DebuggerStepThrough] ValueComparer IProperty.GetProviderValueComparer() - => _providerValueComparer ?? TypeMapping.ProviderValueComparer; + => _providerValueComparer ??= TypeMapping.ProviderValueComparer; /// [DebuggerStepThrough] diff --git a/src/EFCore/Metadata/RuntimePropertyBase.cs b/src/EFCore/Metadata/RuntimePropertyBase.cs index 89dc9c09cfb..fb322307576 100644 --- a/src/EFCore/Metadata/RuntimePropertyBase.cs +++ b/src/EFCore/Metadata/RuntimePropertyBase.cs @@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -27,7 +26,6 @@ public abstract class RuntimePropertyBase : AnnotatableBase, IRuntimePropertyBas private IClrPropertySetter? _materializationSetter; private PropertyAccessors? _accessors; private PropertyIndexes? _indexes; - private IComparer? _currentValueComparer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -176,11 +174,4 @@ IClrPropertySetter IRuntimePropertyBase.GetSetter() IClrPropertyGetter IPropertyBase.GetGetter() => NonCapturingLazyInitializer.EnsureInitialized( ref _getter, this, static property => new ClrPropertyGetterFactory().Create(property)); - - /// - [DebuggerStepThrough] - IComparer IPropertyBase.GetCurrentValueComparer() - => NonCapturingLazyInitializer.EnsureInitialized( - ref _currentValueComparer, this, static property => - new CurrentValueComparerFactory().Create(property)); } diff --git a/src/EFCore/Query/QueryContext.cs b/src/EFCore/Query/QueryContext.cs index 58a57ce456d..9237d7ff653 100644 --- a/src/EFCore/Query/QueryContext.cs +++ b/src/EFCore/Query/QueryContext.cs @@ -142,7 +142,7 @@ public virtual void InitializeStateManager(bool standAlone = false) public virtual InternalEntityEntry StartTracking( IEntityType entityType, object entity, - ValueBuffer valueBuffer) + in ISnapshot snapshot) // InitializeStateManager will populate the field before calling here - => _stateManager!.StartTrackingFromQuery(entityType, entity, valueBuffer); + => _stateManager!.StartTrackingFromQuery(entityType, entity, snapshot); } diff --git a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs index b3c3a01bfff..af0b6288bd7 100644 --- a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs @@ -271,9 +271,6 @@ private sealed class EntityMaterializerInjectingExpressionVisitor : ExpressionVi private static readonly ConstructorInfo MaterializationContextConstructor = typeof(MaterializationContext).GetConstructors().Single(ci => ci.GetParameters().Length == 2); - private static readonly ConstructorInfo ValueBufferConstructor - = typeof(ValueBuffer).GetTypeInfo().DeclaredConstructors.Single(ci => ci.GetParameters().Length == 1); - private static readonly PropertyInfo DbContextMemberInfo = typeof(QueryContext).GetTypeInfo().GetProperty(nameof(QueryContext.Context))!; @@ -289,7 +286,7 @@ private static readonly MethodInfo TryGetEntryMethodInfo private static readonly MethodInfo StartTrackingMethodInfo = typeof(QueryContext).GetMethod( - nameof(QueryContext.StartTracking), new[] { typeof(IEntityType), typeof(object), typeof(ValueBuffer) })!; + nameof(QueryContext.StartTracking), new[] { typeof(IEntityType), typeof(object), typeof(ISnapshot).MakeByRefType() })!; private static readonly MethodInfo CreateNullKeyValueInNoTrackingQueryMethod = typeof(EntityMaterializerInjectingExpressionVisitor) @@ -488,13 +485,13 @@ private Expression MaterializeEntity( var variables = new List(); var shadowValuesVariable = Variable( - typeof(ValueBuffer), - "shadowValueBuffer" + _currentEntityIndex); + typeof(ISnapshot), + "shadowSnapshot" + _currentEntityIndex); variables.Add(shadowValuesVariable); expressions.Add( Assign( shadowValuesVariable, - Constant(ValueBuffer.Empty))); + Constant(Snapshot.Empty))); var returnType = typeBase.ClrType; var valueBufferExpression = Call(materializationContextVariable, MaterializationContext.GetValueBufferMethod); @@ -584,27 +581,21 @@ private BlockExpression CreateFullMaterializeExpression( var valueBufferExpression = Call( materializationContextVariable, MaterializationContext.GetValueBufferMethod); - IEnumerable shadowProperties = runtimeEntityType.GetProperties(); - - if (runtimeEntityType is IEntityType concreteEntityType) - { - shadowProperties = shadowProperties - .Concat(concreteEntityType.GetNavigations()) - .Concat(concreteEntityType.GetSkipNavigations()); - } - - shadowProperties = shadowProperties.Where(n => n.IsShadowProperty()).OrderBy(e => e.GetShadowIndex()); + var shadowProperties = ((IEnumerable)runtimeEntityType.GetProperties()) + .Concat(runtimeEntityType.GetNavigations()) + .Concat(runtimeEntityType.GetSkipNavigations()) + .Where(n => n.IsShadowProperty()) + .OrderBy(e => e.GetShadowIndex()); blockExpressions.Add( Assign( shadowValuesVariable, - New( - ValueBufferConstructor, + ShadowValuesFactoryFactory.Instance.CreateConstructorExpression(runtimeEntityType, NewArrayInit( typeof(object), - shadowProperties.Select( - p => valueBufferExpression.CreateValueBufferReadValueExpression( - typeof(object), p.GetIndex(), p)))))); + shadowProperties.Select(p => + Convert(valueBufferExpression.CreateValueBufferReadValueExpression( + p.ClrType, p.GetIndex(), p), typeof(object))))))); } materializer = materializer.Type == returnType diff --git a/src/EFCore/Query/StructuralTypeShaperExpression.cs b/src/EFCore/Query/StructuralTypeShaperExpression.cs index 26b24a7989e..21800633f87 100644 --- a/src/EFCore/Query/StructuralTypeShaperExpression.cs +++ b/src/EFCore/Query/StructuralTypeShaperExpression.cs @@ -216,7 +216,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// This expression if the type was not changed, or a new expression with the updated type. public virtual StructuralTypeShaperExpression WithType(ITypeBase type) => type != StructuralType - ? new StructuralTypeShaperExpression(type, ValueBufferExpression, IsNullable) + ? new StructuralTypeShaperExpression(type, ValueBufferExpression, IsNullable, materializationCondition: null) : this; /// @@ -227,7 +227,7 @@ public virtual StructuralTypeShaperExpression WithType(ITypeBase type) public virtual StructuralTypeShaperExpression MakeNullable(bool nullable = true) => IsNullable != nullable // Marking nullable requires re-computation of materialization condition - ? new StructuralTypeShaperExpression(StructuralType, ValueBufferExpression, nullable) + ? new StructuralTypeShaperExpression(StructuralType, ValueBufferExpression, nullable, materializationCondition: null) : this; /// diff --git a/src/EFCore/Storage/ValueConversion/Internal/StringNumberConverter.cs b/src/EFCore/Storage/ValueConversion/Internal/StringNumberConverter.cs index f648f55ff01..83dc64d4102 100644 --- a/src/EFCore/Storage/ValueConversion/Internal/StringNumberConverter.cs +++ b/src/EFCore/Storage/ValueConversion/Internal/StringNumberConverter.cs @@ -108,7 +108,7 @@ protected static Expression> ToNumber() if (typeof(TNumber).IsNullableType()) { expression = Expression.Condition( - Expression.Call(param, typeof(TNumber).GetMethod("get_HasValue")!), + Expression.MakeMemberAccess(param, typeof(TNumber).GetProperty("HasValue")!), expression, Expression.Constant(null, typeof(string))); } diff --git a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs index ffc0e74493c..3d91a2b8836 100644 --- a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs @@ -999,9 +999,8 @@ protected void AssertPropertiesModified(EntityEntry entry, bool expected) protected static EntityEntry TrackFromQuery(DbContext context, TEntity pub) where TEntity : class - => new( - context.GetService().StartTrackingFromQuery( - context.Model.FindEntityType(typeof(TEntity))!, pub, new ValueBuffer())); + => new(context.GetService().StartTrackingFromQuery( + context.Model.FindEntityType(typeof(TEntity))!, pub, Snapshot.Empty)); protected virtual void ExecuteWithStrategyInTransaction( Action testOperation, diff --git a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs index eeb03f1ca2d..88fa345bd89 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs @@ -2106,30 +2106,30 @@ public void Navigation_fixup_happens_when_entities_are_tracked_from_query() var stateManager = context.GetService(); - stateManager.StartTrackingFromQuery(categoryType, new Category(11), new ValueBuffer(new object[] { 11 })); - stateManager.StartTrackingFromQuery(categoryType, new Category(12), new ValueBuffer(new object[] { 12 })); - stateManager.StartTrackingFromQuery(categoryType, new Category(13), new ValueBuffer(new object[] { 13 })); + stateManager.StartTrackingFromQuery(categoryType, new Category(11), new Snapshot(11)); + stateManager.StartTrackingFromQuery(categoryType, new Category(12), new Snapshot(12)); + stateManager.StartTrackingFromQuery(categoryType, new Category(13), new Snapshot(13)); - stateManager.StartTrackingFromQuery(productType, new Product(21, 11), new ValueBuffer(new object[] { 21, 11 })); + stateManager.StartTrackingFromQuery(productType, new Product(21, 11), new Snapshot(21, 11)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(productType, new Product(22, 11), new ValueBuffer(new object[] { 22, 11 })); + stateManager.StartTrackingFromQuery(productType, new Product(22, 11), new Snapshot(22, 11)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(productType, new Product(23, 11), new ValueBuffer(new object[] { 23, 11 })); + stateManager.StartTrackingFromQuery(productType, new Product(23, 11), new Snapshot(23, 11)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(productType, new Product(24, 12), new ValueBuffer(new object[] { 24, 12 })); + stateManager.StartTrackingFromQuery(productType, new Product(24, 12), new Snapshot(24, 12)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(productType, new Product(25, 12), new ValueBuffer(new object[] { 25, 12 })); + stateManager.StartTrackingFromQuery(productType, new Product(25, 12), new Snapshot(25, 12)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(31, 22), new ValueBuffer(new object[] { 31, 22 })); + stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(31, 22), new Snapshot(31, 22)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(32, 22), new ValueBuffer(new object[] { 32, 22 })); + stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(32, 22), new Snapshot(32, 22)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(33, 24), new ValueBuffer(new object[] { 33, 24 })); + stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(33, 24), new Snapshot(33, 24)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(34, 24), new ValueBuffer(new object[] { 34, 24 })); + stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(34, 24), new Snapshot(34, 24)); AssertAllFixedUp(context); - stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(35, 24), new ValueBuffer(new object[] { 35, 24 })); + stateManager.StartTrackingFromQuery(offerType, new SpecialOffer(35, 24), new Snapshot(35, 24)); AssertAllFixedUp(context); diff --git a/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs index c8116a31f9f..cd7944103ff 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs @@ -606,11 +606,11 @@ public void StartTracking_is_no_op_if_entity_is_already_tracked() var stateManager = CreateStateManager(model); var category = new Category { Id = 77, PrincipalId = 777 }; - var valueBuffer = new ValueBuffer(new object[] { 77, "Bjork", 777 }); + var snapshot = new Snapshot(77, "Bjork", 777); - var entry = stateManager.StartTrackingFromQuery(categoryType, category, valueBuffer); + var entry = stateManager.StartTrackingFromQuery(categoryType, category, snapshot); - Assert.Same(entry, stateManager.StartTrackingFromQuery(categoryType, category, valueBuffer)); + Assert.Same(entry, stateManager.StartTrackingFromQuery(categoryType, category, snapshot)); } [ConditionalFact] diff --git a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs index a3319ee9c8a..3887affd924 100644 --- a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs +++ b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs @@ -134,7 +134,7 @@ public InternalEntityEntry CreateEntry(IDictionary values, IEnti public InternalEntityEntry StartTrackingFromQuery( IEntityType baseEntityType, object entity, - in ValueBuffer valueBuffer) + in ISnapshot snapshot) => throw new NotImplementedException(); public InternalEntityEntry TryGetEntry(IKey key, IReadOnlyList keyValues)