Skip to content

Commit

Permalink
Obsolete structural comparers and make key comparers do deep snapshot…
Browse files Browse the repository at this point in the history
… for arrays

See PR discussion: #19644 (review)
  • Loading branch information
ajcvickers committed Jan 27, 2020
1 parent 70288d2 commit 953bd11
Show file tree
Hide file tree
Showing 19 changed files with 128 additions and 103 deletions.
6 changes: 2 additions & 4 deletions src/EFCore.Cosmos/Storage/Internal/CosmosTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ public class CosmosTypeMapping : CoreTypeMapping
public CosmosTypeMapping(
[NotNull] Type clrType,
[CanBeNull] ValueComparer comparer = null,
[CanBeNull] ValueComparer keyComparer = null,
[CanBeNull] ValueComparer structuralComparer = null)
[CanBeNull] ValueComparer keyComparer = null)
: base(
new CoreTypeMappingParameters(
clrType,
converter: null,
comparer,
keyComparer,
structuralComparer))
keyComparer))
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public CosmosTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
_clrTypeMappings
= new Dictionary<Type, CosmosTypeMapping>
{
{ typeof(byte[]), new CosmosTypeMapping(typeof(byte[]), structuralComparer: new ArrayStructuralComparer<byte>()) }
{ typeof(byte[]), new CosmosTypeMapping(typeof(byte[]), keyComparer: new ArrayStructuralComparer<byte>()) }
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,9 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property,
CoreAnnotationNames.TypeMapping,
CoreAnnotationNames.ValueComparer,
CoreAnnotationNames.KeyValueComparer,
#pragma warning disable 618
CoreAnnotationNames.StructuralValueComparer,
#pragma warning restore 618
CoreAnnotationNames.ValueConverter,
CoreAnnotationNames.ProviderClrType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ private IEnumerable<string> GetAnnotationNamespaces(IEnumerable<IAnnotatable> it
CoreAnnotationNames.TypeMapping,
CoreAnnotationNames.ValueComparer,
CoreAnnotationNames.KeyValueComparer,
#pragma warning disable 618
CoreAnnotationNames.StructuralValueComparer,
#pragma warning restore 618
CoreAnnotationNames.ConstructorBinding,
CoreAnnotationNames.NavigationAccessMode,
CoreAnnotationNames.PropertyAccessMode,
Expand Down
12 changes: 6 additions & 6 deletions src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public InMemoryTable([NotNull] IEntityType entityType, bool sensitiveLoggingEnab
_valueConverters.Add((property.GetIndex(), converter));
}

var comparer = property.GetStructuralValueComparer();
var comparer = property.GetKeyValueComparer();
if (!comparer.HasDefaultBehavior)
{
if (_valueComparers == null)
Expand Down Expand Up @@ -146,8 +146,8 @@ public virtual IReadOnlyList<object[]> SnapshotRows()
return rows;
}

private static List<ValueComparer> GetStructuralComparers(IEnumerable<IProperty> properties)
=> properties.Select(p => p.GetStructuralValueComparer()).ToList();
private static List<ValueComparer> GetKeyComparers(IEnumerable<IProperty> properties)
=> properties.Select(p => p.GetKeyValueComparer()).ToList();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -158,7 +158,7 @@ private static List<ValueComparer> GetStructuralComparers(IEnumerable<IProperty>
public virtual void Create(IUpdateEntry entry)
{
var row = entry.EntityType.GetProperties()
.Select(p => SnapshotValue(p, p.GetStructuralValueComparer(), entry))
.Select(p => SnapshotValue(p, p.GetKeyValueComparer(), entry))
.ToArray();

_rows.Add(CreateKey(entry), row);
Expand Down Expand Up @@ -207,7 +207,7 @@ private static bool IsConcurrencyConflict(
{
if (property.IsConcurrencyToken)
{
var comparer = property.GetStructuralValueComparer();
var comparer = property.GetKeyValueComparer();
var originalValue = entry.GetOriginalValue(property);

if ((comparer != null && !comparer.Equals(rowValue, originalValue))
Expand Down Expand Up @@ -235,7 +235,7 @@ public virtual void Update(IUpdateEntry entry)
if (_rows.ContainsKey(key))
{
var properties = entry.EntityType.GetProperties().ToList();
var comparers = GetStructuralComparers(properties);
var comparers = GetKeyComparers(properties);
var valueBuffer = new object[properties.Count];
var concurrencyConflicts = new Dictionary<IProperty, object>();

Expand Down
6 changes: 2 additions & 4 deletions src/EFCore.InMemory/Storage/Internal/InMemoryTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ public class InMemoryTypeMapping : CoreTypeMapping
public InMemoryTypeMapping(
[NotNull] Type clrType,
[CanBeNull] ValueComparer comparer = null,
[CanBeNull] ValueComparer keyComparer = null,
[CanBeNull] ValueComparer structuralComparer = null)
[CanBeNull] ValueComparer keyComparer = null)
: base(
new CoreTypeMappingParameters(
clrType,
converter: null,
comparer,
keyComparer,
structuralComparer))
keyComparer))
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,12 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo)
Check.DebugAssert(clrType != null, "ClrType is null");

if (clrType.IsValueType
|| clrType == typeof(string))
|| clrType == typeof(string)
|| clrType == typeof(byte[]))
{
return new InMemoryTypeMapping(clrType);
}

if (clrType == typeof(byte[]))
{
return new InMemoryTypeMapping(clrType, structuralComparer: new ArrayStructuralComparer<byte>());
}

if (clrType.FullName == "NetTopologySuite.Geometries.Geometry"
|| clrType.GetBaseTypes().Any(t => t.FullName == "NetTopologySuite.Geometries.Geometry"))
{
Expand All @@ -67,7 +63,6 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo)
return new InMemoryTypeMapping(
clrType,
comparer,
comparer,
comparer);
}

Expand Down
9 changes: 9 additions & 0 deletions src/EFCore/ChangeTracking/ValueComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
Expand All @@ -28,6 +29,14 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking
/// </summary>
public abstract class ValueComparer : IEqualityComparer
{
internal static readonly MethodInfo ArrayCopyMethod
= typeof(Array).GetMethods()
.Single(t => t.Name == nameof(Array.Copy)
&& t.GetParameters().Length == 3
&& t.GetParameters()[0].ParameterType == typeof(Array)
&& t.GetParameters()[1].ParameterType == typeof(Array)
&& t.GetParameters()[2].ParameterType == typeof(int));

internal static readonly MethodInfo EqualityComparerHashCodeMethod
= typeof(IEqualityComparer).GetRuntimeMethod(nameof(IEqualityComparer.GetHashCode), new[] { typeof(object) });

Expand Down
46 changes: 43 additions & 3 deletions src/EFCore/ChangeTracking/ValueComparer`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public class ValueComparer<T> : ValueComparer, IEqualityComparer<T>
public ValueComparer(bool favorStructuralComparisons)
: this(
CreateDefaultEqualsExpression(),
CreateDefaultHashCodeExpression(favorStructuralComparisons))
CreateDefaultHashCodeExpression(favorStructuralComparisons),
CreateDefaultSnapshotExpression(favorStructuralComparisons))
{
}

Expand All @@ -57,7 +58,7 @@ public ValueComparer(bool favorStructuralComparisons)
public ValueComparer(
[NotNull] Expression<Func<T, T, bool>> equalsExpression,
[NotNull] Expression<Func<T, int>> hashCodeExpression)
: this(equalsExpression, hashCodeExpression, v => v)
: this(equalsExpression, hashCodeExpression, CreateDefaultSnapshotExpression(false))
{
}

Expand Down Expand Up @@ -157,7 +158,46 @@ protected static Expression<Func<T, T, bool>> CreateDefaultEqualsExpression()
}

/// <summary>
/// Creates an expression for generated a hash code.
/// Creates an expression for creating a snapshot of a value.
/// </summary>
/// <returns> The snapshot expression. </returns>
protected static Expression<Func<T, T>> CreateDefaultSnapshotExpression(bool favorStructuralComparisons)
{
if (!favorStructuralComparisons
|| !typeof(T).IsArray)
{
return v => v;
}

var sourceParameter = Expression.Parameter(typeof(T), "source");
var lengthVariable = Expression.Variable(typeof(int), "length");
var destinationVariable = Expression.Variable(typeof(T), "destination");

// Code looks like:
// var length = source.Length;
// var destination = new T[length];
// Array.Copy(source, destination, length);
// return destination;
return Expression.Lambda<Func<T, T>>(
Expression.Block(
new[] { lengthVariable, destinationVariable },
Expression.Assign(
lengthVariable,
Expression.Property(sourceParameter, typeof(T).GetProperty(nameof(Array.Length)))),
Expression.Assign(
destinationVariable,
Expression.NewArrayBounds(typeof(T).TryGetSequenceType(), lengthVariable)),
Expression.Call(
ArrayCopyMethod,
sourceParameter,
destinationVariable,
lengthVariable),
destinationVariable),
sourceParameter);
}

/// <summary>
/// Creates an expression for generating a hash code.
/// </summary>
/// <param name="favorStructuralComparisons">
/// If <c>true</c>, then <see cref="IStructuralEquatable" /> is used if the type implements it.
Expand Down
7 changes: 4 additions & 3 deletions src/EFCore/Extensions/ConventionPropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,19 +306,20 @@ public static void SetKeyValueComparer(
/// <param name="property"> The property. </param>
/// <param name="comparer"> The comparer, or <c>null</c> to remove any previously set comparer. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
[Obsolete("Use SetKeyValueComparer. Starting with EF Core 5.0, key comparers must implement structural comparisons and deep copies.")]
public static void SetStructuralValueComparer(
[NotNull] this IConventionProperty property,
[CanBeNull] ValueComparer comparer,
bool fromDataAnnotation = false)
=> property.AsProperty().SetStructuralValueComparer(
comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
=> property.SetKeyValueComparer(comparer, fromDataAnnotation);

/// <summary>
/// Returns the configuration source for <see cref="PropertyExtensions.GetStructuralValueComparer" />.
/// </summary>
/// <param name="property"> The property to find configuration source for. </param>
/// <returns> The configuration source for <see cref="PropertyExtensions.GetStructuralValueComparer" />. </returns>
[Obsolete("Use GetKeyValueComparerConfigurationSource. Starting with EF Core 5.0, key comparers must implement structural comparisons and deep copies.")]
public static ConfigurationSource? GetStructuralValueComparerConfigurationSource([NotNull] this IConventionProperty property)
=> property.FindAnnotation(CoreAnnotationNames.StructuralValueComparer)?.GetConfigurationSource();
=> property.GetKeyValueComparerConfigurationSource();
}
}
3 changes: 2 additions & 1 deletion src/EFCore/Extensions/MutablePropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ public static void SetKeyValueComparer([NotNull] this IMutableProperty property,
/// </summary>
/// <param name="property"> The property. </param>
/// <param name="comparer"> The comparer, or <c>null</c> to remove any previously set comparer. </param>
[Obsolete("Use SetKeyValueComparer. Starting with EF Core 5.0, key comparers must implement structural comparisons and deep copies.")]
public static void SetStructuralValueComparer([NotNull] this IMutableProperty property, [CanBeNull] ValueComparer comparer)
=> property.AsProperty().SetStructuralValueComparer(comparer, ConfigurationSource.Explicit);
=> property.SetKeyValueComparer(comparer);
}
}
14 changes: 2 additions & 12 deletions src/EFCore/Extensions/PropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,19 +310,9 @@ public static ValueComparer GetKeyValueComparer([NotNull] this IProperty propert
/// <param name="property"> The property. </param>
/// <param name="fallback"> If true, then the key comparer is returned when the structural comparer is not set. </param>
/// <returns> The comparer, or <c>null</c> if none has been set. </returns>
[Obsolete("Use GetKeyValueComparer. Starting with EF Core 5.0, key comparers must implement structural comparisons and deep copies.")]
public static ValueComparer GetStructuralValueComparer([NotNull] this IProperty property, bool fallback = true)
{
Check.NotNull(property, nameof(property));

var comparer = (ValueComparer)property[CoreAnnotationNames.StructuralValueComparer];

return fallback
? comparer
?? (ValueComparer)property[CoreAnnotationNames.KeyValueComparer]
?? (ValueComparer)property[CoreAnnotationNames.ValueComparer]
?? property.FindTypeMapping()?.StructuralComparer
: comparer;
}
=> property.GetKeyValueComparer(fallback);

/// <summary>
/// Creates a formatted string representation of the given properties such as is useful
Expand Down
2 changes: 2 additions & 0 deletions src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ bool CanSetValueGenerator(
/// The same builder instance if the configuration was applied,
/// <c>null</c> otherwise.
/// </returns>
[Obsolete("Use HasKeyValueComparer. Starting with EF Core 5.0, key comparers must implement structural comparisons and deep copies.")]
IConventionPropertyBuilder HasStructuralValueComparer([CanBeNull] ValueComparer comparer, bool fromDataAnnotation = false);

/// <summary>
Expand All @@ -414,6 +415,7 @@ bool CanSetValueGenerator(
/// <returns>
/// <c>true</c> if the given <see cref="ValueComparer" /> can be configured for this property.
/// </returns>
[Obsolete("Use CanSetStructuralValueComparer. Starting with EF Core 5.0, key comparers must implement structural comparisons and deep copies.")]
bool CanSetStructuralValueComparer([CanBeNull] ValueComparer comparer, bool fromDataAnnotation = false);
}
}
4 changes: 4 additions & 0 deletions src/EFCore/Metadata/Internal/CoreAnnotationNames.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;

namespace Microsoft.EntityFrameworkCore.Metadata.Internal
Expand Down Expand Up @@ -139,6 +140,7 @@ public static class CoreAnnotationNames
/// 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>
[Obsolete("Use KeyValueComparer. Starting with EF Core 5.0, key comparers must implement structural comparisons and deep copies.")]
public const string StructuralValueComparer = "StructuralValueComparer";

/// <summary>
Expand Down Expand Up @@ -252,7 +254,9 @@ public static class CoreAnnotationNames
ValueConverter,
ValueComparer,
KeyValueComparer,
#pragma warning disable 618
StructuralValueComparer,
#pragma warning restore 618
AfterSaveBehavior,
BeforeSaveBehavior,
QueryFilter,
Expand Down
35 changes: 2 additions & 33 deletions src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,37 +553,6 @@ public virtual bool CanSetKeyValueComparer([CanBeNull] ValueComparer comparer, C
&& Metadata.CheckValueComparer(comparer) == null)
|| Metadata.GetKeyValueComparer(fallback: false) == comparer;

/// <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 InternalPropertyBuilder HasStructuralValueComparer(
[CanBeNull] ValueComparer comparer, ConfigurationSource configurationSource)
{
if (configurationSource.Overrides(Metadata.GetStructuralValueComparerConfigurationSource())
|| Metadata.GetStructuralValueComparer(fallback: false) == comparer)
{
Metadata.SetStructuralValueComparer(comparer, configurationSource);

return this;
}

return null;
}

/// <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 CanSetStructuralValueComparer([CanBeNull] ValueComparer comparer, ConfigurationSource? configurationSource)
=> (configurationSource.Overrides(Metadata.GetStructuralValueComparerConfigurationSource())
&& Metadata.CheckValueComparer(comparer) == null)
|| Metadata.GetStructuralValueComparer(fallback: false) == comparer;

/// <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
Expand Down Expand Up @@ -975,7 +944,7 @@ bool IConventionPropertyBuilder.CanSetKeyValueComparer(ValueComparer comparer, b
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
IConventionPropertyBuilder IConventionPropertyBuilder.HasStructuralValueComparer(ValueComparer comparer, bool fromDataAnnotation)
=> HasStructuralValueComparer(
=> HasKeyValueComparer(
comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
Expand All @@ -985,7 +954,7 @@ IConventionPropertyBuilder IConventionPropertyBuilder.HasStructuralValueComparer
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
bool IConventionPropertyBuilder.CanSetStructuralValueComparer(ValueComparer comparer, bool fromDataAnnotation)
=> CanSetStructuralValueComparer(
=> CanSetKeyValueComparer(
comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
}
}
Loading

0 comments on commit 953bd11

Please sign in to comment.