Skip to content

Commit

Permalink
Change IStateManager.StartTrackingFromQuery to use ISnapshot instead …
Browse files Browse the repository at this point in the history
…of ValueBuffer (#32099)

Fixes #31117
Part of #26544
  • Loading branch information
AndriySvyryd authored Oct 24, 2023
1 parent b614e3e commit 00d51eb
Show file tree
Hide file tree
Showing 48 changed files with 454 additions and 358 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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<ParameterExpression>();
var shaperBlockExpressions = new List<Expression>();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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])
Expand Down Expand Up @@ -1881,7 +1888,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional
{
Assign(entityAlreadyTrackedVariable, Constant(false)),

// shadowValueBuffer = ValueBuffer;
// shadowSnapshot = Snapshot.Empty;
ifFalseBlock.Expressions[0],

// entityType = EntityType;
Expand All @@ -1904,20 +1911,20 @@ protected override Expression VisitConditional(ConditionalExpression conditional
var newInstanceAssignmentVariables = instanceAssignmentBody.Variables.ToList();
var newInstanceAssignmentExpressions = new List<Expression>();

// 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(...)
// jsonManagerPrm.CaptureState();
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])
Expand Down
1 change: 1 addition & 0 deletions src/EFCore/ChangeTracking/IDependentKeyValueFactory`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public interface IDependentKeyValueFactory<TKey> : IDependentKeyValueFactory
/// <param name="key">The key instance.</param>
/// <returns><see langword="true" /> if the key instance was created; <see langword="false" /> otherwise.</returns>
[ContractAnnotation("=>true, key:notnull; =>false, key:null")]
[Obsolete]
bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen(true)] out TKey? key);

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/EFCore/ChangeTracking/IPrincipalKeyValueFactory`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public interface IPrincipalKeyValueFactory<TKey> : IPrincipalKeyValueFactory
/// </summary>
/// <param name="valueBuffer">The buffer containing key values.</param>
/// <returns>The key object, or null if any of the key values were null.</returns>
[Obsolete]
object? CreateFromBuffer(ValueBuffer valueBuffer);

/// <summary>
Expand Down
79 changes: 38 additions & 41 deletions src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public virtual IComparer<IUpdateEntry> Create(IPropertyBase propertyBase)
public virtual IComparer<IUpdateEntry> Create(IProperty property)
{
var modelType = propertyBase.ClrType;
var modelType = property.ClrType;
var nonNullableModelType = modelType.UnwrapNullableType();
if (IsGenericComparable(modelType, nonNullableModelType))
{
return (IComparer<IUpdateEntry>)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<IUpdateEntry>)Activator.CreateInstance(comparerType, propertyBase, converter)!;
}

if (typeof(IStructuralComparable).IsAssignableFrom(nonNullableProviderType))
{
return new StructuralEntryCurrentProviderValueComparer(propertyBase, converter);
}
return (IComparer<IUpdateEntry>)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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
/// </summary>
public class EmptyShadowValuesFactoryFactory : SnapshotFactoryFactory
{
private EmptyShadowValuesFactoryFactory()
{
}

/// <summary>
/// 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.
/// </summary>
public static readonly EmptyShadowValuesFactoryFactory Instance = new();

/// <summary>
/// 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
Expand Down Expand Up @@ -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.
/// </summary>
protected override Expression CreateReadShadowValueExpression(ParameterExpression? parameter, IPropertyBase property)
protected override Expression CreateReadShadowValueExpression(Expression? parameter, IPropertyBase property)
=> Expression.Default(property.ClrType);

/// <summary>
Expand All @@ -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.
/// </summary>
protected override Expression CreateReadValueExpression(ParameterExpression? parameter, IPropertyBase property)
protected override Expression CreateReadValueExpression(Expression? parameter, IPropertyBase property)
=> Expression.Default(property.ClrType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class EntryCurrentProviderValueComparer : EntryCurrentValueComparer
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EntryCurrentProviderValueComparer(
IPropertyBase property,
IProperty property,
ValueConverter converter)
: base(property)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
/// </summary>
public class EntryCurrentValueComparer : IComparer<IUpdateEntry>, IEqualityComparer<IUpdateEntry>
{
private readonly IPropertyBase _property;
private readonly IProperty _property;
private readonly IComparer _underlyingComparer;

/// <summary>
Expand All @@ -22,7 +22,7 @@ public class EntryCurrentValueComparer : IComparer<IUpdateEntry>, 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.
/// </summary>
public EntryCurrentValueComparer(IPropertyBase property)
public EntryCurrentValueComparer(IProperty property)
: this(property, Comparer.Default)
{
}
Expand All @@ -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.
/// </summary>
public EntryCurrentValueComparer(IPropertyBase property, IComparer underlyingComparer)
public EntryCurrentValueComparer(IProperty property, IComparer underlyingComparer)
{
_property = property;
_underlyingComparer = underlyingComparer;
Expand Down
16 changes: 0 additions & 16 deletions src/EFCore/ChangeTracking/Internal/IIdentityMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,6 @@ public interface IIdentityMap
/// </summary>
IEnumerable<InternalEntityEntry> All();

/// <summary>
/// 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.
/// </summary>
bool Contains(in ValueBuffer valueBuffer);

/// <summary>
/// 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.
/// </summary>
bool Contains(IForeignKey foreignKey, in ValueBuffer valueBuffer);

/// <summary>
/// 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
Expand Down
9 changes: 9 additions & 0 deletions src/EFCore/ChangeTracking/Internal/ISnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,13 @@ public interface ISnapshot
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
T GetValue<T>(int index);

/// <summary>
/// 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.
/// </summary>
bool IsEmpty
=> false;
}
2 changes: 1 addition & 1 deletion src/EFCore/ChangeTracking/Internal/IStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public interface IStateManager : IResettableService
InternalEntityEntry StartTrackingFromQuery(
IEntityType baseEntityType,
object entity,
in ValueBuffer valueBuffer);
in ISnapshot snapshot);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Loading

0 comments on commit 00d51eb

Please sign in to comment.