-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Collection change tracking for primitive collections #31480
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace Microsoft.EntityFrameworkCore.ChangeTracking; | ||
|
||
/// <summary> | ||
/// A <see cref="ValueComparer{T}"/> for lists of primitive items. The list can be typed as <see cref="IEnumerable{T}"/>, | ||
/// but can only be used with instances that implement <see cref="IList{T}"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para> | ||
/// This comparer should be used for reference types and non-nullable value types. Use | ||
/// <see cref="NullableValueTypeListComparer{TElement}"/> for nullable value types. | ||
/// </para> | ||
/// <para> | ||
/// See <see href="https://aka.ms/efcore-docs-value-comparers">EF Core value comparers</see> for more information and examples. | ||
/// </para> | ||
/// </remarks> | ||
/// <typeparam name="TElement">The element type.</typeparam> | ||
public sealed class ListComparer<TElement> : ValueComparer<IEnumerable<TElement>> | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of the list comparer. | ||
/// </summary> | ||
/// <param name="elementComparer">The comparer to use for comparing elements.</param> | ||
public ListComparer(ValueComparer<TElement> elementComparer) | ||
: base( | ||
(a, b) => Compare(a, b, elementComparer), | ||
o => GetHashCode(o, elementComparer), | ||
source => Snapshot(source, elementComparer)) | ||
{ | ||
} | ||
|
||
private static bool Compare(IEnumerable<TElement>? a, IEnumerable<TElement>? b, ValueComparer<TElement> elementComparer) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be compatible with compiled models these methods need to be public. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, they shouldn't use captured variables. Instead, in-line the element comparer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's leave this for RC2. I can see it fails with compiled models, but I can't see the path forward where we can get the element comparer without capturing it in some way. Will file an issue. We can discuss.
|
||
{ | ||
if (ReferenceEquals(a, b)) | ||
{ | ||
return true; | ||
} | ||
|
||
if (a is null) | ||
{ | ||
return b is null; | ||
} | ||
|
||
if (b is null) | ||
{ | ||
return false; | ||
} | ||
|
||
if (a is IList<TElement> aList && b is IList<TElement> bList) | ||
{ | ||
if (aList.Count != bList.Count) | ||
{ | ||
return false; | ||
} | ||
|
||
for (var i = 0; i < aList.Count; i++) | ||
{ | ||
var (el1, el2) = (aList[i], bList[i]); | ||
if (el1 is null) | ||
{ | ||
if (el2 is null) | ||
{ | ||
continue; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
if (el2 is null) | ||
{ | ||
return false; | ||
} | ||
|
||
if (!elementComparer.Equals(el1, el2)) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
throw new InvalidOperationException( | ||
CoreStrings.BadListType( | ||
(a is IList<TElement?> ? b : a).GetType().ShortDisplayName(), | ||
typeof(ListComparer<TElement?>).ShortDisplayName(), | ||
typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); | ||
} | ||
|
||
private static int GetHashCode(IEnumerable<TElement> source, ValueComparer<TElement> elementComparer) | ||
{ | ||
var hash = new HashCode(); | ||
|
||
foreach (var el in source) | ||
{ | ||
hash.Add(el == null ? 0 : elementComparer.GetHashCode(el)); | ||
} | ||
|
||
return hash.ToHashCode(); | ||
} | ||
|
||
private static IList<TElement> Snapshot(IEnumerable<TElement> source, ValueComparer<TElement> elementComparer) | ||
{ | ||
if (!(source is IList<TElement> sourceList)) | ||
{ | ||
throw new InvalidOperationException( | ||
CoreStrings.BadListType( | ||
source.GetType().ShortDisplayName(), | ||
typeof(ListComparer<TElement?>).ShortDisplayName(), | ||
typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); | ||
} | ||
|
||
if (sourceList.IsReadOnly) | ||
{ | ||
var snapshot = new TElement[sourceList.Count]; | ||
|
||
for (var i = 0; i < sourceList.Count; i++) | ||
{ | ||
var instance = sourceList[i]; | ||
if (instance != null) | ||
{ | ||
snapshot[i] = elementComparer.Snapshot(instance); | ||
} | ||
} | ||
|
||
return snapshot; | ||
} | ||
else | ||
{ | ||
var snapshot = (source is List<TElement> || sourceList.IsReadOnly) | ||
? new List<TElement>(sourceList.Count) | ||
: (IList<TElement>)Activator.CreateInstance(source.GetType())!; | ||
|
||
foreach (var e in sourceList) | ||
{ | ||
snapshot.Add(e == null ? (TElement)(object)null! : elementComparer.Snapshot(e)); | ||
} | ||
|
||
return snapshot; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace Microsoft.EntityFrameworkCore.ChangeTracking; | ||
|
||
/// <summary> | ||
/// A <see cref="ValueComparer{T}"/> for lists of primitive items. The list can be typed as <see cref="IEnumerable{T}"/>, | ||
/// but can only be used with instances that implement <see cref="IList{T}"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para> | ||
/// This comparer should be used for nullable value types. Use <see cref="NullableValueTypeListComparer{TElement}"/> for reference | ||
/// types and non-nullable value types. | ||
/// </para> | ||
/// <para> | ||
/// See <see href="https://aka.ms/efcore-docs-value-comparers">EF Core value comparers</see> for more information and examples. | ||
/// </para> | ||
/// </remarks> | ||
/// <typeparam name="TElement">The element type.</typeparam> | ||
public sealed class NullableValueTypeListComparer<TElement> : ValueComparer<IEnumerable<TElement?>> | ||
where TElement : struct | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of the list comparer. | ||
/// </summary> | ||
/// <param name="elementComparer">The comparer to use for comparing elements.</param> | ||
public NullableValueTypeListComparer(ValueComparer<TElement> elementComparer) | ||
: base( | ||
(a, b) => Compare(a, b, elementComparer), | ||
o => GetHashCode(o, elementComparer), | ||
source => Snapshot(source, elementComparer)) | ||
{ | ||
} | ||
|
||
private static bool Compare(IEnumerable<TElement?>? a, IEnumerable<TElement?>? b, ValueComparer<TElement> elementComparer) | ||
{ | ||
if (ReferenceEquals(a, b)) | ||
{ | ||
return true; | ||
} | ||
|
||
if (a is null) | ||
{ | ||
return b is null; | ||
} | ||
|
||
if (b is null) | ||
{ | ||
return false; | ||
} | ||
|
||
if (a is IList<TElement?> aList && b is IList<TElement?> bList) | ||
{ | ||
if (aList.Count != bList.Count) | ||
{ | ||
return false; | ||
} | ||
|
||
for (var i = 0; i < aList.Count; i++) | ||
{ | ||
var (el1, el2) = (aList[i], bList[i]); | ||
if (el1 is null) | ||
{ | ||
if (el2 is null) | ||
{ | ||
continue; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
if (el2 is null) | ||
{ | ||
return false; | ||
} | ||
|
||
if (!elementComparer.Equals(el1, el2)) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
throw new InvalidOperationException( | ||
CoreStrings.BadListType( | ||
(a is IList<TElement?> ? b : a).GetType().ShortDisplayName(), | ||
typeof(NullableValueTypeListComparer<TElement>).ShortDisplayName(), | ||
typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); | ||
} | ||
|
||
private static int GetHashCode(IEnumerable<TElement?> source, ValueComparer<TElement> elementComparer) | ||
{ | ||
var hash = new HashCode(); | ||
|
||
foreach (var el in source) | ||
{ | ||
hash.Add(el == null ? 0 : elementComparer.GetHashCode(el)); | ||
} | ||
|
||
return hash.ToHashCode(); | ||
} | ||
|
||
private static IList<TElement?> Snapshot(IEnumerable<TElement?> source, ValueComparer<TElement> elementComparer) | ||
{ | ||
if (!(source is IList<TElement?> sourceList)) | ||
{ | ||
throw new InvalidOperationException( | ||
CoreStrings.BadListType( | ||
source.GetType().ShortDisplayName(), | ||
typeof(NullableValueTypeListComparer<TElement>).ShortDisplayName(), | ||
typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); | ||
} | ||
|
||
if (sourceList.IsReadOnly) | ||
{ | ||
var snapshot = new TElement?[sourceList.Count]; | ||
|
||
for (var i = 0; i < sourceList.Count; i++) | ||
{ | ||
var instance = sourceList[i]; | ||
snapshot[i] = instance == null ? null : (TElement?)elementComparer.Snapshot(instance); | ||
} | ||
|
||
return snapshot; | ||
} | ||
else | ||
{ | ||
var snapshot = source is List<TElement?> || sourceList.IsReadOnly | ||
? new List<TElement?>(sourceList.Count) | ||
: (IList<TElement?>)Activator.CreateInstance(source.GetType())!; | ||
|
||
foreach (var e in sourceList) | ||
{ | ||
snapshot.Add(e == null ? null : (TElement?)elementComparer.Snapshot(e)); | ||
} | ||
|
||
return snapshot; | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this replace the equivalent one in Cosmos?