Skip to content

Commit

Permalink
Add overloads to HasConversion that take a ValueComparer (#22074)
Browse files Browse the repository at this point in the history
Fixes #18643
  • Loading branch information
ajcvickers authored Aug 15, 2020
1 parent 6190dac commit 1dc1788
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 32 deletions.
41 changes: 41 additions & 0 deletions src/EFCore/Metadata/Builders/PropertyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.ComponentModel;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
Expand Down Expand Up @@ -399,6 +400,46 @@ public virtual PropertyBuilder HasConversion([CanBeNull] ValueConverter converte
return this;
}

/// <summary>
/// Configures the property so that the property value is converted to the given type before
/// writing to the database and converted back when reading from the database.
/// </summary>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <typeparam name="TProvider"> The type to convert to and from. </typeparam>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual PropertyBuilder HasConversion<TProvider>([CanBeNull] ValueComparer valueComparer)
=> HasConversion(typeof(TProvider), valueComparer);

/// <summary>
/// Configures the property so that the property value is converted to the given type before
/// writing to the database and converted back when reading from the database.
/// </summary>
/// <param name="providerClrType"> The type to convert to and from. </param>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual PropertyBuilder HasConversion([CanBeNull] Type providerClrType, [CanBeNull] ValueComparer valueComparer)
{
Builder.HasConversion(providerClrType, ConfigurationSource.Explicit);
Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit);

return this;
}

/// <summary>
/// Configures the property so that the property value is converted to and from the database
/// using the given <see cref="ValueConverter" />.
/// </summary>
/// <param name="converter"> The converter to use. </param>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual PropertyBuilder HasConversion([CanBeNull] ValueConverter converter, [CanBeNull] ValueComparer valueComparer)
{
Builder.HasConversion(converter, ConfigurationSource.Explicit);
Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit);

return this;
}

#region Hidden System.Object members

/// <summary>
Expand Down
67 changes: 67 additions & 0 deletions src/EFCore/Metadata/Builders/PropertyBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -306,6 +307,72 @@ public virtual PropertyBuilder<TProperty> HasConversion<TProvider>([CanBeNull] V
public new virtual PropertyBuilder<TProperty> HasConversion([CanBeNull] ValueConverter converter)
=> (PropertyBuilder<TProperty>)base.HasConversion(converter);

/// <summary>
/// Configures the property so that the property value is converted to the given type before
/// writing to the database and converted back when reading from the database.
/// </summary>
/// <typeparam name="TProvider"> The type to convert to and from. </typeparam>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual PropertyBuilder<TProperty> HasConversion<TProvider>([CanBeNull] ValueComparer valueComparer)
=> (PropertyBuilder<TProperty>)base.HasConversion<TProvider>(valueComparer);

/// <summary>
/// Configures the property so that the property value is converted to the given type before
/// writing to the database and converted back when reading from the database.
/// </summary>
/// <param name="providerClrType"> The type to convert to and from. </param>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual PropertyBuilder<TProperty> HasConversion(
[CanBeNull] Type providerClrType,
[CanBeNull] ValueComparer valueComparer)
=> (PropertyBuilder<TProperty>)base.HasConversion(providerClrType, valueComparer);

/// <summary>
/// Configures the property so that the property value is converted to and from the database
/// using the given conversion expressions.
/// </summary>
/// <typeparam name="TProvider"> The store type generated by the conversions. </typeparam>
/// <param name="convertToProviderExpression"> An expression to convert objects when writing data to the store. </param>
/// <param name="convertFromProviderExpression"> An expression to convert objects when reading data from the store. </param>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual PropertyBuilder<TProperty> HasConversion<TProvider>(
[NotNull] Expression<Func<TProperty, TProvider>> convertToProviderExpression,
[NotNull] Expression<Func<TProvider, TProperty>> convertFromProviderExpression,
[CanBeNull] ValueComparer valueComparer)
=> HasConversion(
new ValueConverter<TProperty, TProvider>(
Check.NotNull(convertToProviderExpression, nameof(convertToProviderExpression)),
Check.NotNull(convertFromProviderExpression, nameof(convertFromProviderExpression))),
valueComparer);

/// <summary>
/// Configures the property so that the property value is converted to and from the database
/// using the given <see cref="ValueConverter{TModel,TProvider}" />.
/// </summary>
/// <typeparam name="TProvider"> The store type generated by the converter. </typeparam>
/// <param name="converter"> The converter to use. </param>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual PropertyBuilder<TProperty> HasConversion<TProvider>(
[CanBeNull] ValueConverter<TProperty, TProvider> converter,
[CanBeNull] ValueComparer valueComparer)
=> HasConversion((ValueConverter)converter, valueComparer);

/// <summary>
/// Configures the property so that the property value is converted to and from the database
/// using the given <see cref="ValueConverter" />.
/// </summary>
/// <param name="converter"> The converter to use. </param>
/// <param name="valueComparer"> The comparer to use for values before conversion. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual PropertyBuilder<TProperty> HasConversion(
[CanBeNull] ValueConverter converter,
[CanBeNull] ValueComparer valueComparer)
=> (PropertyBuilder<TProperty>)base.HasConversion(converter, valueComparer);

/// <summary>
/// <para>
/// Sets the <see cref="PropertyAccessMode" /> to use for this property.
Expand Down
62 changes: 30 additions & 32 deletions test/EFCore.Specification.Tests/CustomConvertersTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -998,15 +998,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity<StringForeignKeyDataType>(
b =>
{
var property = b.Property(e => e.StringKeyDataTypeId)
.HasConversion(v => "KeyValue=" + v, v => v.Substring(9)).Metadata;
property.SetValueComparer(caseInsensitiveComparer);
b.Property(e => e.StringKeyDataTypeId)
.HasConversion(
v => "KeyValue=" + v,
v => v.Substring(9),
caseInsensitiveComparer);
});

modelBuilder.Entity<MaxLengthDataTypes>(
b =>
{
var bytesComparer = new ValueComparer<byte[]>(
(v1, v2) => v1.SequenceEqual(v2),
v => v.GetHashCode());
b.Property(e => e.String3)
.HasConversion(
new ValueConverter<string, string>(
Expand All @@ -1020,33 +1025,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
.HasConversion(
new ValueConverter<byte[], byte[]>(
v => v.Reverse().Concat(new byte[] { 4, 20 }).ToArray(),
v => v.Reverse().Skip(2).ToArray()))
v => v.Reverse().Skip(2).ToArray()),
bytesComparer)
.HasMaxLength(7);
b.Property(e => e.ByteArray9000)
.HasConversion(
BytesToStringConverter.DefaultInfo.Create())
BytesToStringConverter.DefaultInfo.Create(),
bytesComparer)
.HasMaxLength(LongStringLength * 2);
var bytesComparer = new ValueComparer<byte[]>(
(v1, v2) => v1.SequenceEqual(v2),
v => v.GetHashCode());
b.Property(e => e.ByteArray5).Metadata.SetValueComparer(bytesComparer);
b.Property(e => e.ByteArray9000).Metadata.SetValueComparer(bytesComparer);
});

modelBuilder.Entity<StringListDataType>(
b =>
{
b.Property(e => e.Strings).HasConversion(v => string.Join(",", v), v => v.Split(new[] { ',' }).ToList());
b.Property(e => e.Id).ValueGeneratedNever();
var comparer = new ValueComparer<IList<string>>(
(v1, v2) => v1.SequenceEqual(v2),
v => v.GetHashCode());
b.Property(e => e.Strings).HasConversion(
v => string.Join(",", v),
v => v.Split(new[] { ',' }).ToList(),
new ValueComparer<IList<string>>(
(v1, v2) => v1.SequenceEqual(v2),
v => v.GetHashCode()));
b.Property(e => e.Strings).Metadata.SetValueComparer(comparer);
b.Property(e => e.Id).ValueGeneratedNever();
});

modelBuilder.Entity<Order>(
Expand All @@ -1063,14 +1063,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.HasKey(c => c.CounterId);
b.Property(c => c.Discriminator).HasConversion(
d => StringToDictionarySerializer.Serialize(d),
json => StringToDictionarySerializer.Deserialize(json));
var comparer = new ValueComparer<IDictionary<string, string>>(
(v1, v2) => v1.SequenceEqual(v2),
v => v.GetHashCode(),
v => (IDictionary<string, string>)new Dictionary<string, string>(v));
b.Property(e => e.Discriminator).Metadata.SetValueComparer(comparer);
json => StringToDictionarySerializer.Deserialize(json),
new ValueComparer<IDictionary<string, string>>(
(v1, v2) => v1.SequenceEqual(v2),
v => v.GetHashCode(),
v => (IDictionary<string, string>)new Dictionary<string, string>(v)));
});

var urlConverter = new UrlSchemeRemover();
Expand Down Expand Up @@ -1127,8 +1124,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
b.Property(e => e.Tags).HasConversion(
c => string.Join(",", c),
s => s.Split(',', StringSplitOptions.None).ToList()).Metadata
.SetValueComparer(new ValueComparer<List<string>>(favorStructuralComparisons: true));
s => s.Split(',', StringSplitOptions.None).ToList(),
new ValueComparer<List<string>>(favorStructuralComparisons: true));
b.HasData(new CollectionScalar
{
Expand All @@ -1140,8 +1137,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity<CollectionEnum>(
b =>
{
b.Property(e => e.Roles).HasConversion(new RolesToStringConveter()).Metadata
.SetValueComparer(new ValueComparer<ICollection<Roles>>(favorStructuralComparisons: true));
b.Property(e => e.Roles).HasConversion(
new RolesToStringConveter(),
new ValueComparer<ICollection<Roles>>(favorStructuralComparisons: true));
b.HasData(new CollectionEnum
{
Expand Down

0 comments on commit 1dc1788

Please sign in to comment.