diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs index aa4589d54b8..d08337b4b6a 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs @@ -15,12 +15,21 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal; public sealed class StringDictionaryComparer : ValueComparer, IInfrastructure { private static readonly MethodInfo CompareMethod = typeof(StringDictionaryComparer).GetMethod( + nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(Func)])!; + + private static readonly MethodInfo LegacyCompareMethod = typeof(StringDictionaryComparer).GetMethod( nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(ValueComparer)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(StringDictionaryComparer).GetMethod( + nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(Func)])!; + + private static readonly MethodInfo LegacyGetHashCodeMethod = typeof(StringDictionaryComparer).GetMethod( nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; private static readonly MethodInfo SnapshotMethod = typeof(StringDictionaryComparer).GetMethod( + nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(Func)])!; + + private static readonly MethodInfo LegacySnapshotMethod = typeof(StringDictionaryComparer).GetMethod( nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(ValueComparer)])!; /// @@ -52,46 +61,134 @@ ValueComparer IInfrastructure.Instance var prm1 = Expression.Parameter(typeof(object), "a"); var prm2 = Expression.Parameter(typeof(object), "b"); - return Expression.Lambda>( - Expression.Call( - CompareMethod, + if (elementComparer is ValueComparer) + { + // (a, b) => Compare(a, b, elementComparer.Equals, elementComparer.Type) + return Expression.Lambda>( + Expression.Call( + CompareMethod, + prm1, + prm2, + elementComparer.EqualsExpression), prm1, - prm2, + prm2); + } + else + { + // (a, b) => Compare(a, b, new Comparer(...)) + return Expression.Lambda>( + Expression.Call( + LegacyCompareMethod, + prm1, + prm2, #pragma warning disable EF9100 - elementComparer.ConstructorExpression), + elementComparer.ConstructorExpression), #pragma warning restore EF9100 - prm1, - prm2); + prm1, + prm2); + } } private static Expression> GetHashCodeLambda(ValueComparer elementComparer) { var prm = Expression.Parameter(typeof(object), "o"); - return Expression.Lambda>( - Expression.Call( - GetHashCodeMethod, - Expression.Convert( - prm, - typeof(IEnumerable)), + if (elementComparer is ValueComparer) + { + // o => GetHashCode((IEnumerable)o, elementComparer.GetHashCode) + return Expression.Lambda>( + Expression.Call( + GetHashCodeMethod, + Expression.Convert( + prm, + typeof(IEnumerable)), + elementComparer.HashCodeExpression), + prm); + } + else + { + // o => GetHashCode((IEnumerable)o, new Comparer(...)) + return Expression.Lambda>( + Expression.Call( + LegacyGetHashCodeMethod, + Expression.Convert( + prm, + typeof(IEnumerable)), #pragma warning disable EF9100 - elementComparer.ConstructorExpression), + elementComparer.ConstructorExpression), #pragma warning restore EF9100 - prm); + prm); + } } private static Expression> SnapshotLambda(ValueComparer elementComparer) { var prm = Expression.Parameter(typeof(object), "source"); - return Expression.Lambda>( - Expression.Call( - SnapshotMethod, - prm, + if (elementComparer is ValueComparer) + { + // source => Snapshot(source, elementComparer.Snapshot, elementComparer.Type) + return Expression.Lambda>( + Expression.Call( + SnapshotMethod, + prm, + elementComparer.SnapshotExpression), + prm); + } + else + { + // source => Snapshot(source, new Comparer(..)) + return Expression.Lambda>( + Expression.Call( + LegacySnapshotMethod, + prm, #pragma warning disable EF9100 - elementComparer.ConstructorExpression), + elementComparer.ConstructorExpression), #pragma warning restore EF9100 - prm); + prm); + } + } + + private static bool Compare(object? a, object? b, Func elementCompare) + { + if (ReferenceEquals(a, b)) + { + return true; + } + + if (a is null) + { + return b is null; + } + + if (b is null) + { + return false; + } + + if (a is IReadOnlyDictionary aDictionary && b is IReadOnlyDictionary bDictionary) + { + if (aDictionary.Count != bDictionary.Count) + { + return false; + } + + foreach (var pair in aDictionary) + { + if (!bDictionary.TryGetValue(pair.Key, out var bValue) + || !elementCompare(pair.Value, bValue)) + { + return false; + } + } + + return true; + } + + throw new InvalidOperationException( + CosmosStrings.BadDictionaryType( + (a is IDictionary ? b : a).GetType().ShortDisplayName(), + typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName())); } private static bool Compare(object? a, object? b, ValueComparer elementComparer) @@ -136,6 +233,27 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) typeof(IDictionary<,>).MakeGenericType(typeof(string), elementComparer.Type).ShortDisplayName())); } + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) + { + if (source is not IReadOnlyDictionary sourceDictionary) + { + throw new InvalidOperationException( + CosmosStrings.BadDictionaryType( + source.GetType().ShortDisplayName(), + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); + } + + var hash = new HashCode(); + + foreach (var pair in sourceDictionary) + { + hash.Add(pair.Key); + hash.Add(pair.Value == null ? 0 : elementGetHashCode(pair.Value)); + } + + return hash.ToHashCode(); + } + private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) { if (source is not IReadOnlyDictionary sourceDictionary) @@ -157,6 +275,25 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer return hash.ToHashCode(); } + private static IReadOnlyDictionary Snapshot(object source, Func elementSnapshot) + { + if (source is not IReadOnlyDictionary sourceDictionary) + { + throw new InvalidOperationException( + CosmosStrings.BadDictionaryType( + source.GetType().ShortDisplayName(), + typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName())); + } + + var snapshot = new Dictionary(); + foreach (var pair in sourceDictionary) + { + snapshot[pair.Key] = pair.Value == null ? default : (TElement?)elementSnapshot(pair.Value); + } + + return snapshot; + } + private static IReadOnlyDictionary Snapshot(object source, ValueComparer elementComparer) { if (source is not IReadOnlyDictionary sourceDictionary) diff --git a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs index d95a40877db..ca5eb4fb32f 100644 --- a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs @@ -33,15 +33,15 @@ public sealed class ListOfNullableValueTypesComparer : private static readonly MethodInfo CompareMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(Func)])!; /// /// Creates a new instance of the list comparer. @@ -67,15 +67,13 @@ ValueComparer IInfrastructure.Instance var prm1 = Expression.Parameter(typeof(IEnumerable), "a"); var prm2 = Expression.Parameter(typeof(IEnumerable), "b"); - //(a, b) => Compare(a, b, (ValueComparer)elementComparer) + //(a, b) => Compare(a, b, elementComparer.Equals) return Expression.Lambda?, IEnumerable?, bool>>( Expression.Call( CompareMethod, prm1, prm2, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.EqualsExpression), prm1, prm2); } @@ -84,14 +82,12 @@ ValueComparer IInfrastructure.Instance { var prm = Expression.Parameter(typeof(IEnumerable), "o"); - //o => GetHashCode(o, (ValueComparer)elementComparer) + //o => GetHashCode(o, elementComparer.GetHashCode) return Expression.Lambda, int>>( Expression.Call( GetHashCodeMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.HashCodeExpression), prm); } @@ -99,18 +95,16 @@ ValueComparer IInfrastructure.Instance { var prm = Expression.Parameter(typeof(IEnumerable), "source"); - //source => Snapshot(source, (ValueComparer)elementComparer) + //source => Snapshot(source, elementComparer.Snapshot) return Expression.Lambda, IEnumerable>>( Expression.Call( SnapshotMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.SnapshotExpression), prm); } - private static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) + private static bool Compare(IEnumerable? a, IEnumerable? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -152,7 +146,7 @@ private static bool Compare(IEnumerable? a, IEnumerable? b return false; } - if (!elementComparer.Equals(el1, el2)) + if (!elementCompare(el1, el2)) { return false; } @@ -164,29 +158,29 @@ private static bool Compare(IEnumerable? a, IEnumerable? b throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) { var hash = new HashCode(); foreach (var el in source) { - hash.Add(el == null ? 0 : elementComparer.GetHashCode(el)); + hash.Add(el == null ? 0 : elementGetHashCode(el)); } return hash.ToHashCode(); } - private static IList Snapshot(IEnumerable source, ValueComparer elementComparer) + private static IList Snapshot(IEnumerable source, Func elementSnapshot) { if (source is not IList sourceList) { throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } if (IsArray) @@ -195,7 +189,7 @@ private static int GetHashCode(IEnumerable source, ValueComparer source, ValueComparer() : (IList)Activator.CreateInstance()!; foreach (var e in sourceList) { - snapshot.Add(e == null ? null : elementComparer.Snapshot(e)); + snapshot.Add(e == null ? null : elementSnapshot(e)); } return IsReadOnly diff --git a/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs index e0e77528d87..2938e0d94d2 100644 --- a/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs @@ -30,12 +30,21 @@ public sealed class ListOfReferenceTypesComparer : Valu && typeof(TConcreteList).GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); private static readonly MethodInfo CompareMethod = typeof(ListOfReferenceTypesComparer).GetMethod( + nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(Func)])!; + + private static readonly MethodInfo LegacyCompareMethod = typeof(ListOfReferenceTypesComparer).GetMethod( nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(ValueComparer)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfReferenceTypesComparer).GetMethod( + nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(Func)])!; + + private static readonly MethodInfo LegacyGetHashCodeMethod = typeof(ListOfReferenceTypesComparer).GetMethod( nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfReferenceTypesComparer).GetMethod( + nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(Func)])!; + + private static readonly MethodInfo LegacySnapshotMethod = typeof(ListOfReferenceTypesComparer).GetMethod( nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(ValueComparer)])!; /// @@ -62,43 +71,143 @@ ValueComparer IInfrastructure.Instance var prm1 = Expression.Parameter(typeof(object), "a"); var prm2 = Expression.Parameter(typeof(object), "b"); - // (a, b) => Compare(a, b, elementComparer) - return Expression.Lambda>( - Expression.Call( - CompareMethod, + if (elementComparer is ValueComparer) + { + // (a, b) => Compare(a, b, elementComparer.Equals, elementComparer.Type) + return Expression.Lambda>( + Expression.Call( + CompareMethod, + prm1, + prm2, + elementComparer.EqualsExpression), + prm1, + prm2); + } + else + { + // (a, b) => Compare(a, b, new Comparer(...)) + return Expression.Lambda>( + Expression.Call( + LegacyCompareMethod, + prm1, + prm2, + elementComparer.ConstructorExpression), prm1, - prm2, - elementComparer.ConstructorExpression), - prm1, - prm2); + prm2); + } } private static Expression> GetHashCodeLambda(ValueComparer elementComparer) { var prm = Expression.Parameter(typeof(object), "o"); - //o => GetHashCode((IEnumerable)o, elementComparer) - return Expression.Lambda>( - Expression.Call( - GetHashCodeMethod, - Expression.Convert( - prm, - typeof(IEnumerable)), - elementComparer.ConstructorExpression), - prm); + if (elementComparer is ValueComparer) + { + // o => GetHashCode((IEnumerable)o, elementComparer.GetHashCode) + return Expression.Lambda>( + Expression.Call( + GetHashCodeMethod, + Expression.Convert( + prm, + typeof(IEnumerable)), + elementComparer.HashCodeExpression), + prm); + } + else + { + // o => GetHashCode((IEnumerable)o, new Comparer(...)) + return Expression.Lambda>( + Expression.Call( + LegacyGetHashCodeMethod, + Expression.Convert( + prm, + typeof(IEnumerable)), + elementComparer.ConstructorExpression), + prm); + } } private static Expression> SnapshotLambda(ValueComparer elementComparer) { var prm = Expression.Parameter(typeof(object), "source"); - //source => Snapshot(source, elementComparer) - return Expression.Lambda>( - Expression.Call( - SnapshotMethod, - prm, - elementComparer.ConstructorExpression), - prm); + if (elementComparer is ValueComparer) + { + // source => Snapshot(source, elementComparer.Snapshot, elementComparer.Type) + return Expression.Lambda>( + Expression.Call( + SnapshotMethod, + prm, + elementComparer.SnapshotExpression), + prm); + } + else + { + // source => Snapshot(source, new Comparer(..)) + return Expression.Lambda>( + Expression.Call( + LegacySnapshotMethod, + prm, + elementComparer.ConstructorExpression), + prm); + } + } + + private static bool Compare(object? a, object? b, Func elementCompare) + { + if (ReferenceEquals(a, b)) + { + return true; + } + + if (a is null) + { + return b is null; + } + + if (b is null) + { + return false; + } + + if (a is IList aList && b is IList 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 (!elementCompare(el1, el2)) + { + return false; + } + } + + return true; + } + + throw new InvalidOperationException( + CoreStrings.BadListType( + (a is IList ? b : a).GetType().ShortDisplayName(), + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } private static bool Compare(object? a, object? b, ValueComparer elementComparer) @@ -158,6 +267,18 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); } + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) + { + var hash = new HashCode(); + + foreach (var el in source) + { + hash.Add(el == null ? 0 : elementGetHashCode((TElement)el)); + } + + return hash.ToHashCode(); + } + private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) { var hash = new HashCode(); @@ -170,6 +291,41 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer return hash.ToHashCode(); } + private static IList Snapshot(object source, Func elementSnapshot) + { + if (source is not IList sourceList) + { + throw new InvalidOperationException( + CoreStrings.BadListType( + source.GetType().ShortDisplayName(), + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); + } + + if (IsArray) + { + var snapshot = new TElement?[sourceList.Count]; + for (var i = 0; i < sourceList.Count; i++) + { + var instance = sourceList[i]; + snapshot[i] = instance == null ? null : elementSnapshot(instance); + } + + return snapshot; + } + else + { + var snapshot = IsReadOnly ? new List() : (IList)Activator.CreateInstance()!; + foreach (var e in sourceList) + { + snapshot.Add(e == null ? null : elementSnapshot(e)); + } + + return IsReadOnly + ? (IList)Activator.CreateInstance(typeof(TConcreteList), snapshot)! + : snapshot; + } + } + private static IList Snapshot(object source, ValueComparer elementComparer) { if (source is not IList sourceList) diff --git a/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs index 19a3a8d4a2f..57e8e0f6793 100644 --- a/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs @@ -31,14 +31,14 @@ public sealed class ListOfValueTypesComparer : ValueCom private static readonly MethodInfo CompareMethod = typeof(ListOfValueTypesComparer).GetMethod( nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfValueTypesComparer).GetMethod( nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfValueTypesComparer).GetMethod( - nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(Func)])!; /// /// Creates a new instance of the list comparer. @@ -64,15 +64,13 @@ ValueComparer IInfrastructure.Instance var prm1 = Expression.Parameter(typeof(IEnumerable), "a"); var prm2 = Expression.Parameter(typeof(IEnumerable), "b"); - //(a, b) => Compare(a, b, (ValueComparer)elementComparer) + //(a, b) => Compare(a, b, elementComparer.Equals) return Expression.Lambda?, IEnumerable?, bool>>( Expression.Call( CompareMethod, prm1, prm2, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.EqualsExpression), prm1, prm2); } @@ -81,14 +79,12 @@ private static Expression, int>> GetHashCodeLambda(Va { var prm = Expression.Parameter(typeof(IEnumerable), "o"); - //o => GetHashCode(o, (ValueComparer)elementComparer) + //o => GetHashCode(o, elementComparer.GetHashCode) return Expression.Lambda, int>>( Expression.Call( GetHashCodeMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.HashCodeExpression), prm); } @@ -96,18 +92,16 @@ private static Expression, IEnumerable>> Sn { var prm = Expression.Parameter(typeof(IEnumerable), "source"); - //source => Snapshot(source, (ValueComparer)elementComparer) + //source => Snapshot(source, elementComparer.SnapShot) return Expression.Lambda, IEnumerable>>( Expression.Call( SnapshotMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.SnapshotExpression), prm); } - private static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) + private static bool Compare(IEnumerable? a, IEnumerable? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -134,7 +128,7 @@ private static bool Compare(IEnumerable? a, IEnumerable? b, for (var i = 0; i < aList.Count; i++) { var (el1, el2) = (aList[i], bList[i]); - if (!elementComparer.Equals(el1, el2)) + if (!elementCompare(el1, el2)) { return false; } @@ -146,29 +140,29 @@ private static bool Compare(IEnumerable? a, IEnumerable? b, throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) { var hash = new HashCode(); foreach (var el in source) { - hash.Add(elementComparer.GetHashCode(el)); + hash.Add(elementGetHashCode(el)); } return hash.ToHashCode(); } - private static IList Snapshot(IEnumerable source, ValueComparer elementComparer) + private static IList Snapshot(IEnumerable source, Func elementSnapshot) { if (source is not IList sourceList) { throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement).MakeNullable()).ShortDisplayName())); } if (IsArray) @@ -177,7 +171,7 @@ private static IList Snapshot(IEnumerable source, ValueCompa for (var i = 0; i < sourceList.Count; i++) { var instance = sourceList[i]; - snapshot[i] = elementComparer.Snapshot(instance); + snapshot[i] = elementSnapshot(instance); } return snapshot; @@ -187,7 +181,7 @@ private static IList Snapshot(IEnumerable source, ValueCompa var snapshot = IsReadOnly ? new List() : (IList)Activator.CreateInstance()!; foreach (var e in sourceList) { - snapshot.Add(elementComparer.Snapshot(e)); + snapshot.Add(elementSnapshot(e)); } return IsReadOnly diff --git a/src/EFCore/Storage/TypeMappingSourceBase.cs b/src/EFCore/Storage/TypeMappingSourceBase.cs index b38c0e7462e..4286bd7b950 100644 --- a/src/EFCore/Storage/TypeMappingSourceBase.cs +++ b/src/EFCore/Storage/TypeMappingSourceBase.cs @@ -150,7 +150,9 @@ protected virtual bool TryFindJsonCollectionMapping( { elementMapping ??= FindMapping(elementType); - if (elementMapping is { JsonValueReaderWriter: not null }) + // don't generate mapping for nested collections (i.e. where ElementTypeMapping is not null already) + // they are not supported and we can only get into trouble tryong to generate comparers - see #35239 + if (elementMapping is { JsonValueReaderWriter: not null, ElementTypeMapping: null }) { var elementReader = elementMapping.JsonValueReaderWriter!;