Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interceptor for identity resolution when attaching graphs #28246

Merged
merged 2 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ public virtual void BumpValueGenerators(object?[] row)
}

private TKey CreateKey(IUpdateEntry entry)
=> _keyValueFactory.CreateFromCurrentValues(entry);
=> _keyValueFactory.CreateFromCurrentValues(entry)!;

private static object? SnapshotValue(IProperty property, ValueComparer? comparer, IUpdateEntry entry)
{
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/IPrincipalKeyValueFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public interface IPrincipalKeyValueFactory<TKey>
/// </summary>
/// <param name="entry">The entry tracking an entity instance.</param>
/// <returns>The key value.</returns>
TKey CreateFromCurrentValues(IUpdateEntry entry);
TKey? CreateFromCurrentValues(IUpdateEntry entry);

/// <summary>
/// Finds the first null key value in the given entry and returns the associated <see cref="IProperty" />.
Expand All @@ -59,7 +59,7 @@ public interface IPrincipalKeyValueFactory<TKey>
/// </summary>
/// <param name="entry">The entry tracking an entity instance.</param>
/// <returns>The key value.</returns>
TKey CreateFromOriginalValues(IUpdateEntry entry);
TKey? CreateFromOriginalValues(IUpdateEntry entry);

/// <summary>
/// Creates a key object from the relationship snapshot key values in the given entry.
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/ChangeTracking/Internal/DependentsMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private bool TryCreateFromCurrentValues(IUpdateEntry entry, [NotNullWhen(true)]
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IEnumerable<IUpdateEntry> GetDependents(IUpdateEntry principalEntry)
=> _map.TryGetValue(_principalKeyValueFactory.CreateFromCurrentValues(principalEntry), out var dependents)
=> _map.TryGetValue(_principalKeyValueFactory.CreateFromCurrentValues(principalEntry)!, out var dependents)
? dependents
: Enumerable.Empty<IUpdateEntry>();

Expand Down
61 changes: 43 additions & 18 deletions src/EFCore/ChangeTracking/Internal/EntityGraphAttacher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
public class EntityGraphAttacher : IEntityGraphAttacher
{
private readonly IEntityEntryGraphIterator _graphIterator;
private HashSet<object>? _visited;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -49,10 +50,12 @@ public virtual void AttachGraph(
null),
PaintAction);

_visited = null;
rootEntry.StateManager.CompleteAttachGraph();
}
catch
{
_visited = null;
rootEntry.StateManager.AbortAttachGraph();
throw;
}
Expand Down Expand Up @@ -84,22 +87,25 @@ await _graphIterator.TraverseGraphAsync(
PaintActionAsync,
cancellationToken).ConfigureAwait(false);

_visited = null;
rootEntry.StateManager.CompleteAttachGraph();
}
catch
{
_visited = null;
rootEntry.StateManager.AbortAttachGraph();
throw;
}
}

private static bool PaintAction(
private bool PaintAction(
EntityEntryGraphNode<(EntityState TargetState, EntityState StoreGenTargetState, bool Force)> node)
{
SetReferenceLoaded(node);

var internalEntityEntry = node.GetInfrastructure();
if (internalEntityEntry.EntityState != EntityState.Detached)
if (internalEntityEntry.EntityState != EntityState.Detached
|| (_visited != null && _visited.Contains(internalEntityEntry.Entity)))
{
return false;
}
Expand All @@ -108,24 +114,34 @@ private static bool PaintAction(

var (isGenerated, isSet) = internalEntityEntry.IsKeySet;

internalEntityEntry.SetEntityState(
isSet
? (isGenerated ? storeGenTargetState : targetState)
: EntityState.Added, // Key can only be not-set if it is store-generated
acceptChanges: true,
forceStateWhenUnknownKey: force ? targetState : null);
if (internalEntityEntry.StateManager.ResolveToExistingEntry(
internalEntityEntry,
node.InboundNavigation, node.SourceEntry?.GetInfrastructure()))
{
(_visited ??= new HashSet<object>(ReferenceEqualityComparer.Instance)).Add(internalEntityEntry.Entity);
}
else
{
internalEntityEntry.SetEntityState(
isSet
? (isGenerated ? storeGenTargetState : targetState)
: EntityState.Added, // Key can only be not-set if it is store-generated
acceptChanges: true,
forceStateWhenUnknownKey: force ? targetState : null);
}

return true;
}

private static async Task<bool> PaintActionAsync(
private async Task<bool> PaintActionAsync(
EntityEntryGraphNode<(EntityState TargetState, EntityState StoreGenTargetState, bool Force)> node,
CancellationToken cancellationToken)
{
SetReferenceLoaded(node);

var internalEntityEntry = node.GetInfrastructure();
if (internalEntityEntry.EntityState != EntityState.Detached)
if (internalEntityEntry.EntityState != EntityState.Detached
|| (_visited != null && _visited.Contains(internalEntityEntry.Entity)))
{
return false;
}
Expand All @@ -134,14 +150,23 @@ private static async Task<bool> PaintActionAsync(

var (isGenerated, isSet) = internalEntityEntry.IsKeySet;

await internalEntityEntry.SetEntityStateAsync(
isSet
? (isGenerated ? storeGenTargetState : targetState)
: EntityState.Added, // Key can only be not-set if it is store-generated
acceptChanges: true,
forceStateWhenUnknownKey: force ? targetState : null,
cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (internalEntityEntry.StateManager.ResolveToExistingEntry(
internalEntityEntry,
node.InboundNavigation, node.SourceEntry?.GetInfrastructure()))
{
(_visited ??= new HashSet<object>()).Add(internalEntityEntry.Entity);
}
else
{
await internalEntityEntry.SetEntityStateAsync(
isSet
? (isGenerated ? storeGenTargetState : targetState)
: EntityState.Added, // Key can only be not-set if it is store-generated
acceptChanges: true,
forceStateWhenUnknownKey: force ? targetState : null,
cancellationToken: cancellationToken)
.ConfigureAwait(false);
}

return true;
}
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/ChangeTracking/Internal/IIdentityMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public interface IIdentityMap
/// </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
/// 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>
InternalEntityEntry? TryGetEntry(InternalEntityEntry entry);

/// <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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ public interface IInternalEntityEntryNotifier
/// </summary>
void StateChanged(InternalEntityEntry entry, EntityState oldState, bool fromQuery);

/// <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>
void FixupResolved(InternalEntityEntry entry, InternalEntityEntry duplicateEntry);

/// <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
10 changes: 10 additions & 0 deletions src/EFCore/ChangeTracking/Internal/INavigationFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,14 @@ void StateChanged(
InternalEntityEntry entry,
EntityState oldState,
bool fromQuery);

/// <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>
void FixupResolved(
InternalEntityEntry entry,
InternalEntityEntry duplicateEntry);
}
23 changes: 23 additions & 0 deletions src/EFCore/ChangeTracking/Internal/IStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,29 @@ void RecordReferencedUntrackedEntity(
INavigationBase navigation,
InternalEntityEntry referencedFromEntry);

/// <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>
void UpdateReferencedUntrackedEntity(
object referencedEntity,
object newReferencedEntity,
INavigationBase navigation,
InternalEntityEntry referencedFromEntry);

/// <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 ResolveToExistingEntry(
InternalEntityEntry newEntry,
INavigationBase? navigation,
InternalEntityEntry? referencedFromEntry);

/// <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
20 changes: 17 additions & 3 deletions src/EFCore/ChangeTracking/Internal/IdentityMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ public virtual bool Contains(IForeignKey foreignKey, in ValueBuffer valueBuffer)
=> foreignKey.GetDependentKeyValueFactory<TKey>()!.TryCreateFromBuffer(valueBuffer, out var key)
&& _identityMap.ContainsKey(key);

/// <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 virtual InternalEntityEntry? TryGetEntry(InternalEntityEntry entry)
{
var key = PrincipalKeyValueFactory.CreateFromCurrentValues(entry);
return key != null && _identityMap.TryGetValue(key, out var existingEntry)
? existingEntry
: null;
}

/// <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 @@ -194,7 +208,7 @@ public virtual bool Contains(IForeignKey foreignKey, in ValueBuffer valueBuffer)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void AddOrUpdate(InternalEntityEntry entry)
=> Add(PrincipalKeyValueFactory.CreateFromCurrentValues(entry), entry, updateDuplicate: true);
=> Add(PrincipalKeyValueFactory.CreateFromCurrentValues(entry)!, entry, updateDuplicate: true);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -203,7 +217,7 @@ public virtual void AddOrUpdate(InternalEntityEntry entry)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void Add(InternalEntityEntry entry)
=> Add(PrincipalKeyValueFactory.CreateFromCurrentValues(entry), entry);
=> Add(PrincipalKeyValueFactory.CreateFromCurrentValues(entry)!, entry);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -371,7 +385,7 @@ public virtual void Clear()
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void Remove(InternalEntityEntry entry)
=> Remove(PrincipalKeyValueFactory.CreateFromCurrentValues(entry), entry);
=> Remove(PrincipalKeyValueFactory.CreateFromCurrentValues(entry)!, entry);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ public virtual void StateChanged(InternalEntityEntry entry, EntityState oldState
_localViewListener.StateChanged(entry, oldState, fromQuery);
}

/// <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 virtual void FixupResolved(
InternalEntityEntry entry,
InternalEntityEntry duplicateEntry)
=> _navigationFixer.FixupResolved(entry, duplicateEntry);

/// <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
Loading