diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlListTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlListTypeMapping.cs index 1f024448f..49d6741c8 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlListTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlListTypeMapping.cs @@ -1,4 +1,5 @@ #region License + // The PostgreSQL License // // Copyright (C) 2016 The Npgsql Development Team @@ -19,9 +20,11 @@ // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + #endregion using System; +using System.Collections; using System.Text; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -32,53 +35,60 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping { /// - /// Maps PostgreSQL arrays to .NET List{T}. + /// Maps PostgreSQL arrays to . /// public class NpgsqlListTypeMapping : RelationalTypeMapping { + /// + /// The CLR type of the list items. + /// public RelationalTypeMapping ElementMapping { get; } /// - /// Creates the default array mapping (i.e. for the single-dimensional CLR array type) + /// Creates the default list mapping. /// public NpgsqlListTypeMapping(RelationalTypeMapping elementMapping, Type listType) - : this(elementMapping.StoreType + "[]", elementMapping, listType) - {} + : this(elementMapping.StoreType + "[]", elementMapping, listType) {} + /// NpgsqlListTypeMapping(string storeType, RelationalTypeMapping elementMapping, Type listType) - : base(new RelationalTypeMappingParameters( - new CoreTypeMappingParameters(listType, null, CreateComparer(elementMapping, listType)), storeType - )) - { - ElementMapping = elementMapping; - } + : base( + new RelationalTypeMappingParameters( + new CoreTypeMappingParameters(listType, null, CreateComparer(elementMapping, listType)), storeType)) + => ElementMapping = elementMapping; + /// protected NpgsqlListTypeMapping(RelationalTypeMappingParameters parameters, RelationalTypeMapping elementMapping) - : base(parameters) {} + : base(parameters) + => ElementMapping = elementMapping; + /// public override RelationalTypeMapping Clone(string storeType, int? size) => new NpgsqlListTypeMapping(StoreType, ElementMapping, ClrType); + /// public override CoreTypeMapping Clone(ValueConverter converter) => new NpgsqlListTypeMapping(Parameters.WithComposedConverter(converter), ElementMapping); + /// protected override string GenerateNonNullSqlLiteral(object value) { - // TODO: Duplicated from NpgsqlArrayTypeMapping - var arr = (Array)value; + var list = (IList)value; - if (arr.Rank != 1) + if (list.GetType().GenericTypeArguments[0] != ElementMapping.ClrType) throw new NotSupportedException("Multidimensional array literals aren't supported"); var sb = new StringBuilder(); sb.Append("ARRAY["); - for (var i = 0; i < arr.Length; i++) + for (var i = 0; i < list.Count; i++) { - sb.Append(ElementMapping.GenerateSqlLiteral(arr.GetValue(i))); - if (i < arr.Length - 1) - sb.Append(","); + if (i > 0) + sb.Append(','); + + sb.Append(ElementMapping.GenerateSqlLiteral(list[i])); } - sb.Append("]"); + + sb.Append(']'); return sb.ToString(); } @@ -91,14 +101,13 @@ protected override string GenerateNonNullSqlLiteral(object value) static ValueComparer CreateComparer(RelationalTypeMapping elementMapping, Type listType) { Debug.Assert(listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)); + var elementType = listType.GetGenericArguments()[0]; // We use different comparer implementations based on whether we have a non-null element comparer, // and if not, whether the element is IEquatable - if (elementMapping.Comparer != null) - return (ValueComparer)Activator.CreateInstance( - typeof(SingleDimComparerWithComparer<>).MakeGenericType(elementType), elementMapping); + return (ValueComparer)Activator.CreateInstance(typeof(SingleDimComparerWithComparer<>).MakeGenericType(elementType), elementMapping); if (typeof(IEquatable<>).MakeGenericType(elementType).IsAssignableFrom(elementType)) return (ValueComparer)Activator.CreateInstance(typeof(SingleDimComparerWithIEquatable<>).MakeGenericType(elementType)); @@ -110,10 +119,11 @@ static ValueComparer CreateComparer(RelationalTypeMapping elementMapping, Type l class SingleDimComparerWithComparer : ValueComparer> { - public SingleDimComparerWithComparer(RelationalTypeMapping elementMapping) : base( - (a, b) => Compare(a, b, (ValueComparer)elementMapping.Comparer), - o => o.GetHashCode(), // TODO: Need to get hash code of elements... - source => Snapshot(source, (ValueComparer)elementMapping.Comparer)) {} + public SingleDimComparerWithComparer(RelationalTypeMapping elementMapping) + : base( + (a, b) => Compare(a, b, (ValueComparer)elementMapping.Comparer), + o => o.GetHashCode(), // TODO: Need to get hash code of elements... + source => Snapshot(source, (ValueComparer)elementMapping.Comparer)) {} public override Type Type => typeof(List); @@ -133,25 +143,27 @@ static bool Compare(List a, List b, ValueComparer elementCo static List Snapshot(List source, ValueComparer elementComparer) { - if (source == null) + if (source is null) return null; var snapshot = new List(source.Count); + // Note: the following currently boxes every element access because ValueComparer isn't really // generic (see https://github.com/aspnet/EntityFrameworkCore/issues/11072) foreach (var e in source) snapshot.Add(elementComparer.Snapshot(e)); + return snapshot; } } - class SingleDimComparerWithIEquatable : ValueComparer> - where TElem : IEquatable + class SingleDimComparerWithIEquatable : ValueComparer> where TElem : IEquatable { - public SingleDimComparerWithIEquatable(): base( - (a, b) => Compare(a, b), - o => o.GetHashCode(), // TODO: Need to get hash code of elements... - source => DoSnapshot(source)) {} + public SingleDimComparerWithIEquatable() + : base( + (a, b) => Compare(a, b), + o => o.GetHashCode(), // TODO: Need to get hash code of elements... + source => DoSnapshot(source)) {} public override Type Type => typeof(List); @@ -169,8 +181,10 @@ static bool Compare(List a, List b) { if (elem2 == null) continue; + return false; } + if (!elem1.Equals(elem2)) return false; } @@ -180,21 +194,25 @@ static bool Compare(List a, List b) static List DoSnapshot(List source) { - if (source == null) + if (source is null) return null; + var snapshot = new List(source.Count); + foreach (var e in source) snapshot.Add(e); + return snapshot; } } class SingleDimComparerWithEquals : ValueComparer> { - public SingleDimComparerWithEquals() : base( - (a, b) => Compare(a, b), - o => o.GetHashCode(), // TODO: Need to get hash code of elements... - source => DoSnapshot(source)) {} + public SingleDimComparerWithEquals() + : base( + (a, b) => Compare(a, b), + o => o.GetHashCode(), // TODO: Need to get hash code of elements... + source => DoSnapshot(source)) {} public override Type Type => typeof(List); @@ -215,6 +233,7 @@ static bool Compare(List a, List b) continue; return false; } + if (!elem1.Equals(elem2)) return false; } @@ -224,14 +243,16 @@ static bool Compare(List a, List b) static List DoSnapshot(List source) { - if (source == null) + if (source is null) return null; var snapshot = new List(source.Count); + // Note: the following currently boxes every element access because ValueComparer isn't really // generic (see https://github.com/aspnet/EntityFrameworkCore/issues/11072) foreach (var e in source) snapshot.Add(e); + return snapshot; } }