Skip to content

Commit

Permalink
Thanksgiving project: lookup entities by primary key, alternate key, …
Browse files Browse the repository at this point in the history
…or foreign key

Fixes #29685.
  • Loading branch information
ajcvickers committed Dec 3, 2022
1 parent 35e9e5a commit 17449e8
Show file tree
Hide file tree
Showing 23 changed files with 3,297 additions and 79 deletions.
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/IPrincipalKeyValueFactory`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking;
public interface IPrincipalKeyValueFactory<TKey> : IPrincipalKeyValueFactory
{
/// <summary>
/// Creates a key object from key values obtained in-order from the given array.
/// Creates a key object from key values obtained in-order from the given enumerable.
/// </summary>
/// <param name="keyValues">The key values.</param>
/// <returns>The key object, or null if any of the key values were null.</returns>
object? CreateFromKeyValues(object?[] keyValues);
object? CreateFromKeyValues(IEnumerable<object?> keyValues);

/// <summary>
/// Creates a key object from key values obtained from their indexed position in the given <see cref="ValueBuffer" />.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
public class CompositeDependentKeyValueFactory : CompositeValueFactory
{
private readonly IForeignKey _foreignKey;
private readonly IPrincipalKeyValueFactory<object[]> _principalKeyValueFactory;
private readonly IPrincipalKeyValueFactory<IEnumerable<object?>> _principalKeyValueFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -22,7 +22,7 @@ public class CompositeDependentKeyValueFactory : CompositeValueFactory
/// </summary>
public CompositeDependentKeyValueFactory(
IForeignKey foreignKey,
IPrincipalKeyValueFactory<object[]> principalKeyValueFactory)
IPrincipalKeyValueFactory<IEnumerable<object?>> principalKeyValueFactory)
: base(foreignKey.Properties)
{
_foreignKey = foreignKey;
Expand All @@ -36,7 +36,7 @@ public CompositeDependentKeyValueFactory(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override object CreatePrincipalEquatableKey(IUpdateEntry entry, bool fromOriginalValues)
=> new EquatableKeyValue<object[]>(
=> new EquatableKeyValue<IEnumerable<object?>>(
_foreignKey,
fromOriginalValues
? _principalKeyValueFactory.CreateFromOriginalValues(entry)!
Expand All @@ -52,9 +52,9 @@ public override object CreatePrincipalEquatableKey(IUpdateEntry entry, bool from
public override object? CreateDependentEquatableKey(IUpdateEntry entry, bool fromOriginalValues)
=> fromOriginalValues
? TryCreateFromOriginalValues(entry, out var originalKeyValue)
? new EquatableKeyValue<object[]>(_foreignKey, originalKeyValue, EqualityComparer)
? new EquatableKeyValue<IEnumerable<object?>>(_foreignKey, originalKeyValue, EqualityComparer)
: null
: TryCreateFromCurrentValues(entry, out var keyValue)
? new EquatableKeyValue<object[]>(_foreignKey, keyValue, EqualityComparer)
? new EquatableKeyValue<IEnumerable<object?>>(_foreignKey, keyValue, EqualityComparer)
: null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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.
/// </summary>
public class CompositePrincipalKeyValueFactory : CompositeValueFactory, IPrincipalKeyValueFactory<object[]>
public class CompositePrincipalKeyValueFactory : CompositeValueFactory, IPrincipalKeyValueFactory<IEnumerable<object?>>
{
private readonly IKey _key;

Expand All @@ -31,8 +31,12 @@ public CompositePrincipalKeyValueFactory(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.
/// </summary>
public virtual object? CreateFromKeyValues(object?[] keyValues)
=> keyValues.Any(v => v == null) ? null : keyValues;
public virtual object? CreateFromKeyValues(IEnumerable<object?> keyValues)
// ReSharper disable once PossibleMultipleEnumeration
=> keyValues.Any(v => v == null)
? null
// ReSharper disable once PossibleMultipleEnumeration
: keyValues;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -70,7 +74,7 @@ public virtual IProperty FindNullPropertyInKeyValues(object?[] keyValues)
/// 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 object[] CreateFromCurrentValues(IUpdateEntry entry)
public virtual IEnumerable<object?> CreateFromCurrentValues(IUpdateEntry entry)
=> CreateFromEntry(entry, (e, p) => e.GetCurrentValue(p));

/// <summary>
Expand All @@ -88,7 +92,7 @@ public virtual object[] CreateFromCurrentValues(IUpdateEntry entry)
/// 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 object[] CreateFromOriginalValues(IUpdateEntry entry)
public virtual IEnumerable<object?> CreateFromOriginalValues(IUpdateEntry entry)
=> CreateFromEntry(entry, (e, p) => e.GetOriginalValue(p));

/// <summary>
Expand All @@ -97,7 +101,7 @@ public virtual object[] CreateFromOriginalValues(IUpdateEntry entry)
/// 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 object[] CreateFromRelationshipSnapshot(IUpdateEntry entry)
public virtual IEnumerable<object?> CreateFromRelationshipSnapshot(IUpdateEntry entry)
=> CreateFromEntry(entry, (e, p) => e.GetRelationshipSnapshotValue(p));

private object[] CreateFromEntry(
Expand Down Expand Up @@ -128,7 +132,7 @@ private object[] CreateFromEntry(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual object CreateEquatableKey(IUpdateEntry entry, bool fromOriginalValues)
=> new EquatableKeyValue<object[]>(
=> new EquatableKeyValue<IEnumerable<object?>>(
_key,
fromOriginalValues
? CreateFromOriginalValues(entry)
Expand Down
83 changes: 58 additions & 25 deletions src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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.
/// </summary>
public class CompositeValueFactory : IDependentKeyValueFactory<object[]>
public class CompositeValueFactory : IDependentKeyValueFactory<IEnumerable<object?>>
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -31,7 +31,7 @@ public CompositeValueFactory(IReadOnlyList<IProperty> properties)
/// 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 IEqualityComparer<object[]> EqualityComparer { get; }
public virtual IEqualityComparer<IEnumerable<object?>> EqualityComparer { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -47,9 +47,9 @@ public CompositeValueFactory(IReadOnlyList<IProperty> properties)
/// 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 bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen(true)] out object[]? key)
public virtual bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen(true)] out IEnumerable<object?>? key)
{
key = new object[Properties.Count];
var keyArray = new object[Properties.Count];
var index = 0;

foreach (var property in Properties)
Expand All @@ -61,9 +61,10 @@ public virtual bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen
return false;
}

key[index++] = value;
keyArray[index++] = value;
}

key = keyArray;
return true;
}

Expand All @@ -73,7 +74,7 @@ public virtual bool TryCreateFromBuffer(in ValueBuffer valueBuffer, [NotNullWhen
/// 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 bool TryCreateFromCurrentValues(IUpdateEntry entry, [NotNullWhen(true)] out object[]? key)
public virtual bool TryCreateFromCurrentValues(IUpdateEntry entry, [NotNullWhen(true)] out IEnumerable<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetCurrentValue(p), out key);

/// <summary>
Expand All @@ -82,7 +83,7 @@ public virtual bool TryCreateFromCurrentValues(IUpdateEntry entry, [NotNullWhen(
/// 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 bool TryCreateFromPreStoreGeneratedCurrentValues(IUpdateEntry entry, [NotNullWhen(true)] out object[]? key)
public virtual bool TryCreateFromPreStoreGeneratedCurrentValues(IUpdateEntry entry, [NotNullWhen(true)] out IEnumerable<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetPreStoreGeneratedCurrentValue(p), out key);

/// <summary>
Expand All @@ -91,7 +92,7 @@ public virtual bool TryCreateFromPreStoreGeneratedCurrentValues(IUpdateEntry ent
/// 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 bool TryCreateFromOriginalValues(IUpdateEntry entry, [NotNullWhen(true)] out object[]? key)
public virtual bool TryCreateFromOriginalValues(IUpdateEntry entry, [NotNullWhen(true)] out IEnumerable<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetOriginalValue(p), out key);

/// <summary>
Expand All @@ -100,7 +101,7 @@ public virtual bool TryCreateFromOriginalValues(IUpdateEntry entry, [NotNullWhen
/// 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 bool TryCreateFromRelationshipSnapshot(IUpdateEntry entry, [NotNullWhen(true)] out object[]? key)
public virtual bool TryCreateFromRelationshipSnapshot(IUpdateEntry entry, [NotNullWhen(true)] out IEnumerable<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetRelationshipSnapshotValue(p), out key);

/// <summary>
Expand All @@ -112,9 +113,9 @@ public virtual bool TryCreateFromRelationshipSnapshot(IUpdateEntry entry, [NotNu
protected virtual bool TryCreateFromEntry(
IUpdateEntry entry,
Func<IUpdateEntry, IProperty, object?> getValue,
[NotNullWhen(true)] out object[]? key)
[NotNullWhen(true)] out IEnumerable<object?>? key)
{
key = new object[Properties.Count];
var keyArray = new object[Properties.Count];
var index = 0;

foreach (var property in Properties)
Expand All @@ -126,9 +127,10 @@ protected virtual bool TryCreateFromEntry(
return false;
}

key[index++] = value;
keyArray[index++] = value;
}

key = keyArray;
return true;
}

Expand Down Expand Up @@ -156,21 +158,23 @@ public virtual object CreatePrincipalEquatableKey(IUpdateEntry entry, bool fromO
/// 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 static IEqualityComparer<object[]> CreateEqualityComparer(IReadOnlyList<IProperty> properties)
protected static IEqualityComparer<IEnumerable<object?>> CreateEqualityComparer(IReadOnlyList<IProperty> properties)
=> new CompositeCustomComparer(properties.Select(p => p.GetKeyValueComparer()).ToList());

private sealed class CompositeCustomComparer : IEqualityComparer<object[]>
private sealed class CompositeCustomComparer : IEqualityComparer<IEnumerable<object?>>
{
private readonly int _valueCount;
private readonly Func<object, object, bool>[] _equals;
private readonly Func<object, int>[] _hashCodes;

public CompositeCustomComparer(IList<ValueComparer> comparers)
{
_valueCount = comparers.Count;
_equals = comparers.Select(c => (Func<object, object, bool>)c.Equals).ToArray();
_hashCodes = comparers.Select(c => (Func<object, int>)c.GetHashCode).ToArray();
}

public bool Equals(object[]? x, object[]? y)
public bool Equals(IEnumerable<object?>? x, IEnumerable<object?>? y)
{
if (ReferenceEquals(x, y))
{
Expand All @@ -187,14 +191,44 @@ public bool Equals(object[]? x, object[]? y)
return false;
}

if (x.Length != y.Length)
if (x is IReadOnlyList<object> xList
&& y is IReadOnlyList<object> yList)
{
return false;
}
if (xList.Count != _valueCount
|| yList.Count != _valueCount)
{
return false;
}

for (var i = 0; i < x.Length; i++)
for (var i = 0; i < _valueCount; i++)
{
if (!_equals[i](xList[i], yList[i]))
{
return false;
}
}
}
else
{
if (!_equals[i](x[i], y[i]))
using var xEnumerator = x.GetEnumerator();
using var yEnumerator = y.GetEnumerator();

for (var i = 0; i < _valueCount; i++)
{
if (!xEnumerator.MoveNext()
|| !yEnumerator.MoveNext())
{
return false;
}

if (!_equals[i++](xEnumerator.Current!, yEnumerator.Current!))
{
return false;
}
}

if (xEnumerator.MoveNext()
|| yEnumerator.MoveNext())
{
return false;
}
Expand All @@ -203,15 +237,14 @@ public bool Equals(object[]? x, object[]? y)
return true;
}

public int GetHashCode(object[] obj)
public int GetHashCode(IEnumerable<object?> obj)
{
var hashCode = new HashCode();

// ReSharper disable once ForCanBeConvertedToForeach
// ReSharper disable once LoopCanBeConvertedToQuery
for (var i = 0; i < obj.Length; i++)
using var enumerator = obj.GetEnumerator();
for (var i = 0; i < _valueCount && enumerator.MoveNext(); i++)
{
hashCode.Add(_hashCodes[i](obj[i]));
hashCode.Add(_hashCodes[i](enumerator.Current!));
}

return hashCode.ToHashCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public virtual IDependentKeyValueFactory<TKey> CreateSimple<TKey>(
/// 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 IDependentKeyValueFactory<object[]> CreateComposite(
public virtual IDependentKeyValueFactory<IEnumerable<object?>> CreateComposite(
IForeignKey foreignKey,
IPrincipalKeyValueFactory<object[]> principalKeyValueFactory)
IPrincipalKeyValueFactory<IEnumerable<object?>> principalKeyValueFactory)
=> new CompositeDependentKeyValueFactory(foreignKey, principalKeyValueFactory);
}
11 changes: 11 additions & 0 deletions src/EFCore/ChangeTracking/Internal/DependentsMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ public virtual IEnumerable<IUpdateEntry> GetDependents(IUpdateEntry principalEnt
? dependents
: Enumerable.Empty<IUpdateEntry>();

/// <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 IEnumerable<IUpdateEntry> GetDependents(IEnumerable<object> keyValues)
=> _map.TryGetValue((TKey)_principalKeyValueFactory.CreateFromKeyValues(keyValues)!, out var dependents)
? dependents
: Enumerable.Empty<IUpdateEntry>();

/// <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
8 changes: 8 additions & 0 deletions src/EFCore/ChangeTracking/Internal/IDependentsMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ public interface IDependentsMap
/// </summary>
IEnumerable<IUpdateEntry> GetDependents(IUpdateEntry principalEntry);

/// <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>
IEnumerable<IUpdateEntry> GetDependents(IEnumerable<object> keyValues);

/// <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
2 changes: 1 addition & 1 deletion src/EFCore/ChangeTracking/Internal/IIdentityMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public interface IIdentityMap
/// 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(object?[] keyValues);
InternalEntityEntry? TryGetEntry(IEnumerable<object?> keyValues);

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

0 comments on commit 17449e8

Please sign in to comment.