diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/ListComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/ListComparer.cs index f55d5ea3891..cbb65b37454 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/ListComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/ListComparer.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal @@ -50,7 +48,7 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer eleme private static TCollection? Snapshot(TCollection? source, ValueComparer elementComparer, bool readOnly) { - if (source == null) + if (source is null) { return null; } diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/NullableListComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/NullableListComparer.cs index b1582cc32b1..94b9d90e942 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/NullableListComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/NullableListComparer.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal @@ -28,7 +26,8 @@ public NullableListComparer(ValueComparer elementComparer, bool readOnly) (a, b) => Compare(a, b, (ValueComparer)elementComparer), o => GetHashCode(o, (ValueComparer)elementComparer), source => Snapshot(source, (ValueComparer)elementComparer, readOnly)) - { } + { + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -50,7 +49,7 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer eleme private static TCollection? Snapshot(TCollection? source, ValueComparer elementComparer, bool readOnly) { - if (source == null) + if (source is null) { return null; } diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/NullableSingleDimensionalArrayComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/NullableSingleDimensionalArrayComparer.cs index 04f6d950605..a845232b77c 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/NullableSingleDimensionalArrayComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/NullableSingleDimensionalArrayComparer.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -26,7 +25,8 @@ public NullableSingleDimensionalArrayComparer(ValueComparer elementComparer) : b (a, b) => Compare(a, b, (ValueComparer)elementComparer), o => GetHashCode(o, (ValueComparer)elementComparer), source => Snapshot(source, (ValueComparer)elementComparer)) - { } + { + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -48,6 +48,11 @@ private static bool Compare(TElement?[]? a, TElement?[]? b, ValueComparer eleme [return: NotNullIfNotNull("source")] private static TElement?[]? Snapshot(TElement?[]? source, ValueComparer elementComparer) { - if (source == null) + if (source is null) { return null; } diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/NullableStringDictionaryComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/NullableStringDictionaryComparer.cs index 5f3c4ae58be..02c7f5a86fb 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/NullableStringDictionaryComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/NullableStringDictionaryComparer.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal @@ -51,7 +49,7 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer eleme private static TCollection? Snapshot(TCollection? source, ValueComparer elementComparer, bool readOnly) { - if (source == null) + if (source is null) { return null; } diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/SingleDimensionalArrayComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/SingleDimensionalArrayComparer.cs index bf195dc1169..e84a997e1a5 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/SingleDimensionalArrayComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/SingleDimensionalArrayComparer.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -25,7 +24,8 @@ public SingleDimensionalArrayComparer(ValueComparer elementComparer) : base( (a, b) => Compare(a, b, (ValueComparer)elementComparer), o => GetHashCode(o, (ValueComparer)elementComparer), source => Snapshot(source, (ValueComparer)elementComparer)) - { } + { + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -47,6 +47,11 @@ private static bool Compare(TElement[]? a, TElement[]? b, ValueComparer elemen [return: NotNullIfNotNull("source")] private static TElement[]? Snapshot(TElement[]? source, ValueComparer elementComparer) { - if (source == null) + if (source is null) { return null; } diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs index caa08e52258..61cf84d8b12 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal @@ -50,19 +48,15 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer eleme private static TCollection? Snapshot(TCollection? source, ValueComparer elementComparer, bool readOnly) { - if (source == null) + if (source is null) { return null; } diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs index fce71a4dfc9..b95eeb556a5 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -79,7 +77,9 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies) if (clrType.IsArray) { - var elementMapping = FindPrimitiveMapping(new TypeMappingInfo(elementType)); + var elementMappingInfo = new TypeMappingInfo(elementType); + var elementMapping = FindPrimitiveMapping(elementMappingInfo) + ?? FindCollectionMapping(elementMappingInfo); return elementMapping == null ? null : new CosmosTypeMapping(clrType, CreateArrayComparer(elementMapping, elementType)); @@ -93,11 +93,12 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies) || genericTypeDefinition == typeof(IList<>) || genericTypeDefinition == typeof(IReadOnlyList<>)) { - var elementMapping = FindPrimitiveMapping(new TypeMappingInfo(elementType)); + var elementMappingInfo = new TypeMappingInfo(elementType); + var elementMapping = FindPrimitiveMapping(elementMappingInfo) + ?? FindCollectionMapping(elementMappingInfo); return elementMapping == null ? null - : new CosmosTypeMapping(clrType, - CreateListComparer(elementMapping, elementType, clrType, genericTypeDefinition == typeof(IReadOnlyList<>))); + : new CosmosTypeMapping(clrType, CreateListComparer(elementMapping, elementType, clrType)); } if (genericTypeDefinition == typeof(Dictionary<,>) @@ -111,16 +112,12 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies) } elementType = genericArguments[1]; - var elementMapping = FindPrimitiveMapping(new TypeMappingInfo(elementType)); - if(elementMapping == null) - { - return null; - } - + var elementMappingInfo = new TypeMappingInfo(elementType); + var elementMapping = FindPrimitiveMapping(elementMappingInfo) + ?? FindCollectionMapping(elementMappingInfo); return elementMapping == null ? null - : new CosmosTypeMapping(clrType, - CreateStringDictionaryComparer(elementMapping, elementType, clrType, genericTypeDefinition == typeof(IReadOnlyDictionary<,>))); + : new CosmosTypeMapping(clrType, CreateStringDictionaryComparer(elementMapping, elementType, clrType)); } } @@ -139,7 +136,7 @@ private static ValueComparer CreateArrayComparer(CoreTypeMapping elementMapping, } private static ValueComparer CreateListComparer( - CoreTypeMapping elementMapping, Type elementType, Type listType, bool readOnly) + CoreTypeMapping elementMapping, Type elementType, Type listType, bool readOnly = false) { var unwrappedType = elementType.UnwrapNullableType(); @@ -152,7 +149,7 @@ private static ValueComparer CreateListComparer( } private static ValueComparer CreateStringDictionaryComparer( - CoreTypeMapping elementMapping, Type elementType, Type dictType, bool readOnly) + CoreTypeMapping elementMapping, Type elementType, Type dictType, bool readOnly = false) { var unwrappedType = elementType.UnwrapNullableType(); diff --git a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs index 7a4f38700de..9fe5c7bc24b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs @@ -1,12 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; +using System.Runtime.InteropServices; using Microsoft.Azure.Cosmos; +using Microsoft.Azure.Cosmos.Serialization.HybridRow; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Internal; @@ -518,7 +516,6 @@ await Can_add_update_delete_with_collection( }, new[] { 3f, 2 }); - await Can_add_update_delete_with_collection( new decimal?[] { 1, null }, c => @@ -554,6 +551,79 @@ await Can_add_update_delete_with_collection( new Dictionary { { "1", 1 }, { "2", null } }); } + [ConditionalFact] + public async Task Can_add_update_delete_with_nested_collections() + { + await Can_add_update_delete_with_collection( + new List> { new List { 1, 2 } }, + c => + { + c.Collection.Clear(); + c.Collection.Add(new List { 3 }); + }, + new List> { new List { 3 } }); + + await Can_add_update_delete_with_collection>( + new List(), + c => + { + c.Collection.Add(new byte?[] { 3, null }); + c.Collection.Add(null); + }, + new List { new byte?[] { 3, null }, null }); + + await Can_add_update_delete_with_collection>>( + new Dictionary[] { new Dictionary { { "1", null } } }, + c => + { + var dictionary = c.Collection[0]["3"] = "2"; + }, + new List> { new Dictionary { { "1", null }, { "3", "2" } } }); + + await Can_add_update_delete_with_collection( + new List[] { new List { 1f }, new List { 2 } }, + c => + { + c.Collection[1][0] = 3f; + }, + new List[] { new List { 1f }, new List { 3f } }); + + await Can_add_update_delete_with_collection( + new decimal?[][] { new decimal?[] { 1, null } }, + c => + { + c.Collection[0][1] = 3; + }, + new decimal?[][] { new decimal?[] { 1, 3 } }); + + await Can_add_update_delete_with_collection( + new Dictionary> { { "1", new List { 1 } } }, + c => + { + c.Collection["2"] = new List { 3 }; + }, + new Dictionary> { { "1", new List { 1 } }, { "2", new List { 3 } } }); + + await Can_add_update_delete_with_collection>( + new SortedDictionary { { "2", new long?[] { 2 } }, { "1", new long?[] { 1 } } }, + c => + { + c.Collection.Clear(); + c.Collection["2"] = null; + }, + new SortedDictionary { { "2", null } }); + + await Can_add_update_delete_with_collection>>( + ImmutableDictionary>.Empty + .Add("2", new Dictionary { { "value", 2 } }).Add("1", new Dictionary { { "value", 1 } }), + c => + { + c.Collection = ImmutableDictionary>.Empty + .Add("1", new Dictionary { { "value", 1 } }).Add("2", null); + }, + new Dictionary> { { "1", new Dictionary { { "value", 1 } } }, { "2", null } }); + } + private async Task Can_add_update_delete_with_collection( TCollection initialValue, Action> modify, diff --git a/test/EFCore.Cosmos.FunctionalTests/ValueConvertersEndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/ValueConvertersEndToEndCosmosTest.cs index c83641d9593..d8484b62b4b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ValueConvertersEndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ValueConvertersEndToEndCosmosTest.cs @@ -5,7 +5,7 @@ #nullable enable -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore.Cosmos { public class ValueConvertersEndToEndCosmosTest : ValueConvertersEndToEndTestBase