Skip to content

Commit

Permalink
Cosmos: add basic support for nested collections of primitive types
Browse files Browse the repository at this point in the history
  • Loading branch information
AndriySvyryd authored Jul 31, 2021
1 parent 70b2d0f commit 235d6fc
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 51 deletions.
6 changes: 2 additions & 4 deletions src/EFCore.Cosmos/ChangeTracking/Internal/ListComparer.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -50,7 +48,7 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer<TEleme
return false;
}

if (aList == bList)
if (ReferenceEquals(aList, bList))
{
return true;
}
Expand Down Expand Up @@ -79,7 +77,7 @@ private static int GetHashCode(TCollection source, ValueComparer<TElement> eleme

private static TCollection? Snapshot(TCollection? source, ValueComparer<TElement> elementComparer, bool readOnly)
{
if (source == null)
if (source is null)
{
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -28,7 +26,8 @@ public NullableListComparer(ValueComparer elementComparer, bool readOnly)
(a, b) => Compare(a, b, (ValueComparer<TElement>)elementComparer),
o => GetHashCode(o, (ValueComparer<TElement>)elementComparer),
source => Snapshot(source, (ValueComparer<TElement>)elementComparer, readOnly))
{ }
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -50,7 +49,7 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer<TEleme
return false;
}

if (aList == bList)
if (ReferenceEquals(aList, bList))
{
return true;
}
Expand Down Expand Up @@ -90,7 +89,7 @@ private static int GetHashCode(TCollection source, ValueComparer<TElement> eleme

private static TCollection? Snapshot(TCollection? source, ValueComparer<TElement> elementComparer, bool readOnly)
{
if (source == null)
if (source is null)
{
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -26,7 +25,8 @@ public NullableSingleDimensionalArrayComparer(ValueComparer elementComparer) : b
(a, b) => Compare(a, b, (ValueComparer<TElement>)elementComparer),
o => GetHashCode(o, (ValueComparer<TElement>)elementComparer),
source => Snapshot(source, (ValueComparer<TElement>)elementComparer))
{ }
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -48,6 +48,11 @@ private static bool Compare(TElement?[]? a, TElement?[]? b, ValueComparer<TEleme
return false;
}

if (ReferenceEquals(a, b))
{
return true;
}

for (var i = 0; i < a.Length; i++)
{
var (aElement, bElement) = (a[i], b[i]);
Expand Down Expand Up @@ -84,7 +89,7 @@ private static int GetHashCode(TElement?[] source, ValueComparer<TElement> eleme
[return: NotNullIfNotNull("source")]
private static TElement?[]? Snapshot(TElement?[]? source, ValueComparer<TElement> elementComparer)
{
if (source == null)
if (source is null)
{
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -51,7 +49,7 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer<TEleme
return false;
}

if (aDict == bDict)
if (ReferenceEquals(aDict, bDict))
{
return true;
}
Expand Down Expand Up @@ -97,7 +95,7 @@ private static int GetHashCode(TCollection source, ValueComparer<TElement> eleme

private static TCollection? Snapshot(TCollection? source, ValueComparer<TElement> elementComparer, bool readOnly)
{
if (source == null)
if (source is null)
{
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -25,7 +24,8 @@ public SingleDimensionalArrayComparer(ValueComparer elementComparer) : base(
(a, b) => Compare(a, b, (ValueComparer<TElement>)elementComparer),
o => GetHashCode(o, (ValueComparer<TElement>)elementComparer),
source => Snapshot(source, (ValueComparer<TElement>)elementComparer))
{ }
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -47,6 +47,11 @@ private static bool Compare(TElement[]? a, TElement[]? b, ValueComparer<TElement
return false;
}

if (ReferenceEquals(a, b))
{
return true;
}

for (var i = 0; i < a.Length; i++)
{
if (!elementComparer.Equals(a[i], b[i]))
Expand All @@ -72,7 +77,7 @@ private static int GetHashCode(TElement[] source, ValueComparer<TElement> elemen
[return: NotNullIfNotNull("source")]
private static TElement[]? Snapshot(TElement[]? source, ValueComparer<TElement> elementComparer)
{
if (source == null)
if (source is null)
{
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -50,19 +48,15 @@ private static bool Compare(TCollection? a, TCollection? b, ValueComparer<TEleme
return false;
}

if (aDict == bDict)
if (ReferenceEquals(aDict, bDict))
{
return true;
}

foreach (var aPair in aDict)
{
if (!bDict.TryGetValue(aPair.Key, out var bValue))
{
return false;
}

if (!elementComparer.Equals(aPair.Value, bValue))
if (!bDict.TryGetValue(aPair.Key, out var bValue)
|| !elementComparer.Equals(aPair.Value, bValue))
{
return false;
}
Expand All @@ -85,7 +79,7 @@ private static int GetHashCode(TCollection source, ValueComparer<TElement> eleme

private static TCollection? Snapshot(TCollection? source, ValueComparer<TElement> elementComparer, bool readOnly)
{
if (source == null)
if (source is null)
{
return null;
}
Expand Down
29 changes: 13 additions & 16 deletions src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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<,>)
Expand All @@ -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));
}
}

Expand All @@ -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();

Expand All @@ -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();

Expand Down
80 changes: 75 additions & 5 deletions test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 =>
Expand Down Expand Up @@ -554,6 +551,79 @@ await Can_add_update_delete_with_collection(
new Dictionary<string, short?> { { "1", 1 }, { "2", null } });
}

[ConditionalFact]
public async Task Can_add_update_delete_with_nested_collections()
{
await Can_add_update_delete_with_collection(
new List<List<short>> { new List<short> { 1, 2 } },
c =>
{
c.Collection.Clear();
c.Collection.Add(new List<short> { 3 });
},
new List<List<short>> { new List<short> { 3 } });

await Can_add_update_delete_with_collection<IList<byte?[]>>(
new List<byte?[]>(),
c =>
{
c.Collection.Add(new byte?[] { 3, null });
c.Collection.Add(null);
},
new List<byte?[]> { new byte?[] { 3, null }, null });

await Can_add_update_delete_with_collection<IReadOnlyList<Dictionary<string, string>>>(
new Dictionary<string, string>[] { new Dictionary<string, string> { { "1", null } } },
c =>
{
var dictionary = c.Collection[0]["3"] = "2";
},
new List<Dictionary<string, string>> { new Dictionary<string, string> { { "1", null }, { "3", "2" } } });

await Can_add_update_delete_with_collection(
new List<float>[] { new List<float> { 1f }, new List<float> { 2 } },
c =>
{
c.Collection[1][0] = 3f;
},
new List<float>[] { new List<float> { 1f }, new List<float> { 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<string, List<int>> { { "1", new List<int> { 1 } } },
c =>
{
c.Collection["2"] = new List<int> { 3 };
},
new Dictionary<string, List<int>> { { "1", new List<int> { 1 } }, { "2", new List<int> { 3 } } });

await Can_add_update_delete_with_collection<IDictionary<string, long?[]>>(
new SortedDictionary<string, long?[]> { { "2", new long?[] { 2 } }, { "1", new long?[] { 1 } } },
c =>
{
c.Collection.Clear();
c.Collection["2"] = null;
},
new SortedDictionary<string, long?[]> { { "2", null } });

await Can_add_update_delete_with_collection<IReadOnlyDictionary<string, Dictionary<string, short?>>>(
ImmutableDictionary<string, Dictionary<string, short?>>.Empty
.Add("2", new Dictionary<string, short?> { { "value", 2 } }).Add("1", new Dictionary<string, short?> { { "value", 1 } }),
c =>
{
c.Collection = ImmutableDictionary<string, Dictionary<string, short?>>.Empty
.Add("1", new Dictionary<string, short?> { { "value", 1 } }).Add("2", null);
},
new Dictionary<string, Dictionary<string, short?>> { { "1", new Dictionary<string, short?> { { "value", 1 } } }, { "2", null } });
}

private async Task Can_add_update_delete_with_collection<TCollection>(
TCollection initialValue,
Action<CustomerWithCollection<TCollection>> modify,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#nullable enable

namespace Microsoft.EntityFrameworkCore
namespace Microsoft.EntityFrameworkCore.Cosmos
{
public class ValueConvertersEndToEndCosmosTest
: ValueConvertersEndToEndTestBase<ValueConvertersEndToEndCosmosTest.ValueConvertersEndToEndCosmosFixture>
Expand Down

0 comments on commit 235d6fc

Please sign in to comment.