Skip to content

Commit

Permalink
Cosmos: Add basic support for collections and dictionaries of primiti…
Browse files Browse the repository at this point in the history
…ve types (#25344)

Fixes #14762
  • Loading branch information
AndriySvyryd committed Jul 28, 2021
1 parent 2bd48c9 commit 423a107
Show file tree
Hide file tree
Showing 13 changed files with 989 additions and 8 deletions.
101 changes: 101 additions & 0 deletions src/EFCore.Cosmos/ChangeTracking/Internal/ListComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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
{
/// <summary>
/// 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.
/// </summary>
public sealed class ListComparer<TElement, TCollection> : ValueComparer<TCollection>
where TCollection : class, IEnumerable<TElement>
{
/// <summary>
/// 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.
/// </summary>
public ListComparer(ValueComparer elementComparer, bool readOnly)
: base(
(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
/// 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.
/// </summary>
public override Type Type => typeof(TCollection);

private static bool Compare(TCollection? a, TCollection? b, ValueComparer<TElement> elementComparer)
{
if (a is not IReadOnlyList<TElement> aList)
{
return b is not IReadOnlyList<TElement>;
}

if (b is not IReadOnlyList<TElement> bList || aList.Count != bList.Count)
{
return false;
}

if (aList == bList)
{
return true;
}

for (var i = 0; i < aList.Count; i++)
{
if (!elementComparer.Equals(aList[i], bList[i]))
{
return false;
}
}

return true;
}

private static int GetHashCode(TCollection source, ValueComparer<TElement> elementComparer)
{
var hash = new HashCode();
foreach (var el in source)
{
hash.Add(el, elementComparer);
}

return hash.ToHashCode();
}

private static TCollection? Snapshot(TCollection? source, ValueComparer<TElement> elementComparer, bool readOnly)
{
if (source == null)
{
return null;
}

if (readOnly)
{
return source;
}

var snapshot = new List<TElement>(((IReadOnlyList<TElement>)source).Count);
foreach (var e in source)
{
snapshot.Add(elementComparer.Snapshot(e)!);
}

return (TCollection)(object)snapshot;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;

namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal
{
/// <summary>
/// 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.
/// </summary>
public class NullableEqualityComparer<T> : IEqualityComparer<T?>
where T : struct
{
private readonly IEqualityComparer<T> _underlyingComparer;

/// <summary>
/// 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.
/// </summary>
public NullableEqualityComparer(IEqualityComparer<T> underlyingComparer)
=> _underlyingComparer = underlyingComparer;

/// <summary>
/// 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.
/// </summary>
public virtual bool Equals(T? x, T? y)
=> x is null
? y is null
: y.HasValue && _underlyingComparer.Equals(x.Value, y.Value);

/// <summary>
/// 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.
/// </summary>
public virtual int GetHashCode(T? obj)
=> obj is null ? 0 : _underlyingComparer.GetHashCode(obj.Value);
}
}
112 changes: 112 additions & 0 deletions src/EFCore.Cosmos/ChangeTracking/Internal/NullableListComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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
{
/// <summary>
/// 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.
/// </summary>
public sealed class NullableListComparer<TElement, TCollection> : ValueComparer<TCollection>
where TCollection : class, IEnumerable<TElement?>
where TElement : struct
{
/// <summary>
/// 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.
/// </summary>
public NullableListComparer(ValueComparer elementComparer, bool readOnly)
: base(
(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
/// 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.
/// </summary>
public override Type Type => typeof(TCollection);

private static bool Compare(TCollection? a, TCollection? b, ValueComparer<TElement> elementComparer)
{
if (a is not IReadOnlyList<TElement?> aList)
{
return b is not IReadOnlyList<TElement?>;
}

if (b is not IReadOnlyList<TElement?> bList || aList.Count != bList.Count)
{
return false;
}

if (aList == bList)
{
return true;
}

for (var i = 0; i < aList.Count; i++)
{
var (aElement, bElement) = (aList[i], bList[i]);
if (aElement is null)
{
if (bElement is null)
{
continue;
}

return false;
}
if (bElement is null || !elementComparer.Equals(aElement, bElement))
{
return false;
}
}

return true;
}

private static int GetHashCode(TCollection source, ValueComparer<TElement> elementComparer)
{
var nullableEqualityComparer = new NullableEqualityComparer<TElement>(elementComparer);
var hash = new HashCode();
foreach (var el in source)
{
hash.Add(el, nullableEqualityComparer);
}

return hash.ToHashCode();
}

private static TCollection? Snapshot(TCollection? source, ValueComparer<TElement> elementComparer, bool readOnly)
{
if (source == null)
{
return null;
}

if (readOnly)
{
return source;
}

var snapshot = new List<TElement?>(((IReadOnlyList<TElement?>)source).Count);
foreach (var e in source)
{
snapshot.Add(e is { } value ? elementComparer.Snapshot(value) : null);
}

return (TCollection)(object)snapshot;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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;

namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal
{
/// <summary>
/// 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.
/// </summary>
public sealed class NullableSingleDimensionalArrayComparer<TElement> : ValueComparer<TElement?[]>
where TElement : struct
{
/// <summary>
/// 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.
/// </summary>
public NullableSingleDimensionalArrayComparer(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
/// 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.
/// </summary>
public override Type Type => typeof(TElement?[]);

private static bool Compare(TElement?[]? a, TElement?[]? b, ValueComparer<TElement> elementComparer)
{
if (a is null)
{
return b is null;
}

if (b is null || a.Length != b.Length)
{
return false;
}

for (var i = 0; i < a.Length; i++)
{
var (aElement, bElement) = (a[i], b[i]);
if (aElement is null)
{
if (bElement is null)
{
continue;
}

return false;
}
if (bElement is null || !elementComparer.Equals(aElement, bElement))
{
return false;
}
}

return true;
}

private static int GetHashCode(TElement?[] source, ValueComparer<TElement> elementComparer)
{
var nullableEqualityComparer = new NullableEqualityComparer<TElement>(elementComparer);
var hash = new HashCode();
foreach (var el in source)
{
hash.Add(el, nullableEqualityComparer);
}

return hash.ToHashCode();
}

[return: NotNullIfNotNull("source")]
private static TElement?[]? Snapshot(TElement?[]? source, ValueComparer<TElement> elementComparer)
{
if (source == null)
{
return null;
}

var snapshot = new TElement?[source.Length];
for (var i = 0; i < source.Length; i++)
{
snapshot[i] = source[i] is { } value ? elementComparer.Snapshot(value) : null;
}

return snapshot;
}
}
}
Loading

0 comments on commit 423a107

Please sign in to comment.