diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs
index 198546aa4..7eef3b4b9 100644
--- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs
+++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
+using System.Linq;
using System.Text;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
@@ -12,30 +13,15 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
///
/// See: https://www.postgresql.org/docs/current/static/hstore.html
///
- public class NpgsqlHstoreTypeMapping : NpgsqlTypeMapping
+ public abstract class NpgsqlHstoreTypeMapping : NpgsqlTypeMapping
{
- static readonly HstoreComparer ComparerInstance = new HstoreComparer();
-
- ///
- /// Constructs an instance of the class.
- ///
- public NpgsqlHstoreTypeMapping()
- : base(
- new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(Dictionary), null, ComparerInstance),
- "hstore"
- ), NpgsqlDbType.Hstore) {}
-
protected NpgsqlHstoreTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters, NpgsqlDbType.Hstore) {}
- protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
- => new NpgsqlHstoreTypeMapping(parameters);
-
protected override string GenerateNonNullSqlLiteral(object value)
{
var sb = new StringBuilder("HSTORE '");
- foreach (var kv in (Dictionary)value)
+ foreach (var kv in (IReadOnlyDictionary)value)
{
sb.Append('"');
sb.Append(kv.Key); // TODO: Escape
@@ -55,28 +41,5 @@ protected override string GenerateNonNullSqlLiteral(object value)
sb.Append('\'');
return sb.ToString();
}
-
- class HstoreComparer : ValueComparer>
- {
- public HstoreComparer() : base(
- (a, b) => Compare(a,b),
- o => o.GetHashCode(),
- o => o == null ? null : new Dictionary(o))
- {}
-
- static bool Compare(Dictionary a, Dictionary b)
- {
- if (a == null)
- return b == null;
- if (b == null)
- return false;
- if (a.Count != b.Count)
- return false;
- foreach (var kv in a)
- if (!b.TryGetValue(kv.Key, out var bValue) || kv.Value != bValue)
- return false;
- return true;
- }
- }
}
}
diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlImmutableHstoreTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlImmutableHstoreTypeMapping.cs
new file mode 100644
index 000000000..5a328cec7
--- /dev/null
+++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlImmutableHstoreTypeMapping.cs
@@ -0,0 +1,44 @@
+using System.Collections.Immutable;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
+{
+ ///
+ /// The type mapping for the PostgreSQL hstore type to .
+ ///
+ ///
+ /// See: https://www.postgresql.org/docs/current/static/hstore.html
+ ///
+ public class NpgsqlImmutableHstoreTypeMapping : NpgsqlHstoreTypeMapping
+ {
+ static readonly HstoreComparer ComparerInstance = new HstoreComparer();
+
+ ///
+ /// Constructs an instance of the class.
+ ///
+ public NpgsqlImmutableHstoreTypeMapping()
+ : base(
+ new RelationalTypeMappingParameters(
+ new CoreTypeMappingParameters(typeof(ImmutableDictionary), null, ComparerInstance),
+ "hstore"
+ )) {}
+
+ protected NpgsqlImmutableHstoreTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters) {}
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new NpgsqlImmutableHstoreTypeMapping(parameters);
+
+ class HstoreComparer : ValueComparer>
+ {
+ public HstoreComparer() : base(
+ // We could compare contents here if the references are different, but that would penalize the 99% case
+ // where a different reference means different contents, which would only save a very rare database update.
+ (a, b) => ReferenceEquals(a, b),
+ o => o.GetHashCode(),
+ o => o)
+ {}
+ }
+ }
+}
diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMutableHstoreTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMutableHstoreTypeMapping.cs
new file mode 100644
index 000000000..026fa7b08
--- /dev/null
+++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMutableHstoreTypeMapping.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
+{
+ ///
+ /// The type mapping for the PostgreSQL hstore type to .
+ ///
+ ///
+ /// See: https://www.postgresql.org/docs/current/static/hstore.html
+ ///
+ public class NpgsqlMutableHstoreTypeMapping : NpgsqlHstoreTypeMapping
+ {
+ static readonly HstoreComparer ComparerInstance = new HstoreComparer();
+
+ ///
+ /// Constructs an instance of the class.
+ ///
+ public NpgsqlMutableHstoreTypeMapping()
+ : base(
+ new RelationalTypeMappingParameters(
+ new CoreTypeMappingParameters(typeof(Dictionary), null, ComparerInstance),
+ "hstore"
+ )) {}
+
+ protected NpgsqlMutableHstoreTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters) {}
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new NpgsqlMutableHstoreTypeMapping(parameters);
+
+ private static bool Compare(IReadOnlyDictionary a, IReadOnlyDictionary b)
+ {
+ if (a == null)
+ return b == null;
+ if (b == null)
+ return false;
+ if (a.Count != b.Count)
+ return false;
+ foreach (var kv in a)
+ if (!b.TryGetValue(kv.Key, out var bValue) || kv.Value != bValue)
+ return false;
+ return true;
+ }
+
+ class HstoreComparer : ValueComparer>
+ {
+ public HstoreComparer() : base(
+ (a, b) => Compare(a,b),
+ o => o.GetHashCode(),
+ o => o == null ? null : new Dictionary(o))
+ {}
+ }
+ }
+}
diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlReadOnlyHstoreTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlReadOnlyHstoreTypeMapping.cs
new file mode 100644
index 000000000..31263648a
--- /dev/null
+++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlReadOnlyHstoreTypeMapping.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
+{
+ ///
+ /// The type mapping for the PostgreSQL hstore type to immutable .NET Dictionaries.
+ ///
+ ///
+ /// See: https://www.postgresql.org/docs/current/static/hstore.html
+ ///
+ public class NpgsqlReadOnlyHstoreTypeMapping : NpgsqlHstoreTypeMapping
+ {
+ static readonly HstoreComparer ComparerInstance = new HstoreComparer();
+
+ ///
+ /// Constructs an instance of the class.
+ ///
+ public NpgsqlReadOnlyHstoreTypeMapping()
+ : base(
+ new RelationalTypeMappingParameters(
+ new CoreTypeMappingParameters(typeof(IReadOnlyDictionary), null, ComparerInstance),
+ "hstore"
+ )) {}
+
+ protected NpgsqlReadOnlyHstoreTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters) {}
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new NpgsqlReadOnlyHstoreTypeMapping(parameters);
+
+ class HstoreComparer : ValueComparer>
+ {
+ public HstoreComparer() : base(
+ // We could compare contents here if the references are different, but that would penalize the 99% case
+ // where a different reference means different contents, which would only save a very rare database update.
+ (a, b) => ReferenceEquals(a, b),
+ o => o.GetHashCode(),
+ o => o)
+ {}
+ }
+ }
+}
diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
index 9be22bfca..8df4de5e4 100644
--- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
+++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Data;
using System.Diagnostics;
using System.Linq;
@@ -104,12 +105,14 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource
readonly NpgsqlRangeTypeMapping _daterange;
// Other types
- readonly NpgsqlBoolTypeMapping _bool = new NpgsqlBoolTypeMapping();
- readonly NpgsqlBitTypeMapping _bit = new NpgsqlBitTypeMapping();
- readonly NpgsqlVarbitTypeMapping _varbit = new NpgsqlVarbitTypeMapping();
- readonly NpgsqlByteArrayTypeMapping _bytea = new NpgsqlByteArrayTypeMapping();
- readonly NpgsqlHstoreTypeMapping _hstore = new NpgsqlHstoreTypeMapping();
- readonly NpgsqlTidTypeMapping _tid = new NpgsqlTidTypeMapping();
+ readonly NpgsqlBoolTypeMapping _bool = new NpgsqlBoolTypeMapping();
+ readonly NpgsqlBitTypeMapping _bit = new NpgsqlBitTypeMapping();
+ readonly NpgsqlVarbitTypeMapping _varbit = new NpgsqlVarbitTypeMapping();
+ readonly NpgsqlByteArrayTypeMapping _bytea = new NpgsqlByteArrayTypeMapping();
+ readonly NpgsqlMutableHstoreTypeMapping _hstore = new NpgsqlMutableHstoreTypeMapping();
+ readonly NpgsqlReadOnlyHstoreTypeMapping _readOnlyHstore = new NpgsqlReadOnlyHstoreTypeMapping();
+ readonly NpgsqlImmutableHstoreTypeMapping _immutableHstore = new NpgsqlImmutableHstoreTypeMapping();
+ readonly NpgsqlTidTypeMapping _tid = new NpgsqlTidTypeMapping();
// Special stuff
// ReSharper disable once InconsistentNaming
@@ -184,7 +187,7 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
{ "bit", new[] { _bit } },
{ "bit varying", new[] { _varbit } },
{ "varbit", new[] { _varbit } },
- { "hstore", new[] { _hstore } },
+ { "hstore", new RelationalTypeMapping[] { _hstore, _readOnlyHstore, _immutableHstore } },
{ "point", new[] { _point } },
{ "box", new[] { _box } },
{ "line", new[] { _line } },
@@ -213,46 +216,48 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
var clrTypeMappings = new Dictionary
{
- { typeof(bool), _bool },
- { typeof(byte[]), _bytea },
- { typeof(float), _float4 },
- { typeof(double), _float8 },
- { typeof(decimal), _numeric },
- { typeof(Guid), _uuid },
- { typeof(byte), _int2Byte },
- { typeof(short), _int2 },
- { typeof(int), _int4 },
- { typeof(long), _int8 },
- { typeof(string), _text },
- { typeof(JsonDocument), _jsonbDocument },
- { typeof(JsonElement), _jsonbElement },
- { typeof(char), _singleChar },
- { typeof(DateTime), _timestamp },
- { typeof(TimeSpan), _interval },
- { typeof(DateTimeOffset), _timestamptzDto },
- { typeof(PhysicalAddress), _macaddr },
- { typeof(IPAddress), _inet },
- { typeof((IPAddress, int)), _cidr },
- { typeof(BitArray), _varbit },
- { typeof(Dictionary), _hstore },
- { typeof(NpgsqlTid), _tid },
-
- { typeof(NpgsqlPoint), _point },
- { typeof(NpgsqlBox), _box },
- { typeof(NpgsqlLine), _line },
- { typeof(NpgsqlLSeg), _lseg },
- { typeof(NpgsqlPath), _path },
- { typeof(NpgsqlPolygon), _polygon },
- { typeof(NpgsqlCircle), _circle },
-
- { typeof(NpgsqlRange), _int4range },
- { typeof(NpgsqlRange), _int8range },
- { typeof(NpgsqlRange), _numrange },
- { typeof(NpgsqlRange), _tsrange },
-
- { typeof(NpgsqlTsQuery), _tsquery },
- { typeof(NpgsqlTsVector), _tsvector },
- { typeof(NpgsqlTsRankingNormalization), _rankingNormalization }
+ { typeof(bool), _bool },
+ { typeof(byte[]), _bytea },
+ { typeof(float), _float4 },
+ { typeof(double), _float8 },
+ { typeof(decimal), _numeric },
+ { typeof(Guid), _uuid },
+ { typeof(byte), _int2Byte },
+ { typeof(short), _int2 },
+ { typeof(int), _int4 },
+ { typeof(long), _int8 },
+ { typeof(string), _text },
+ { typeof(JsonDocument), _jsonbDocument },
+ { typeof(JsonElement), _jsonbElement },
+ { typeof(char), _singleChar },
+ { typeof(DateTime), _timestamp },
+ { typeof(TimeSpan), _interval },
+ { typeof(DateTimeOffset), _timestamptzDto },
+ { typeof(PhysicalAddress), _macaddr },
+ { typeof(IPAddress), _inet },
+ { typeof((IPAddress, int)), _cidr },
+ { typeof(BitArray), _varbit },
+ { typeof(IReadOnlyDictionary), _readOnlyHstore },
+ { typeof(ImmutableDictionary), _immutableHstore },
+ { typeof(Dictionary), _hstore },
+ { typeof(NpgsqlTid), _tid },
+
+ { typeof(NpgsqlPoint), _point },
+ { typeof(NpgsqlBox), _box },
+ { typeof(NpgsqlLine), _line },
+ { typeof(NpgsqlLSeg), _lseg },
+ { typeof(NpgsqlPath), _path },
+ { typeof(NpgsqlPolygon), _polygon },
+ { typeof(NpgsqlCircle), _circle },
+
+ { typeof(NpgsqlRange), _int4range },
+ { typeof(NpgsqlRange), _int8range },
+ { typeof(NpgsqlRange), _numrange },
+ { typeof(NpgsqlRange), _tsrange },
+
+ { typeof(NpgsqlTsQuery), _tsquery },
+ { typeof(NpgsqlTsVector), _tsvector },
+ { typeof(NpgsqlTsRankingNormalization), _rankingNormalization }
};
StoreTypeMappings = new ConcurrentDictionary(storeTypeMappings, StringComparer.OrdinalIgnoreCase);
diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs
index 345e16bf9..de40b6ce8 100644
--- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs
+++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs
@@ -1,5 +1,6 @@
-using System;
+using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Net.NetworkInformation;
@@ -115,6 +116,8 @@ public virtual void Can_query_using_any_mapped_data_type()
StringAsJsonb = @"{""a"": ""b""}",
StringAsJson = @"{""a"": ""b""}",
DictionaryAsHstore = new Dictionary { { "a", "b" } },
+ IReadOnlyDictionaryAsHstore = new Dictionary { { "c", "d" } },
+ ImmutableDictionaryAsHstore = ImmutableDictionary.Empty.Add("e", "f"),
NpgsqlRangeAsRange = new NpgsqlRange(4, true, 8, false),
IntArrayAsIntArray= new[] { 2, 3 },
@@ -248,36 +251,42 @@ public virtual void Can_query_using_any_mapped_data_type()
var param30 = new Dictionary { { "a", "b" } };
Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.DictionaryAsHstore == param30));
- var param31 = new NpgsqlRange(4, true, 8, false);
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.NpgsqlRangeAsRange == param31));
+ var param31 = (IReadOnlyDictionary)new Dictionary { { "c", "d" } };
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.IReadOnlyDictionaryAsHstore == param31));
- var param32 = new[] { 2, 3 };
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.IntArrayAsIntArray == param32));
+ var param32 = ImmutableDictionary.Empty.Add("e", "f");
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.ImmutableDictionaryAsHstore == param32));
- var param33 = new[] { PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04") };
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.PhysicalAddressArrayAsMacaddrArray == param33));
+ var param33 = new NpgsqlRange(4, true, 8, false);
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.NpgsqlRangeAsRange == param33));
+
+ var param34 = new[] { 2, 3 };
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.IntArrayAsIntArray == param34));
+
+ var param35 = new[] { PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04") };
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.PhysicalAddressArrayAsMacaddrArray == param35));
// ReSharper disable once ConvertToConstant.Local
- var param34 = (uint)int.MaxValue + 1;
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.UintAsXid == param34));
+ var param36 = (uint)int.MaxValue + 1;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.UintAsXid == param36));
- var param35 = NpgsqlTsQuery.Parse("a & b");
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.SearchQuery == param35));
+ var param37 = NpgsqlTsQuery.Parse("a & b");
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.SearchQuery == param37));
- var param36 = NpgsqlTsVector.Parse("a b");
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.SearchVector == param36));
+ var param38 = NpgsqlTsVector.Parse("a b");
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.SearchVector == param38));
// ReSharper disable once ConvertToConstant.Local
- var param37 = NpgsqlTsRankingNormalization.DivideByLength;
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.RankingNormalization == param37));
+ var param39 = NpgsqlTsRankingNormalization.DivideByLength;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.RankingNormalization == param39));
// ReSharper disable once ConvertToConstant.Local
- var param38 = 12724u;
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.Regconfig == param38));
+ var param40 = 12724u;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.Regconfig == param40));
// ReSharper disable once ConvertToConstant.Local
- var param39 = Mood.Sad;
- Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.Mood == param39));
+ var param41 = Mood.Sad;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.Mood == param41));
}
}
@@ -408,32 +417,38 @@ public virtual void Can_query_using_any_mapped_data_types_with_nulls()
Dictionary param30 = null;
Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.DictionaryAsHstore == param30));
- NpgsqlRange? param31 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.NpgsqlRangeAsRange == param31));
+ IReadOnlyDictionary param31 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.IReadOnlyDictionaryAsHstore == param31));
+
+ ImmutableDictionary param32 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.ImmutableDictionaryAsHstore == param32));
+
+ NpgsqlRange? param33 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.NpgsqlRangeAsRange == param33));
- int[] param32 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.IntArrayAsIntArray == param32));
+ int[] param34 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.IntArrayAsIntArray == param34));
- PhysicalAddress[] param33 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.PhysicalAddressArrayAsMacaddrArray== param33));
+ PhysicalAddress[] param35 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.PhysicalAddressArrayAsMacaddrArray== param35));
- uint? param34 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.UintAsXid == param34));
+ uint? param36 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.UintAsXid == param36));
- NpgsqlTsQuery param35 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.SearchQuery == param35));
+ NpgsqlTsQuery param37 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.SearchQuery == param37));
- NpgsqlTsVector param36 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.SearchVector == param36));
+ NpgsqlTsVector param38 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.SearchVector == param38));
- NpgsqlTsRankingNormalization? param37 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.RankingNormalization == param37));
+ NpgsqlTsRankingNormalization? param39 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.RankingNormalization == param39));
- uint? param38 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.Regconfig == param38));
+ uint? param40 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.Regconfig == param40));
- Mood? param39 = null;
- Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.Mood == param39));
+ Mood? param41 = null;
+ Assert.Same(entity, context.Set().Single(e => e.Int == 911 && e.Mood == param41));
}
}
@@ -682,6 +697,8 @@ static void AssertNullMappedNullableDataTypes(MappedNullableDataTypes entity, in
Assert.Null(entity.StringAsJsonb);
Assert.Null(entity.StringAsJson);
Assert.Null(entity.DictionaryAsHstore);
+ Assert.Null(entity.IReadOnlyDictionaryAsHstore);
+ Assert.Null(entity.ImmutableDictionaryAsHstore);
Assert.Null(entity.NpgsqlRangeAsRange);
Assert.Null(entity.IntArrayAsIntArray);
@@ -1310,6 +1327,13 @@ protected class MappedNullableDataTypes
[Column(TypeName = "hstore")]
public Dictionary DictionaryAsHstore { get; set; }
+ [Column(TypeName = "hstore")]
+ // ReSharper disable once InconsistentNaming
+ public IReadOnlyDictionary IReadOnlyDictionaryAsHstore { get; set; }
+
+ [Column(TypeName = "hstore")]
+ public ImmutableDictionary ImmutableDictionaryAsHstore { get; set; }
+
[Column(TypeName = "int4range")]
public NpgsqlRange? NpgsqlRangeAsRange { get; set; }
diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs
index bced06471..704f239b0 100644
--- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs
+++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs
@@ -1,6 +1,7 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Net;
using System.Net.NetworkInformation;
using System.Text.Json;
@@ -308,7 +309,7 @@ public void GenerateSqlLiteral_returns_hstore_literal()
}));
[Fact]
- public void ValueComparer_hstore()
+ public void ValueComparer_hstore_as_Dictionary()
{
var source = new Dictionary
{
@@ -325,6 +326,40 @@ public void ValueComparer_hstore()
Assert.False(comparer.Equals(source, snapshot));
}
+ [Fact]
+ public void ValueComparer_hstore_as_IReadOnlyDictionary()
+ {
+ var sourceAsDict = new Dictionary
+ {
+ { "k1", "v1" },
+ { "k2", "v2" }
+ };
+ IReadOnlyDictionary source = sourceAsDict;
+
+ var comparer = GetMapping("hstore").Comparer;
+ var snapshot = (IReadOnlyDictionary)comparer.Snapshot(source);
+ Assert.Equal(source, snapshot);
+ Assert.NotSame(source, snapshot);
+ Assert.True(comparer.Equals(source, snapshot));
+ sourceAsDict.Remove("k1");
+ Assert.False(comparer.Equals(source, snapshot));
+ }
+
+ [Fact]
+ public void ValueComparer_hstore_as_ImmutableDictionary()
+ {
+ var source = ImmutableDictionary.Empty
+ .Add("k1", "v1")
+ .Add("k2", "v2");
+
+ var comparer = Mapper.FindMapping(typeof(ImmutableDictionary), "hstore").Comparer;
+ var snapshot = comparer.Snapshot(source);
+ Assert.Equal(source, snapshot);
+ Assert.True(comparer.Equals(source, snapshot));
+ source = source.Remove("k1");
+ Assert.False(comparer.Equals(source, snapshot));
+ }
+
[Fact]
public void GenerateSqlLiteral_returns_enum_literal()
{