From 4542fc84b53e0a83c4d2e4c9c57aba8818847593 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 23 Apr 2023 02:05:56 +0200 Subject: [PATCH] Small tweak to support primitive collections within owned JSON entities Closes #28688 --- ...yableMethodTranslatingExpressionVisitor.cs | 25 +++++++++------ ...itiveCollectionsQueryRelationalTestBase.cs | 31 +++++++++++++++++++ ...dPrimitiveCollectionsQuerySqlServerTest.cs | 14 +++++++++ ...aredPrimitiveCollectionsQuerySqliteTest.cs | 13 ++++++++ 4 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index a5bcaa0dcdf..e043297df61 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -255,19 +255,26 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp var translated = base.VisitMethodCall(methodCallExpression); + // Attempt to translate access into a primitive collection property if (translated == QueryCompilationContext.NotTranslatedExpression && _sqlTranslator.TryTranslatePropertyAccess(methodCallExpression, out var propertyAccessExpression) - && propertyAccessExpression is ColumnExpression + && propertyAccessExpression is { TypeMapping.ElementTypeMapping: RelationalTypeMapping elementTypeMapping - } columnExpression - && TranslateCollection( - columnExpression, - elementTypeMapping, - columnExpression.Name[..1].ToLowerInvariant()) - is { } primitiveCollectionTranslation) - { - return primitiveCollectionTranslation; + } collectionPropertyAccessExpression) + { + var tableAlias = collectionPropertyAccessExpression switch + { + ColumnExpression c => c.Name[..1].ToLowerInvariant(), + JsonScalarExpression { Path: [.., { PropertyName: string propertyName }] } => propertyName[..1].ToLowerInvariant(), + _ => "j" + }; + + if (TranslateCollection(collectionPropertyAccessExpression, elementTypeMapping, tableAlias) is + { } primitiveCollectionTranslation) + { + return primitiveCollectionTranslation; + } } return translated; diff --git a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs index 3ae808f294b..43da781f406 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs @@ -10,6 +10,37 @@ public abstract class NonSharedPrimitiveCollectionsQueryRelationalTestBase : Non public override Task Array_of_byte() => AssertTranslationFailed(() => TestArray((byte)1, (byte)2)); + [ConditionalFact] + public virtual async Task Column_collection_inside_json_owned_entity() + { + var contextFactory = await InitializeAsync( + onModelCreating: mb => mb.Entity().OwnsOne(t => t.Owned, b => b.ToJson()), + seed: context => + { + context.AddRange( + new TestOwner { Owned = new TestOwned { Strings = new[] { "foo", "bar" } } }, + new TestOwner { Owned = new TestOwned { Strings = new[] { "baz" } } }); + context.SaveChanges(); + }); + + await using var context = contextFactory.CreateContext(); + + var result = await context.Set().SingleAsync(o => o.Owned.Strings.Count() == 2); + Assert.Equivalent(new[] { "foo", "bar" }, result.Owned.Strings); + } + + protected class TestOwner + { + public int Id { get; set; } + public TestOwned Owned { get; set; } + } + + [Owned] + protected class TestOwned + { + public string[] Strings { get; set; } + } + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs index afb432cb43e..fe10dc02a9a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs @@ -254,6 +254,20 @@ SELECT COUNT(*) """); } + public override async Task Column_collection_inside_json_owned_entity() + { + await base.Column_collection_inside_json_owned_entity(); + + AssertSql( +""" +SELECT TOP(2) [t].[Id], [t].[Owned] +FROM [TestOwner] AS [t] +WHERE ( + SELECT COUNT(*) + FROM OpenJson(JSON_VALUE([t].[Owned], '$.Strings')) AS [s]) = 2 +"""); + } + [ConditionalFact] public virtual async Task Same_parameter_with_different_type_mappings() { diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs index fdd59b9dfde..c5dd4d5ba7f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs @@ -214,6 +214,19 @@ public override async Task Array_of_geometry_is_not_supported() #endregion Support for specific element types + public override async Task Column_collection_inside_json_owned_entity() + { + await base.Column_collection_inside_json_owned_entity(); + + AssertSql( +""" +SELECT "t"."Id", "t"."Owned" +FROM "TestOwner" AS "t" +WHERE json_array_length(json_extract("t"."Owned", '$.Strings')) = 2 +LIMIT 2 +"""); + } + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; }