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

Thanksgiving project: lookup entities by primary key, alternate key, or foreign key #29686

Merged
merged 3 commits into from
Dec 3, 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
6 changes: 3 additions & 3 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(IReadOnlyList<object?> keyValues);

/// <summary>
/// Creates a key object from key values obtained from their indexed position in the given <see cref="ValueBuffer" />.
Expand All @@ -38,7 +38,7 @@ public interface IPrincipalKeyValueFactory<TKey> : IPrincipalKeyValueFactory
/// </summary>
/// <param name="keyValues">The key values.</param>
/// <returns>The associated property.</returns>
IProperty? FindNullPropertyInKeyValues(object?[] keyValues);
IProperty? FindNullPropertyInKeyValues(IReadOnlyList<object?> keyValues);

/// <summary>
/// Creates a key object from the key values in the given entry.
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<IReadOnlyList<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<IReadOnlyList<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<IReadOnlyList<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<IReadOnlyList<object?>>(_foreignKey, originalKeyValue, EqualityComparer)
: null
: TryCreateFromCurrentValues(entry, out var keyValue)
? new EquatableKeyValue<object[]>(_foreignKey, keyValue, EqualityComparer)
? new EquatableKeyValue<IReadOnlyList<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<IReadOnlyList<object?>>
{
private readonly IKey _key;

Expand All @@ -31,8 +31,18 @@ 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(IReadOnlyList<object?> keyValues) // ReSharper disable once PossibleMultipleEnumeration
{
for (var i = 0; i < keyValues.Count; i++)
{
if (keyValues[i] == null)
{
return null;
}
}

return keyValues;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -49,10 +59,10 @@ 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 IProperty FindNullPropertyInKeyValues(object?[] keyValues)
public virtual IProperty FindNullPropertyInKeyValues(IReadOnlyList<object?> keyValues)
{
var index = -1;
for (var i = 0; i < keyValues.Length; i++)
for (var i = 0; i < keyValues.Count; i++)
{
if (keyValues[i] == null)
{
Expand All @@ -70,7 +80,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 IReadOnlyList<object?> CreateFromCurrentValues(IUpdateEntry entry)
=> CreateFromEntry(entry, (e, p) => e.GetCurrentValue(p));

/// <summary>
Expand All @@ -88,7 +98,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 IReadOnlyList<object?> CreateFromOriginalValues(IUpdateEntry entry)
=> CreateFromEntry(entry, (e, p) => e.GetOriginalValue(p));

/// <summary>
Expand All @@ -97,25 +107,23 @@ 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 IReadOnlyList<object?> CreateFromRelationshipSnapshot(IUpdateEntry entry)
=> CreateFromEntry(entry, (e, p) => e.GetRelationshipSnapshotValue(p));

private object[] CreateFromEntry(
IUpdateEntry entry,
Func<IUpdateEntry, IProperty, object?> getValue)
{
var values = new object[Properties.Count];
var index = 0;

foreach (var property in Properties)
for (var i = 0; i < values.Length; i++)
{
var value = getValue(entry, property);
var value = getValue(entry, Properties[i]);
if (value == null)
{
return default!;
}

values[index++] = value;
values[i] = value;
}

return values;
Expand All @@ -128,7 +136,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<IReadOnlyList<object?>>(
_key,
fromOriginalValues
? CreateFromOriginalValues(entry)
Expand Down
63 changes: 32 additions & 31 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<IReadOnlyList<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<IReadOnlyList<object?>> EqualityComparer { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -47,23 +47,22 @@ 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 IReadOnlyList<object?>? key)
{
key = new object[Properties.Count];
var index = 0;

foreach (var property in Properties)
var keyArray = new object[Properties.Count];
for (var i = 0; i < keyArray.Length; i++)
{
var value = valueBuffer[property.GetIndex()];
var value = valueBuffer[Properties[i].GetIndex()];
if (value == null)
{
key = null;
return false;
}

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

key = keyArray;
return true;
}

Expand All @@ -73,7 +72,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 IReadOnlyList<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetCurrentValue(p), out key);

/// <summary>
Expand All @@ -82,7 +81,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 IReadOnlyList<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetPreStoreGeneratedCurrentValue(p), out key);

/// <summary>
Expand All @@ -91,7 +90,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 IReadOnlyList<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetOriginalValue(p), out key);

/// <summary>
Expand All @@ -100,7 +99,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 IReadOnlyList<object?>? key)
=> TryCreateFromEntry(entry, (e, p) => e.GetRelationshipSnapshotValue(p), out key);

/// <summary>
Expand All @@ -112,23 +111,22 @@ 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 IReadOnlyList<object?>? key)
{
key = new object[Properties.Count];
var index = 0;

foreach (var property in Properties)
var keyArray = new object[Properties.Count];
for (var i = 0; i < keyArray.Length; i++)
{
var value = getValue(entry, property);
var value = getValue(entry, Properties[i]);
if (value == null)
{
key = null;
return false;
}

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

key = keyArray;
return true;
}

Expand Down Expand Up @@ -156,21 +154,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<IReadOnlyList<object?>> CreateEqualityComparer(IReadOnlyList<IProperty> properties)
=> new CompositeCustomComparer(properties.Select(p => p.GetKeyValueComparer()).ToList());

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

public CompositeCustomComparer(IList<ValueComparer> comparers)
{
_equals = comparers.Select(c => (Func<object, object, bool>)c.Equals).ToArray();
_hashCodes = comparers.Select(c => (Func<object, int>)c.GetHashCode).ToArray();
_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(IReadOnlyList<object?>? x, IReadOnlyList<object?>? y)
{
if (ReferenceEquals(x, y))
{
Expand All @@ -187,12 +187,13 @@ public bool Equals(object[]? x, object[]? y)
return false;
}

if (x.Length != y.Length)
if (x.Count != _valueCount
|| y.Count != _valueCount)
{
return false;
}

for (var i = 0; i < x.Length; i++)
for (var i = 0; i < _valueCount; i++)
{
if (!_equals[i](x[i], y[i]))
{
Expand All @@ -203,13 +204,13 @@ public bool Equals(object[]? x, object[]? y)
return true;
}

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

// ReSharper disable once ForCanBeConvertedToForeach
// ReSharper disable once LoopCanBeConvertedToQuery
for (var i = 0; i < obj.Length; i++)
for (var i = 0; i < obj.Count; i++)
{
hashCode.Add(_hashCodes[i](obj[i]));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public abstract class DependentKeyValueFactory<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 DependentKeyValueFactory(
protected DependentKeyValueFactory(
IForeignKey foreignKey,
IPrincipalKeyValueFactory<TKey> principalKeyValueFactory)
{
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<IReadOnlyList<object?>> CreateComposite(
IForeignKey foreignKey,
IPrincipalKeyValueFactory<object[]> principalKeyValueFactory)
IPrincipalKeyValueFactory<IReadOnlyList<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(IReadOnlyList<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
Loading