diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs index aa4589d54b8..408c250627d 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs @@ -15,13 +15,13 @@ 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(ValueComparer)])!; + nameof(Compare), BindingFlags.Static | BindingFlags.Public, [typeof(object), typeof(object), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(StringDictionaryComparer).GetMethod( - nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(GetHashCode), BindingFlags.Static | BindingFlags.Public, [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(StringDictionaryComparer).GetMethod( - nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(ValueComparer)])!; + nameof(Snapshot), BindingFlags.Static | BindingFlags.Public, [typeof(object), typeof(Func)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -57,9 +57,7 @@ ValueComparer IInfrastructure.Instance CompareMethod, prm1, prm2, -#pragma warning disable EF9100 - elementComparer.ConstructorExpression), -#pragma warning restore EF9100 + elementComparer.EqualsExpression), prm1, prm2); } @@ -74,9 +72,7 @@ private static Expression> GetHashCodeLambda(ValueComparer ele Expression.Convert( prm, typeof(IEnumerable)), -#pragma warning disable EF9100 - elementComparer.ConstructorExpression), -#pragma warning restore EF9100 + elementComparer.HashCodeExpression), prm); } @@ -88,13 +84,11 @@ private static Expression> SnapshotLambda(ValueComparer ele Expression.Call( SnapshotMethod, prm, -#pragma warning disable EF9100 - elementComparer.ConstructorExpression), -#pragma warning restore EF9100 + elementComparer.SnapshotExpression), prm); } - private static bool Compare(object? a, object? b, ValueComparer elementComparer) + public static bool Compare(object? a, object? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -121,7 +115,7 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) foreach (var pair in aDictionary) { if (!bDictionary.TryGetValue(pair.Key, out var bValue) - || !elementComparer.Equals(pair.Value, bValue)) + || !elementCompare(pair.Value, bValue)) { return false; } @@ -133,17 +127,17 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) throw new InvalidOperationException( CosmosStrings.BadDictionaryType( (a is IDictionary ? b : a).GetType().ShortDisplayName(), - typeof(IDictionary<,>).MakeGenericType(typeof(string), elementComparer.Type).ShortDisplayName())); + typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + public static int GetHashCode(IEnumerable source, Func elementGetHashCode) { if (source is not IReadOnlyDictionary sourceDictionary) { throw new InvalidOperationException( CosmosStrings.BadDictionaryType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } var hash = new HashCode(); @@ -151,26 +145,26 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer foreach (var pair in sourceDictionary) { hash.Add(pair.Key); - hash.Add(pair.Value == null ? 0 : elementComparer.GetHashCode(pair.Value)); + hash.Add(pair.Value == null ? 0 : elementGetHashCode(pair.Value)); } return hash.ToHashCode(); } - private static IReadOnlyDictionary Snapshot(object source, ValueComparer elementComparer) + public 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), elementComparer.Type).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?)elementComparer.Snapshot(pair.Value); + snapshot[pair.Key] = pair.Value == null ? default : (TElement?)elementSnapshot(pair.Value); } return snapshot; diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs index 25f36155528..253a7489bca 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; @@ -191,10 +192,10 @@ private static ValueComparer CreateStringDictionaryComparer( var unwrappedType = elementType.UnwrapNullableType(); return (ValueComparer)Activator.CreateInstance( - elementType == unwrappedType - ? typeof(StringDictionaryComparer<,>).MakeGenericType(dictType, elementType) - : typeof(NullableStringDictionaryComparer<,>).MakeGenericType(unwrappedType, dictType), - elementMapping.Comparer)!; + typeof(StringDictionaryComparer<,>).MakeGenericType(dictType, elementType), +#pragma warning disable EF1001 // Internal EF Core API usage. + elementMapping.Comparer.ComposeConversion(elementType))!; +#pragma warning restore EF1001 // Internal EF Core API usage. } // This ensures that the element reader/writers are not null when using Cosmos dictionary type mappings, but diff --git a/src/EFCore/ChangeTracking/Internal/ConvertingValueComparer.cs b/src/EFCore/ChangeTracking/Internal/ConvertingValueComparer.cs new file mode 100644 index 00000000000..f5b58f0e265 --- /dev/null +++ b/src/EFCore/ChangeTracking/Internal/ConvertingValueComparer.cs @@ -0,0 +1,85 @@ +// 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.Internal; + +using static Expression; + +/// +/// A composable value comparer that accepts a value comparer, and exposes it as a value comparer for a base type. +/// Used when a collection comparer over e.g. object[] is needed over a specific element type (e.g. int) +/// +/// +/// 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. +/// +public class ConvertingValueComparer : ValueComparer, IInfrastructure +{ + private readonly ValueComparer _valueComparer; + + /// + /// 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. + /// + public ConvertingValueComparer(ValueComparer valueComparer) + : base( + CreateEquals(valueComparer), + CreateHashCode(valueComparer), + CreateSnapshot(valueComparer)) + => _valueComparer = valueComparer; + + private static Expression> CreateEquals(ValueComparer valueComparer) + { + var p1 = Parameter(typeof(TTo), "v1"); + var p2 = Parameter(typeof(TTo), "v2"); + + var body = typeof(TTo).IsAssignableFrom(typeof(TFrom)) + ? valueComparer.EqualsExpression.Body + : valueComparer.ExtractEqualsBody( + Convert(p1, typeof(TFrom)), + Convert(p2, typeof(TFrom))); + + return Lambda>( + body, + p1, + p2); + } + + private static Expression> CreateHashCode(ValueComparer valueComparer) + { + var p = Parameter(typeof(TTo), "v"); + + var body = typeof(TTo).IsAssignableFrom(typeof(TFrom)) + ? valueComparer.HashCodeExpression.Body + : valueComparer.ExtractHashCodeBody( + Convert(p, typeof(TFrom))); + + return Lambda>( + body, + p); + } + + private static Expression> CreateSnapshot(ValueComparer valueComparer) + { + var p = Parameter(typeof(TTo), "v"); + + // types must match exactly as we have both covariance and contravariance case here + var body = typeof(TTo) == typeof(TFrom) + ? valueComparer.SnapshotExpression.Body + : Convert( + valueComparer.ExtractSnapshotBody( + Convert(p, typeof(TFrom))), + typeof(TTo)); + + return Lambda>( + body, + p); + } + + ValueComparer IInfrastructure.Instance + => _valueComparer; +} diff --git a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs index d5a17ac236f..bf199e20f08 100644 --- a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs +++ b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs @@ -25,4 +25,30 @@ public static class ValueComparerExtensions : (ValueComparer)Activator.CreateInstance( typeof(NullableValueComparer<>).MakeGenericType(valueComparer.Type), valueComparer)!; + + /// + /// 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. + /// + public static ValueComparer? ComposeConversion(this ValueComparer? valueComparer, Type targetClrType) + { + if (valueComparer is null || valueComparer.Type == targetClrType) + { + return valueComparer; + } + + if (targetClrType.IsNullableValueType() && !valueComparer.Type.IsNullableValueType() + && targetClrType.UnwrapNullableType() == valueComparer.Type) + { + return (ValueComparer)Activator.CreateInstance( + typeof(NullableValueComparer<>).MakeGenericType(valueComparer.Type), + valueComparer)!; + } + + return (ValueComparer)Activator.CreateInstance( + typeof(ConvertingValueComparer<,>).MakeGenericType(targetClrType, valueComparer.Type), + valueComparer)!; + } } diff --git a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs index d95a40877db..90931a17b95 100644 --- a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs @@ -32,16 +32,16 @@ public sealed class ListOfNullableValueTypesComparer : && typeof(TConcreteList).GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); private static readonly MethodInfo CompareMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( - nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(Compare), BindingFlags.Static | BindingFlags.Public, + [typeof(IEnumerable), typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( - nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(GetHashCode), BindingFlags.Static | BindingFlags.Public, + [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( - nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(Snapshot), BindingFlags.Static | BindingFlags.Public, + [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) + public 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).MakeNullable()).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + public 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) + public 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) @@ -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..965775070d8 100644 --- a/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs @@ -30,13 +30,13 @@ 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(ValueComparer)])!; + nameof(Compare), BindingFlags.Static | BindingFlags.Public, [typeof(object), typeof(object), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfReferenceTypesComparer).GetMethod( - nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(GetHashCode), BindingFlags.Static | BindingFlags.Public, [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfReferenceTypesComparer).GetMethod( - nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(ValueComparer)])!; + nameof(Snapshot), BindingFlags.Static | BindingFlags.Public, [typeof(object), typeof(Func)])!; /// /// Creates a new instance of the list comparer. @@ -62,13 +62,12 @@ 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, prm1, prm2, - elementComparer.ConstructorExpression), + elementComparer.EqualsExpression), prm1, prm2); } @@ -77,14 +76,13 @@ private static Expression> GetHashCodeLambda(ValueComparer ele { 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), + elementComparer.HashCodeExpression), prm); } @@ -92,16 +90,15 @@ private static Expression> SnapshotLambda(ValueComparer ele { var prm = Expression.Parameter(typeof(object), "source"); - //source => Snapshot(source, elementComparer) return Expression.Lambda>( Expression.Call( SnapshotMethod, prm, - elementComparer.ConstructorExpression), + elementComparer.SnapshotExpression), prm); } - private static bool Compare(object? a, object? b, ValueComparer elementComparer) + public static bool Compare(object? a, object? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -143,7 +140,7 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) return false; } - if (!elementComparer.Equals(el1, el2)) + if (!elementCompare(el1, el2)) { return false; } @@ -155,29 +152,29 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) 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) + public 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((TElement?)el)); } return hash.ToHashCode(); } - private static IList Snapshot(object source, ValueComparer elementComparer) + public static IList Snapshot(object source, Func elementSnapshot) { if (source is not IList sourceList) { throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } if (IsArray) @@ -186,7 +183,7 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer for (var i = 0; i < sourceList.Count; i++) { var instance = sourceList[i]; - snapshot[i] = instance == null ? null : (TElement?)elementComparer.Snapshot(instance); + snapshot[i] = instance == null ? null : elementSnapshot(instance); } return snapshot; @@ -196,7 +193,7 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer var snapshot = IsReadOnly ? new List() : (IList)Activator.CreateInstance()!; foreach (var e in sourceList) { - snapshot.Add(e == null ? null : (TElement?)elementComparer.Snapshot(e)); + snapshot.Add(e == null ? null : elementSnapshot(e)); } return IsReadOnly diff --git a/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs index 19a3a8d4a2f..ed9fbc1caa7 100644 --- a/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs @@ -30,15 +30,15 @@ public sealed class ListOfValueTypesComparer : ValueCom && typeof(TConcreteList).GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); private static readonly MethodInfo CompareMethod = typeof(ListOfValueTypesComparer).GetMethod( - nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(Compare), BindingFlags.Static | BindingFlags.Public, + [typeof(IEnumerable), typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfValueTypesComparer).GetMethod( - nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(GetHashCode), BindingFlags.Static | BindingFlags.Public, + [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.Public, [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) + public 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) + public 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) + public 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..5474e119f7a 100644 --- a/src/EFCore/Storage/TypeMappingSourceBase.cs +++ b/src/EFCore/Storage/TypeMappingSourceBase.cs @@ -179,7 +179,7 @@ protected virtual bool TryFindJsonCollectionMapping( : elementType.IsValueType ? typeof(ListOfValueTypesComparer<,>).MakeGenericType(typeToInstantiate, elementType) : typeof(ListOfReferenceTypesComparer<,>).MakeGenericType(typeToInstantiate, elementType), - elementMapping.Comparer.ToNullableComparer(elementType)!); + elementMapping.Comparer.ComposeConversion(elementType)!); return true; diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs index 09e7d8fa59c..b68e3df3cff 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs @@ -212,10 +212,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); list.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer>, Dictionary>(new StringDictionaryComparer, int>(new ValueComparer( - bool (int v1, int v2) => v1 == v2, - int (int v) => v, - int (int v) => v))), + comparer: new ListOfReferenceTypesComparer>, Dictionary>(new ValueComparer>( + bool (Dictionary v1, Dictionary v2) => StringDictionaryComparer, int>.Compare(((object)(v1)), ((object)(v2)), bool (int v1, int v2) => v1 == v2), + int (Dictionary v) => StringDictionaryComparer, int>.GetHashCode(((IEnumerable)(((object)(v)))), int (int v) => v), + Dictionary (Dictionary v) => ((Dictionary)(StringDictionaryComparer, int>.Snapshot(((object)(v)), int (int v) => v))))), keyComparer: new ValueComparer>>( bool (List> v1, List> v2) => object.Equals(v1, v2), int (List> v) => ((object)v).GetHashCode(), @@ -262,10 +262,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); map.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new StringDictionaryComparer, string[]>(new ListOfReferenceTypesComparer(new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v))), + comparer: new StringDictionaryComparer, string[]>(new ValueComparer( + bool (string[] v1, string[] v2) => ListOfReferenceTypesComparer.Compare(((object)(v1)), ((object)(v2)), bool (string v1, string v2) => v1 == v2), + int (string[] v) => ListOfReferenceTypesComparer.GetHashCode(((IEnumerable)(((object)(v)))), int (string v) => ((object)v).GetHashCode()), + string[] (string[] v) => ((string[])(ListOfReferenceTypesComparer.Snapshot(((object)(v)), string (string v) => v))))), keyComparer: new ValueComparer>( bool (Dictionary v1, Dictionary v2) => object.Equals(v1, v2), int (Dictionary v) => ((object)v).GetHashCode(), diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs index 7deabfede83..19d94ae2936 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs @@ -256,10 +256,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); boolNestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfValueTypesComparer(new ValueComparer( - bool (bool v1, bool v2) => v1 == v2, - int (bool v) => ((object)v).GetHashCode(), - bool (bool v) => v))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (bool[] v1, bool[] v2) => ListOfValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (bool v1, bool v2) => v1 == v2), + int (bool[] v) => ListOfValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (bool v) => ((object)v).GetHashCode()), + bool[] (bool[] v) => ((bool[])(ListOfValueTypesComparer.Snapshot(((IEnumerable)(v)), bool (bool v) => v))))), keyComparer: new ValueComparer( bool (bool[][] v1, bool[][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (bool[][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -786,10 +786,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); charNestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfValueTypesComparer(new ValueComparer( - bool (char v1, char v2) => v1 == v2, - int (char v) => ((int)(v)), - char (char v) => v))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (char[] v1, char[] v2) => ListOfValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (char v1, char v2) => v1 == v2), + int (char[] v) => ListOfValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (char v) => ((int)(v))), + char[] (char[] v) => ((char[])(ListOfValueTypesComparer.Snapshot(((IEnumerable)(v)), char (char v) => v))))), keyComparer: new ValueComparer( bool (char[][] v1, char[][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (char[][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -3232,10 +3232,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); int32NestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfValueTypesComparer(new ValueComparer( - bool (int v1, int v2) => v1 == v2, - int (int v) => v, - int (int v) => v))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (int[] v1, int[] v2) => ListOfValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (int v1, int v2) => v1 == v2), + int (int[] v) => ListOfValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (int v) => v), + int[] (int[] v) => ((int[])(ListOfValueTypesComparer.Snapshot(((IEnumerable)(v)), int (int v) => v))))), keyComparer: new ValueComparer( bool (int[][] v1, int[][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (int[][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -3466,10 +3466,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); int64NestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer[], IList>(new ListOfReferenceTypesComparer, long[]>(new ListOfValueTypesComparer(new ValueComparer( - bool (long v1, long v2) => v1 == v2, - int (long v) => ((object)v).GetHashCode(), - long (long v) => v)))), + comparer: new ListOfReferenceTypesComparer[], IList>(new ValueComparer>( + bool (IList v1, IList v2) => ListOfReferenceTypesComparer, long[]>.Compare(((object)(v1)), ((object)(v2)), bool (long[] v1, long[] v2) => ListOfValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (long v1, long v2) => v1 == v2)), + int (IList v) => ListOfReferenceTypesComparer, long[]>.GetHashCode(((IEnumerable)(((object)(v)))), int (long[] v) => ListOfValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (long v) => ((object)v).GetHashCode())), + IList (IList v) => ((IList)(ListOfReferenceTypesComparer, long[]>.Snapshot(((object)(v)), long[] (long[] v) => ((long[])(ListOfValueTypesComparer.Snapshot(((IEnumerable)(v)), long (long v) => v)))))))), keyComparer: new ValueComparer[]>( bool (IList[] v1, IList[] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (IList[] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -3484,10 +3484,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas new JsonCollectionOfStructsReaderWriter( JsonInt64ReaderWriter.Instance))), elementMapping: CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer, long[]>(new ListOfValueTypesComparer(new ValueComparer( - bool (long v1, long v2) => v1 == v2, - int (long v) => ((object)v).GetHashCode(), - long (long v) => v))), + comparer: new ListOfReferenceTypesComparer, long[]>(new ValueComparer( + bool (long[] v1, long[] v2) => ListOfValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (long v1, long v2) => v1 == v2), + int (long[] v) => ListOfValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (long v) => ((object)v).GetHashCode()), + long[] (long[] v) => ((long[])(ListOfValueTypesComparer.Snapshot(((IEnumerable)(v)), long (long v) => v))))), keyComparer: new ValueComparer>( bool (IList v1, IList v2) => object.Equals(v1, v2), int (IList v) => ((object)v).GetHashCode(), @@ -3660,10 +3660,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); int8NestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfReferenceTypesComparer(new ListOfValueTypesComparer(new ValueComparer( - bool (sbyte v1, sbyte v2) => v1 == v2, - int (sbyte v) => ((int)(v)), - sbyte (sbyte v) => v)))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (sbyte[][] v1, sbyte[][] v2) => ListOfReferenceTypesComparer.Compare(((object)(v1)), ((object)(v2)), bool (sbyte[] v1, sbyte[] v2) => ListOfValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (sbyte v1, sbyte v2) => v1 == v2)), + int (sbyte[][] v) => ListOfReferenceTypesComparer.GetHashCode(((IEnumerable)(((object)(v)))), int (sbyte[] v) => ListOfValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (sbyte v) => ((int)(v)))), + sbyte[][] (sbyte[][] v) => ((sbyte[][])(ListOfReferenceTypesComparer.Snapshot(((object)(v)), sbyte[] (sbyte[] v) => ((sbyte[])(ListOfValueTypesComparer.Snapshot(((IEnumerable)(v)), sbyte (sbyte v) => v)))))))), keyComparer: new ValueComparer( bool (sbyte[][][] v1, sbyte[][][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (sbyte[][][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -3678,10 +3678,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas new JsonCollectionOfStructsReaderWriter( JsonSByteReaderWriter.Instance))), elementMapping: CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfValueTypesComparer(new ValueComparer( - bool (sbyte v1, sbyte v2) => v1 == v2, - int (sbyte v) => ((int)(v)), - sbyte (sbyte v) => v))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (sbyte[] v1, sbyte[] v2) => ListOfValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (sbyte v1, sbyte v2) => v1 == v2), + int (sbyte[] v) => ListOfValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (sbyte v) => ((int)(v))), + sbyte[] (sbyte[] v) => ((sbyte[])(ListOfValueTypesComparer.Snapshot(((IEnumerable)(v)), sbyte (sbyte v) => v))))), keyComparer: new ValueComparer( bool (sbyte[][] v1, sbyte[][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (sbyte[][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -5681,10 +5681,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); nullableInt32NestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfNullableValueTypesComparer(new NullableValueComparer(new ValueComparer( - bool (int v1, int v2) => v1 == v2, - int (int v) => v, - int (int v) => v)))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (int? [] v1, int? [] v2) => ListOfNullableValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (int? v1, int? v2) => v1.HasValue && v2.HasValue && ((int)(v1)) == ((int)(v2)) || !(v1.HasValue) && !(v2.HasValue)), + int (int? [] v) => ListOfNullableValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (int? v) => (v.HasValue ? ((int)(v)) : 0)), + int? [] (int? [] v) => ((int? [])(ListOfNullableValueTypesComparer.Snapshot(((IEnumerable)(v)), int? (int? v) => (v.HasValue ? ((int? )(((int)(v)))) : default(int? ))))))), keyComparer: new ValueComparer( bool (int? [][] v1, int? [][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (int? [][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -5859,10 +5859,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); nullableInt64NestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer, long?[][]>(new ListOfReferenceTypesComparer(new ListOfNullableValueTypesComparer(new NullableValueComparer(new ValueComparer( - bool (long v1, long v2) => v1 == v2, - int (long v) => ((object)v).GetHashCode(), - long (long v) => v))))), + comparer: new ListOfReferenceTypesComparer, long?[][]>(new ValueComparer( + bool (long? [][] v1, long? [][] v2) => ListOfReferenceTypesComparer.Compare(((object)(v1)), ((object)(v2)), bool (long? [] v1, long? [] v2) => ListOfNullableValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (long? v1, long? v2) => v1.HasValue && v2.HasValue && ((long)(v1)) == ((long)(v2)) || !(v1.HasValue) && !(v2.HasValue))), + int (long? [][] v) => ListOfReferenceTypesComparer.GetHashCode(((IEnumerable)(((object)(v)))), int (long? [] v) => ListOfNullableValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (long? v) => (v.HasValue ? ((object)((long)(v))).GetHashCode() : 0))), + long? [][] (long? [][] v) => ((long? [][])(ListOfReferenceTypesComparer.Snapshot(((object)(v)), long? [] (long? [] v) => ((long? [])(ListOfNullableValueTypesComparer.Snapshot(((IEnumerable)(v)), long? (long? v) => (v.HasValue ? ((long? )(((long)(v)))) : default(long? )))))))))), keyComparer: new ValueComparer>( bool (List v1, List v2) => object.Equals(v1, v2), int (List v) => ((object)v).GetHashCode(), @@ -5877,10 +5877,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas new JsonCollectionOfNullableStructsReaderWriter( JsonInt64ReaderWriter.Instance))), elementMapping: CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfNullableValueTypesComparer(new NullableValueComparer(new ValueComparer( - bool (long v1, long v2) => v1 == v2, - int (long v) => ((object)v).GetHashCode(), - long (long v) => v)))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (long? [] v1, long? [] v2) => ListOfNullableValueTypesComparer.Compare(((IEnumerable)(v1)), ((IEnumerable)(v2)), bool (long? v1, long? v2) => v1.HasValue && v2.HasValue && ((long)(v1)) == ((long)(v2)) || !(v1.HasValue) && !(v2.HasValue)), + int (long? [] v) => ListOfNullableValueTypesComparer.GetHashCode(((IEnumerable)(v)), int (long? v) => (v.HasValue ? ((object)((long)(v))).GetHashCode() : 0)), + long? [] (long? [] v) => ((long? [])(ListOfNullableValueTypesComparer.Snapshot(((IEnumerable)(v)), long? (long? v) => (v.HasValue ? ((long? )(((long)(v)))) : default(long? ))))))), keyComparer: new ValueComparer( bool (long? [][] v1, long? [][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (long? [][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -6205,10 +6205,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); nullableStringNestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfReferenceTypesComparer(new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (string[] v1, string[] v2) => ListOfReferenceTypesComparer.Compare(((object)(v1)), ((object)(v2)), bool (string v1, string v2) => v1 == v2), + int (string[] v) => ListOfReferenceTypesComparer.GetHashCode(((IEnumerable)(((object)(v)))), int (string v) => ((object)v).GetHashCode()), + string[] (string[] v) => ((string[])(ListOfReferenceTypesComparer.Snapshot(((object)(v)), string (string v) => v))))), keyComparer: new ValueComparer( bool (string[][] v1, string[][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (string[][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))), @@ -7077,10 +7077,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas relationshipIndex: -1, storeGenerationIndex: -1); stringNestedCollection.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ListOfReferenceTypesComparer(new ListOfReferenceTypesComparer(new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v))), + comparer: new ListOfReferenceTypesComparer(new ValueComparer( + bool (string[] v1, string[] v2) => ListOfReferenceTypesComparer.Compare(((object)(v1)), ((object)(v2)), bool (string v1, string v2) => v1 == v2), + int (string[] v) => ListOfReferenceTypesComparer.GetHashCode(((IEnumerable)(((object)(v)))), int (string v) => ((object)v).GetHashCode()), + string[] (string[] v) => ((string[])(ListOfReferenceTypesComparer.Snapshot(((object)(v)), string (string v) => v))))), keyComparer: new ValueComparer( bool (string[][] v1, string[][] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))), int (string[][] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))),