From a1bbd22fbecf357c0a9c1e2242a9d3a56bf95659 Mon Sep 17 00:00:00 2001 From: Matt Vance Date: Thu, 26 Sep 2024 01:21:32 -0700 Subject: [PATCH] Changes from PR review --- .../Internal/NpgsqlHstoreTranslator.cs | 69 +++-- .../Query/NpgsqlSqlExpressionFactory.cs | 4 - .../Mapping/NpgsqlHstoreTypeMapping.cs | 9 +- .../Query/HstoreQueryFixture.cs | 73 ----- .../Query/HstoreQueryTest.cs | 264 +++++++++++------- .../Dictionary/DictionaryContainerEntity.cs | 7 - .../TestModels/Dictionary/DictionaryEntity.cs | 17 -- .../Dictionary/DictionaryQueryContext.cs | 22 -- .../Dictionary/DictionaryQueryData.cs | 47 ---- 9 files changed, 217 insertions(+), 295 deletions(-) delete mode 100644 test/EFCore.PG.FunctionalTests/Query/HstoreQueryFixture.cs delete mode 100644 test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryContainerEntity.cs delete mode 100644 test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryEntity.cs delete mode 100644 test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryContext.cs delete mode 100644 test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryData.cs diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlHstoreTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlHstoreTranslator.cs index 81ca2f1c1..17b1be374 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlHstoreTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlHstoreTranslator.cs @@ -1,7 +1,6 @@ using System.Collections.Immutable; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal; @@ -30,14 +29,15 @@ public class NpgsqlHstoreTranslator : IMethodCallTranslator, IMemberTranslator ImmutableDictionaryType.GetMethod(nameof(ImmutableDictionary.ContainsValue))!; private static readonly MethodInfo Dictionary_Item_Getter = - DictionaryType.GetProperty("Item")!.GetMethod!; + DictionaryType.FindIndexerProperty()!.GetMethod!; private static readonly MethodInfo ImmutableDictionary_Item_Getter = - ImmutableDictionaryType.GetProperty("Item")!.GetMethod!; + ImmutableDictionaryType.FindIndexerProperty()!.GetMethod!; private static readonly MethodInfo Enumerable_Any = - typeof(Enumerable).GetMethod(nameof(Enumerable.Any), - BindingFlags.Public | BindingFlags.Static, new[] { typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)) })! + typeof(Enumerable).GetMethod( + nameof(Enumerable.Any), BindingFlags.Public | BindingFlags.Static, + [typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0))])! .MakeGenericMethod(typeof(KeyValuePair)); private static readonly PropertyInfo Dictionary_Count = DictionaryType.GetProperty(nameof(Dictionary.Count))!; @@ -48,8 +48,20 @@ public class NpgsqlHstoreTranslator : IMethodCallTranslator, IMemberTranslator private static readonly PropertyInfo ImmutableDictionary_IsEmpty = ImmutableDictionaryType.GetProperty(nameof(ImmutableDictionary.IsEmpty))!; + private static readonly PropertyInfo Dictionary_Keys = DictionaryType.GetProperty(nameof(Dictionary.Keys))!; + + private static readonly PropertyInfo ImmutableDictionary_Keys = + ImmutableDictionaryType.GetProperty(nameof(ImmutableDictionary.Keys))!; + + private static readonly PropertyInfo Dictionary_Values = DictionaryType.GetProperty(nameof(Dictionary.Values))!; + + private static readonly PropertyInfo ImmutableDictionary_Values = + ImmutableDictionaryType.GetProperty(nameof(ImmutableDictionary.Values))!; + private readonly RelationalTypeMapping _stringListTypeMapping; + private readonly RelationalTypeMapping _stringTypeMapping; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; + private readonly RelationalTypeMapping _dictionaryKeyCollectionMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -61,6 +73,8 @@ public NpgsqlHstoreTranslator(IRelationalTypeMappingSource typeMappingSource, Np { _sqlExpressionFactory = sqlExpressionFactory; _stringListTypeMapping = typeMappingSource.FindMapping(typeof(List))!; + _stringTypeMapping = typeMappingSource.FindMapping(typeof(string))!; + _dictionaryKeyCollectionMapping = typeMappingSource.FindMapping(typeof(Dictionary.KeyCollection))!; } /// @@ -75,19 +89,14 @@ public NpgsqlHstoreTranslator(IRelationalTypeMappingSource typeMappingSource, Np IReadOnlyList arguments, IDiagnosticsLogger logger) { - if (method == Enumerable_Any) + if (method == Enumerable_Any && arguments[0].TypeMapping?.StoreType == "hstore") { - var value = instance ?? arguments[0]; - if (value.TypeMapping?.StoreType == NpgsqlHstoreTypeMapping.HstoreType) - { - return _sqlExpressionFactory.NotEqual( - Translate(value, Dictionary_Count, typeof(int), logger)!, - _sqlExpressionFactory.Constant(0)); - } - return null; + return _sqlExpressionFactory.NotEqual( + Translate(arguments[0], Dictionary_Count, typeof(int), logger)!, + _sqlExpressionFactory.Constant(0)); } - if (instance?.TypeMapping is null || instance.TypeMapping.StoreType != NpgsqlHstoreTypeMapping.HstoreType) + if (instance?.TypeMapping?.StoreType != "hstore") { return null; } @@ -101,15 +110,15 @@ public NpgsqlHstoreTranslator(IRelationalTypeMappingSource typeMappingSource, Np { return _sqlExpressionFactory.Any( arguments[0], - _sqlExpressionFactory.Function( - "avals", new[] { instance }, false, FalseArrays[1], typeof(List), _stringListTypeMapping), + Translate(instance, Dictionary_Values, typeof(List), logger)!, PgAnyOperatorType.Equal); } if (method == Dictionary_Item_Getter || method == ImmutableDictionary_Item_Getter) { - return _sqlExpressionFactory.MakePostgresBinary(PgExpressionType.HStoreValueForKey, instance, arguments[0]); + return _sqlExpressionFactory.MakePostgresBinary(PgExpressionType.HStoreValueForKey, instance, arguments[0], _stringTypeMapping); } + return null; } @@ -126,19 +135,30 @@ public NpgsqlHstoreTranslator(IRelationalTypeMappingSource typeMappingSource, Np IDiagnosticsLogger logger) { - if (instance?.TypeMapping is null || instance.TypeMapping.StoreType != NpgsqlHstoreTypeMapping.HstoreType) + if (instance?.TypeMapping?.StoreType != "hstore") { return null; } if (member == Dictionary_Count || member == ImmutableDictionary_Count) { - return _sqlExpressionFactory.Function("array_length", new [] - { - _sqlExpressionFactory.Function( - "akeys", new[] { instance }, false, FalseArrays[1], typeof(List), _stringListTypeMapping), + return _sqlExpressionFactory.Function("array_length", + [ + Translate(instance, Dictionary_Keys, null!, logger)!, _sqlExpressionFactory.Constant(1) - }, false, FalseArrays[2], typeof(int)); + ], false, TrueArrays[2], typeof(int)); + } + + if (member == Dictionary_Keys || member == ImmutableDictionary_Keys) + { + return _sqlExpressionFactory.Function( + "akeys", [instance], true, TrueArrays[1], typeof(List), _stringListTypeMapping); + } + + if (member == Dictionary_Values || member == ImmutableDictionary_Values) + { + return _sqlExpressionFactory.Function( + "avals", [instance], true, TrueArrays[1], typeof(List), _stringListTypeMapping); } if (member == ImmutableDictionary_IsEmpty) @@ -147,6 +167,7 @@ public NpgsqlHstoreTranslator(IRelationalTypeMappingSource typeMappingSource, Np Translate(instance, Dictionary_Count, typeof(int), logger)!, _sqlExpressionFactory.Constant(0)); } + return null; } } diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 0cc8c353c..7ffd21594 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -316,10 +316,6 @@ public virtual SqlExpression MakePostgresBinary( case PgExpressionType.Distance: returnType = typeof(double); break; - - case PgExpressionType.HStoreValueForKey: - returnType = typeof(string); - break; } return (PgBinaryExpression)ApplyTypeMapping( diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs index b6bd178e6..0b8b5f0dc 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs @@ -5,7 +5,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; /// /// The type mapping for the PostgreSQL hstore type. Supports both -/// and over strings. +/// and where TKey and TValue are both strings. /// /// /// See: https://www.postgresql.org/docs/current/static/hstore.html @@ -14,11 +14,6 @@ public class NpgsqlHstoreTypeMapping : NpgsqlTypeMapping { private static readonly HstoreMutableComparer MutableComparerInstance = new(); - /// - /// The database store type of the Hstore type - /// - public const string HstoreType = "hstore"; - /// /// 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 @@ -37,7 +32,7 @@ public NpgsqlHstoreTypeMapping(Type clrType) : base( new RelationalTypeMappingParameters( new CoreTypeMappingParameters(clrType, comparer: GetComparer(clrType)), - HstoreType), + "hstore"), NpgsqlDbType.Hstore) { } diff --git a/test/EFCore.PG.FunctionalTests/Query/HstoreQueryFixture.cs b/test/EFCore.PG.FunctionalTests/Query/HstoreQueryFixture.cs deleted file mode 100644 index af0fbd194..000000000 --- a/test/EFCore.PG.FunctionalTests/Query/HstoreQueryFixture.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Dictionary; -using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; - -public class HstoreQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory -{ - protected override string StoreName - => "HstoreQueryTest"; - - protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; - - public TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - - private DictionaryQueryData _expectedData; - - public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder).ConfigureWarnings(wcb => wcb.Ignore(CoreEventId.CollectionWithoutComparer)); - - protected override Task SeedAsync(DictionaryQueryContext context) - => DictionaryQueryContext.SeedAsync(context); - - public Func GetContextCreator() - => CreateContext; - - public ISetSource GetExpectedData() - => _expectedData ??= new DictionaryQueryData(); - - public IReadOnlyDictionary EntitySorters - => new Dictionary> - { - { typeof(DictionaryEntity), e => ((DictionaryEntity)e)?.Id }, { typeof(DictionaryContainerEntity), e => ((DictionaryContainerEntity)e)?.Id } - }.ToDictionary(e => e.Key, e => (object)e.Value); - - public IReadOnlyDictionary EntityAsserters - => new Dictionary> - { - { - typeof(DictionaryEntity), (e, a) => - { - Assert.Equal(e is null, a is null); - if (a is not null) - { - var ee = (DictionaryEntity)e; - var aa = (DictionaryEntity)a; - - Assert.Equal(ee.Id, aa.Id); - Assert.Equal(ee.Dictionary, ee.Dictionary); - Assert.Equal(ee.ImmutableDictionary, ee.ImmutableDictionary); - Assert.Equal(ee.NullableDictionary, ee.NullableDictionary); - Assert.Equal(ee.NullableImmutableDictionary, ee.NullableImmutableDictionary); - - } - } - }, - { - typeof(DictionaryContainerEntity), (e, a) => - { - Assert.Equal(e is null, a is null); - if (a is not null) - { - var ee = (DictionaryContainerEntity)e; - var aa = (DictionaryContainerEntity)a; - - Assert.Equal(ee.Id, aa.Id); - Assert.Equal(ee.DictionaryEntities, ee.DictionaryEntities); - } - } - } - }.ToDictionary(e => e.Key, e => (object)e.Value); -} diff --git a/test/EFCore.PG.FunctionalTests/Query/HstoreQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/HstoreQueryTest.cs index 9e1c69a4e..5ac25a69e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/HstoreQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/HstoreQueryTest.cs @@ -1,7 +1,122 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Dictionary; +using System.Collections.Immutable; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; +public class DictionaryEntity +{ + public int Id { get; set; } + + public Dictionary Dictionary { get; set; } = null!; + + public ImmutableDictionary ImmutableDictionary { get; set; } = null!; + +} + +public class DictionaryQueryContext(DbContextOptions options) : PoolableDbContext(options) +{ + public DbSet SomeEntities { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(); + } + + public static async Task SeedAsync(DictionaryQueryContext context) + { + var arrayEntities = DictionaryQueryData.CreateDictionaryEntities(); + + context.SomeEntities.AddRange(arrayEntities); + await context.SaveChangesAsync(); + } +} + +public class DictionaryQueryData : ISetSource +{ + public IReadOnlyList DictionaryEntities { get; } = CreateDictionaryEntities(); + + public IQueryable Set() + where TEntity : class + { + if (typeof(TEntity) == typeof(DictionaryEntity)) + { + return (IQueryable)DictionaryEntities.AsQueryable(); + } + + throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); + } + + public static IReadOnlyList CreateDictionaryEntities() + => + [ + new() + { + Id = 1, + Dictionary = new() { ["key"] = "value" }, + ImmutableDictionary = new Dictionary { ["key2"] = "value2" }.ToImmutableDictionary(), + }, + new() + { + Id = 2, + Dictionary = new() { ["key"] = "value" }, + ImmutableDictionary = new Dictionary { ["key3"] = "value3" }.ToImmutableDictionary(), + } + ]; +} + +public class HstoreQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory +{ + protected override string StoreName + => "HstoreQueryTest"; + + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + private DictionaryQueryData _expectedData; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings(wcb => wcb.Ignore(CoreEventId.CollectionWithoutComparer)); + + protected override Task SeedAsync(DictionaryQueryContext context) + => DictionaryQueryContext.SeedAsync(context); + + public Func GetContextCreator() + => CreateContext; + + public ISetSource GetExpectedData() + => _expectedData ??= new DictionaryQueryData(); + + public IReadOnlyDictionary EntitySorters + => new Dictionary> + { + { typeof(DictionaryEntity), e => ((DictionaryEntity)e)?.Id } + }.ToDictionary(e => e.Key, e => (object)e.Value); + + public IReadOnlyDictionary EntityAsserters + => new Dictionary> + { + { + typeof(DictionaryEntity), (e, a) => + { + Assert.Equal(e is null, a is null); + if (a is not null) + { + var ee = (DictionaryEntity)e; + var aa = (DictionaryEntity)a; + + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Dictionary, ee.Dictionary); + Assert.Equal(ee.ImmutableDictionary, ee.ImmutableDictionary); + + } + } + } + }.ToDictionary(e => e.Key, e => (object)e.Value); +} + public class HstoreQueryTest : QueryTestBase { public HstoreQueryTest(HstoreQueryFixture fixture, ITestOutputHelper testOutputHelper) @@ -16,57 +131,27 @@ public HstoreQueryTest(HstoreQueryFixture fixture, ITestOutputHelper testOutputH public async Task Dictionary_Contains_key(bool async) { var keyToTest = "key"; - await AssertQuery(async, _ => _.Set().Where(s => s.Dictionary.ContainsKey(keyToTest))); + await AssertQuery(async, ss => ss.Set().Where(s => s.Dictionary.ContainsKey(keyToTest))); AssertSql(""" @__keyToTest_0='key' -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE s."Dictionary" ? @__keyToTest_0 """); } - [Theory] - [MemberData(nameof(IsAsyncData))] - public async Task NullableDictionary_Contains_key(bool async) - { - var keyToTest = "key2"; - await AssertQuery(async, _ => _.Set().Where(s => s.NullableDictionary != null && s.NullableDictionary.ContainsKey(keyToTest))); - AssertSql(""" -@__keyToTest_0='key2' - -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" -FROM "SomeEntities" AS s -WHERE s."NullableDictionary" IS NOT NULL AND s."NullableDictionary" ? @__keyToTest_0 -"""); - } - - [Theory] - [MemberData(nameof(IsAsyncData))] - public async Task ImmutableNullableDictionary_Contains_key(bool async) - { - var keyToTest = "key3"; - await AssertQuery(async, _ => _.Set().Where(s => s.NullableImmutableDictionary != null && s.NullableImmutableDictionary.ContainsKey(keyToTest))); - AssertSql(""" -@__keyToTest_0='key3' - -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" -FROM "SomeEntities" AS s -WHERE s."NullableImmutableDictionary" IS NOT NULL AND s."NullableImmutableDictionary" ? @__keyToTest_0 -"""); - } - [Theory] [MemberData(nameof(IsAsyncData))] public async Task ImmutableDictionary_Contains_key(bool async) { var keyToTest = "key3"; - await AssertQuery(async, _ => _.Set().Where(s => s.ImmutableDictionary.ContainsKey(keyToTest))); + await AssertQuery(async, ss => ss.Set().Where(s => s.ImmutableDictionary.ContainsKey(keyToTest))); AssertSql( """ @__keyToTest_0='key3' -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE s."ImmutableDictionary" ? @__keyToTest_0 """); @@ -77,12 +162,12 @@ WHERE s."ImmutableDictionary" ? @__keyToTest_0 public async Task Dictionary_Contains_value(bool async) { var valueToTest = "value"; - await AssertQuery(async, _ => _.Set().Where(s => s.Dictionary.ContainsValue(valueToTest))); + await AssertQuery(async, ss => ss.Set().Where(s => s.Dictionary.ContainsValue(valueToTest))); AssertSql( """ @__valueToTest_0='value' -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE @__valueToTest_0 = ANY (avals(s."Dictionary")) """); @@ -90,82 +175,70 @@ public async Task Dictionary_Contains_value(bool async) [Theory] [MemberData(nameof(IsAsyncData))] - public async Task Dictionary_Item_equals_found(bool async) + public async Task Dictionary_Keys(bool async) { - var keyToTest = "key"; - var valueToTest = "value"; - await AssertQuery(async, _ => _.Set().Where(s => s.Dictionary[keyToTest] == valueToTest)); + await AssertQuery( + async, ss => ss.Set().Select(s => s.Dictionary.Keys), elementAsserter: Assert.Equal, assertOrder: true); AssertSql( """ -@__valueToTest_0='value' - -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT akeys(s."Dictionary") FROM "SomeEntities" AS s -WHERE s."Dictionary" -> 'key' = @__valueToTest_0 """); } [Theory] [MemberData(nameof(IsAsyncData))] - public async Task Dictionary_Item_equals_not_found(bool async) + public async Task ImmutableDictionary_Keys(bool async) { - var keyToTest = "key"; - var valueToTest = "value2"; - await AssertQuery(async, _ => _.Set().Where(s => s.Dictionary.ContainsKey(keyToTest) && s.Dictionary[keyToTest] == valueToTest), assertEmpty: true); + await AssertQuery( + async, ss => ss.Set().Select(s => s.ImmutableDictionary.Keys), elementAsserter: Assert.Equal, assertOrder: true); AssertSql( """ -@__keyToTest_0='key' -@__valueToTest_1='value2' - -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT akeys(s."ImmutableDictionary") FROM "SomeEntities" AS s -WHERE s."Dictionary" ? @__keyToTest_0 AND s."Dictionary" -> 'key' = @__valueToTest_1 """); } [Theory] [MemberData(nameof(IsAsyncData))] - public async Task NullableDictionary_Item_equals(bool async) + public async Task Dictionary_Values(bool async) { - var keyToTest = "key2"; - var valueToTest = "value"; await AssertQuery( - async, - _ => _.Set().Where( - s => s.NullableDictionary != null - && s.NullableDictionary.ContainsKey(keyToTest) - && s.NullableDictionary[keyToTest] == valueToTest)); + async, ss => ss.Set().Select(s => s.Dictionary.Values), elementAsserter: Assert.Equal, assertOrder: true); AssertSql( """ -@__keyToTest_0='key2' -@__valueToTest_1='value' - -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT avals(s."Dictionary") FROM "SomeEntities" AS s -WHERE s."NullableDictionary" IS NOT NULL AND s."NullableDictionary" ? @__keyToTest_0 AND s."NullableDictionary" -> 'key2' = @__valueToTest_1 """); } [Theory] [MemberData(nameof(IsAsyncData))] - public async Task ImmutableNullableDictionary_Item_equals(bool async) + public async Task ImmutableDictionary_Values(bool async) { - var keyToTest = "key3"; - var valueToTest = "value2"; await AssertQuery( - async, - _ => _.Set().Where( - s => s.NullableImmutableDictionary != null - && s.NullableImmutableDictionary.ContainsKey(keyToTest) - && s.NullableImmutableDictionary[keyToTest] == valueToTest)); + async, ss => ss.Set().Select(s => s.ImmutableDictionary.Values), elementAsserter: Assert.Equal, assertOrder: true); AssertSql( """ -@__keyToTest_0='key3' -@__valueToTest_1='value2' +SELECT avals(s."ImmutableDictionary") +FROM "SomeEntities" AS s +"""); + } -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" + [Theory] + [MemberData(nameof(IsAsyncData))] + public async Task Dictionary_Item_equals(bool async) + { + var keyToTest = "key"; + var valueToTest = "value"; + await AssertQuery(async, ss => ss.Set().Where(s => s.Dictionary[keyToTest] == valueToTest)); + AssertSql( + """ +@__valueToTest_0='value' + +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s -WHERE s."NullableImmutableDictionary" IS NOT NULL AND s."NullableImmutableDictionary" ? @__keyToTest_0 AND s."NullableImmutableDictionary" -> @__keyToTest_0 = @__valueToTest_1 +WHERE s."Dictionary" -> 'key' = @__valueToTest_0 """); } @@ -175,15 +248,18 @@ public async Task ImmutableDictionary_Item_equals(bool async) { var keyToTest = "key2"; var valueToTest = "value2"; - await AssertQuery(async, _ => _.Set().Where(s => s.ImmutableDictionary.ContainsKey(keyToTest) && s.ImmutableDictionary[keyToTest] == valueToTest)); + await AssertQuery(async, ss => + ss.Set().Where(s => s.ImmutableDictionary[keyToTest] == valueToTest), + ss => ss.Set().Where(s => + s.ImmutableDictionary.ContainsKey(keyToTest) && s.ImmutableDictionary[keyToTest] == valueToTest)); AssertSql( """ @__keyToTest_0='key2' @__valueToTest_1='value2' -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s -WHERE s."ImmutableDictionary" ? @__keyToTest_0 AND s."ImmutableDictionary" -> @__keyToTest_0 = @__valueToTest_1 +WHERE s."ImmutableDictionary" -> @__keyToTest_0 = @__valueToTest_1 """); } @@ -191,10 +267,10 @@ public async Task ImmutableDictionary_Item_equals(bool async) [MemberData(nameof(IsAsyncData))] public async Task Dictionary_Where_Count(bool async) { - await AssertQuery(async, _ => _.Set().Where(s => s.Dictionary.Count >= 1)); + await AssertQuery(async, ss => ss.Set().Where(s => s.Dictionary.Count >= 1)); AssertSql( """ -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE array_length(akeys(s."Dictionary"), 1) >= 1 """); @@ -204,7 +280,7 @@ WHERE array_length(akeys(s."Dictionary"), 1) >= 1 [MemberData(nameof(IsAsyncData))] public async Task Dictionary_Select_Count(bool async) { - await AssertQuery(async, _ => _.Set().Select(s => s.Dictionary.Count)); + await AssertQuery(async, ss => ss.Set().Select(s => s.Dictionary.Count)); AssertSql( """ SELECT array_length(akeys(s."Dictionary"), 1) @@ -216,10 +292,10 @@ SELECT array_length(akeys(s."Dictionary"), 1) [MemberData(nameof(IsAsyncData))] public async Task ImmutableDictionary_Where_Count(bool async) { - await AssertQuery(async, _ => _.Set().Where(s => s.ImmutableDictionary.Count >= 1)); + await AssertQuery(async, ss => ss.Set().Where(s => s.ImmutableDictionary.Count >= 1)); AssertSql( """ -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE array_length(akeys(s."ImmutableDictionary"), 1) >= 1 """); @@ -229,7 +305,7 @@ WHERE array_length(akeys(s."ImmutableDictionary"), 1) >= 1 [MemberData(nameof(IsAsyncData))] public async Task ImmutableDictionary_Select_Count(bool async) { - await AssertQuery(async, _ => _.Set().Select(s => s.ImmutableDictionary.Count)); + await AssertQuery(async, ss => ss.Set().Select(s => s.ImmutableDictionary.Count)); AssertSql( """ SELECT array_length(akeys(s."ImmutableDictionary"), 1) @@ -241,10 +317,10 @@ SELECT array_length(akeys(s."ImmutableDictionary"), 1) [MemberData(nameof(IsAsyncData))] public async Task ImmutableDictionary_Where_IsEmpty(bool async) { - await AssertQuery(async, _ => _.Set().Where(s => !s.ImmutableDictionary.IsEmpty)); + await AssertQuery(async, ss => ss.Set().Where(s => !s.ImmutableDictionary.IsEmpty)); AssertSql( """ -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE array_length(akeys(s."ImmutableDictionary"), 1) <> 0 """); @@ -254,10 +330,10 @@ WHERE array_length(akeys(s."ImmutableDictionary"), 1) <> 0 [MemberData(nameof(IsAsyncData))] public async Task Dictionary_Where_Any(bool async) { - await AssertQuery(async, _ => _.Set().Where(s => s.Dictionary.Any())); + await AssertQuery(async, ss => ss.Set().Where(s => s.Dictionary.Any())); AssertSql( """ -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE array_length(akeys(s."Dictionary"), 1) <> 0 """); @@ -267,10 +343,10 @@ WHERE array_length(akeys(s."Dictionary"), 1) <> 0 [MemberData(nameof(IsAsyncData))] public async Task ImmutableDictionary_Where_Any(bool async) { - await AssertQuery(async, _ => _.Set().Where(s => s.ImmutableDictionary.Any())); + await AssertQuery(async, ss => ss.Set().Where(s => s.ImmutableDictionary.Any())); AssertSql( """ -SELECT s."Id", s."Dictionary", s."DictionaryContainerEntityId", s."ImmutableDictionary", s."NullableDictionary", s."NullableImmutableDictionary" +SELECT s."Id", s."Dictionary", s."ImmutableDictionary" FROM "SomeEntities" AS s WHERE array_length(akeys(s."ImmutableDictionary"), 1) <> 0 """); diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryContainerEntity.cs b/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryContainerEntity.cs deleted file mode 100644 index 9db129448..000000000 --- a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryContainerEntity.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Dictionary; - -public class DictionaryContainerEntity -{ - public int Id { get; set; } - public List DictionaryEntities { get; set; } = null!; -} diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryEntity.cs b/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryEntity.cs deleted file mode 100644 index 8603ec98c..000000000 --- a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryEntity.cs +++ /dev/null @@ -1,17 +0,0 @@ -#nullable enable -using System.Collections.Immutable; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Dictionary; - -public class DictionaryEntity -{ - public int Id { get; set; } - - public Dictionary Dictionary { get; set; } = null!; - - public ImmutableDictionary ImmutableDictionary { get; set; } = null!; - - public Dictionary? NullableDictionary { get; set; } = null!; - - public ImmutableDictionary? NullableImmutableDictionary { get; set; } = null!; -} diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryContext.cs b/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryContext.cs deleted file mode 100644 index 33b98a041..000000000 --- a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryContext.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Dictionary; - -public class DictionaryQueryContext(DbContextOptions options) : PoolableDbContext(options) -{ - public DbSet SomeEntities { get; set; } - public DbSet SomeEntityContainers { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(); - modelBuilder.Entity(); - } - - public static async Task SeedAsync(DictionaryQueryContext context) - { - var arrayEntities = DictionaryQueryData.CreateDictionaryEntities(); - - context.SomeEntities.AddRange(arrayEntities); - context.SomeEntityContainers.Add(new DictionaryContainerEntity() { Id = 1, DictionaryEntities = arrayEntities.ToList() }); - await context.SaveChangesAsync(); - } -} diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryData.cs b/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryData.cs deleted file mode 100644 index 5ee965b4a..000000000 --- a/test/EFCore.PG.FunctionalTests/TestModels/Dictionary/DictionaryQueryData.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Immutable; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Dictionary; - -public class DictionaryQueryData : ISetSource -{ - public IReadOnlyList DictionaryEntities { get; } = CreateDictionaryEntities(); - public IReadOnlyList ContainerEntities { get; } = CreateContainerEntities(); - - public IQueryable Set() - where TEntity : class - { - if (typeof(TEntity) == typeof(DictionaryEntity)) - { - return (IQueryable)DictionaryEntities.AsQueryable(); - } - - if (typeof(TEntity) == typeof(DictionaryContainerEntity)) - { - return (IQueryable)ContainerEntities.AsQueryable(); - } - - throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); - } - - public static IReadOnlyList CreateDictionaryEntities() - => - [ - new() - { - Id = 1, - Dictionary = new() { ["key"] = "value" }, - ImmutableDictionary = new Dictionary { ["key2"] = "value2" }.ToImmutableDictionary(), - }, - new() - { - Id = 2, - Dictionary = new() { ["key"] = "value" }, - ImmutableDictionary = new Dictionary { ["key3"] = "value3" }.ToImmutableDictionary(), - NullableDictionary = new() { ["key2"] = "value" }, - NullableImmutableDictionary = new Dictionary { ["key3"] = "value2" }.ToImmutableDictionary(), - } - ]; - - public static IReadOnlyList CreateContainerEntities() - => [new DictionaryContainerEntity { Id = 1, DictionaryEntities = CreateDictionaryEntities().ToList() }]; -}