From 29fbbe15cca46ea9ca004ef3b48827db3cc0361e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D1=83=D1=80=D0=B0=D1=82=D0=BE=D0=B2=20=D0=90=D1=80?= =?UTF-8?q?=D1=82=D1=83=D1=80=20=D0=A4=D0=B0=D0=BD=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=87?= Date: Fri, 28 Jun 2024 18:01:40 +0500 Subject: [PATCH] fix --- ...yableMethodTranslatingExpressionVisitor.cs | 16 +++++- .../Query/NorthwindSelectQueryTestBase.cs | 28 ++++++++++ .../ComplexNavigationsQuerySqlServerTest.cs | 26 +++++---- ...NavigationsSharedTypeQuerySqlServerTest.cs | 54 ++++++++++--------- .../NorthwindSelectQuerySqlServerTest.cs | 28 ++++++++++ .../Query/NorthwindSelectQuerySqliteTest.cs | 29 ++++++++++ 6 files changed, 145 insertions(+), 36 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 20115136354..49be1536b70 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1138,6 +1138,7 @@ private sealed class CorrelationFindingExpressionVisitor : ExpressionVisitor private ParameterExpression? _outerParameter; private bool _correlated; private bool _defaultIfEmpty; + private bool _topLevelSelectMany; public (LambdaExpression, bool, bool) IsCorrelated(LambdaExpression lambdaExpression) { @@ -1146,6 +1147,7 @@ private sealed class CorrelationFindingExpressionVisitor : ExpressionVisitor _correlated = false; _defaultIfEmpty = false; + _topLevelSelectMany = true; _outerParameter = lambdaExpression.Parameters[0]; var result = Visit(lambdaExpression.Body); @@ -1165,8 +1167,18 @@ protected override Expression VisitParameter(ParameterExpression parameterExpres protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { - if (methodCallExpression.Method.IsGenericMethod - && methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.DefaultIfEmptyWithoutArgument) + if (methodCallExpression.Method.Name == nameof(Queryable.SelectMany)) + { + var levelBuffer = _topLevelSelectMany; + _topLevelSelectMany = false; + var result = base.VisitMethodCall(methodCallExpression); + _topLevelSelectMany = levelBuffer; + return result; + } + + if (_topLevelSelectMany + && methodCallExpression.Method.IsGenericMethod + && methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.DefaultIfEmptyWithoutArgument) { _defaultIfEmpty = true; return Visit(methodCallExpression.Arguments[0]); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index ce0c021e3ca..334b3535eb9 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -2454,4 +2454,32 @@ public virtual Task Set_operation_in_pending_collection(bool async) }).Take(5), assertOrder: true, elementAsserter: (e, a) => AssertCollection(e.OrderIds, a.OrderIds, elementSorter: ee => ee)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nested_SelectMany_with_DefaultIfEmpty(bool async) => AssertQuery( + async, + ss => ss.Set().SelectMany( + ownerCustomer => ss.Set() + .Where(neighbor => ownerCustomer.City == neighbor.City) + .Select(x => new { x.City, x.Address }).SelectMany( + customer => ss.Set() + .Where(branch => branch.Address == customer.Address) + .DefaultIfEmpty())) + .Select(x => new { x.City, x.Address })); + + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nested_SelectMany_with_anonymous_type_and_DefaultIfEmpty(bool async) => AssertQuery( + async, + ss => ss.Set().SelectMany( + ownerCustomer => ss.Set() + .Where(neighbor => ownerCustomer.City == neighbor.City) + .Select(x => new { x.City, x.Address }).SelectMany( + customer => ss.Set() + .Where(branch => branch.Address == customer.Address) + .DefaultIfEmpty(), (left, right) => new { left, right }), + (left, right) => new { left, right }) + .Select(x => new { x.left.City, x.left.Address })); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 45d923f908e..1b8a39723a2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -3853,24 +3853,30 @@ FROM [LevelOne] AS [l] public override async Task Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(bool async) { // DefaultIfEmpty on child collection. Issue #19095. - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( async () => await base.Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(async)); AssertSql( """ SELECT [s0].[l1Name], [s0].[l2Name], [s0].[l3Name] FROM [LevelOne] AS [l] -OUTER APPLY ( +CROSS APPLY ( SELECT [s].[l1Name], [s].[l2Name], [s].[l3Name] - FROM [LevelTwo] AS [l0] - LEFT JOIN [LevelThree] AS [l1] ON [l0].[Id] = [l1].[Id] - CROSS APPLY ( - SELECT [l].[Name] AS [l1Name], [l1].[Name] AS [l2Name], [l3].[Name] AS [l3Name] - FROM [LevelFour] AS [l2] - LEFT JOIN [LevelThree] AS [l3] ON [l2].[OneToOne_Optional_PK_Inverse4Id] = [l3].[Id] - WHERE [l1].[Id] IS NOT NULL AND [l1].[Id] = [l2].[OneToMany_Optional_Inverse4Id] + FROM ( + SELECT 1 AS empty + ) AS [e] + LEFT JOIN ( + SELECT [l0].[Id] + FROM [LevelTwo] AS [l0] + WHERE [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] + ) AS [l1] ON 1 = 1 + LEFT JOIN [LevelThree] AS [l2] ON [l1].[Id] = [l2].[Id] + OUTER APPLY ( + SELECT [l].[Name] AS [l1Name], [l2].[Name] AS [l2Name], [l4].[Name] AS [l3Name] + FROM [LevelFour] AS [l3] + LEFT JOIN [LevelThree] AS [l4] ON [l3].[OneToOne_Optional_PK_Inverse4Id] = [l4].[Id] + WHERE [l2].[Id] IS NOT NULL AND [l2].[Id] = [l3].[OneToMany_Optional_Inverse4Id] ) AS [s] - WHERE [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] ) AS [s0] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index fb0cc9ffd47..f8d88c4edf4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -440,44 +440,50 @@ END IS NULL public override async Task Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(bool async) { // DefaultIfEmpty on child collection. Issue #19095. - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( async () => await base.Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(async)); AssertSql( """ SELECT [s0].[l1Name], [s0].[l2Name], [s0].[l3Name] FROM [Level1] AS [l] -OUTER APPLY ( +CROSS APPLY ( SELECT [s].[l1Name], [s].[l2Name], [s].[l3Name] - FROM [Level1] AS [l0] + FROM ( + SELECT 1 AS empty + ) AS [e] LEFT JOIN ( - SELECT [l1].[Id], [l1].[Level2_Required_Id], [l1].[Level3_Name], [l1].[OneToMany_Required_Inverse3Id] - FROM [Level1] AS [l1] - WHERE [l1].[Level2_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse3Id] IS NOT NULL - ) AS [l2] ON CASE - WHEN [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l0].[Id] + SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Required_Id], [l0].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] + ) AS [l1] ON 1 = 1 + LEFT JOIN ( + SELECT [l2].[Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Required_Inverse3Id] + FROM [Level1] AS [l2] + WHERE [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL + ) AS [l3] ON CASE + WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l1].[Id] END = CASE - WHEN [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l2].[Id] + WHEN [l3].[Level2_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l3].[Id] END - CROSS APPLY ( - SELECT [l].[Name] AS [l1Name], [l2].[Level3_Name] AS [l2Name], [l5].[Level3_Name] AS [l3Name] - FROM [Level1] AS [l3] + OUTER APPLY ( + SELECT [l].[Name] AS [l1Name], [l3].[Level3_Name] AS [l2Name], [l6].[Level3_Name] AS [l3Name] + FROM [Level1] AS [l4] LEFT JOIN ( - SELECT [l4].[Id], [l4].[Level2_Required_Id], [l4].[Level3_Name], [l4].[OneToMany_Required_Inverse3Id] - FROM [Level1] AS [l4] - WHERE [l4].[Level2_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse3Id] IS NOT NULL - ) AS [l5] ON [l3].[OneToOne_Optional_PK_Inverse4Id] = CASE - WHEN [l5].[Level2_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l5].[Id] + SELECT [l5].[Id], [l5].[Level2_Required_Id], [l5].[Level3_Name], [l5].[OneToMany_Required_Inverse3Id] + FROM [Level1] AS [l5] + WHERE [l5].[Level2_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse3Id] IS NOT NULL + ) AS [l6] ON [l4].[OneToOne_Optional_PK_Inverse4Id] = CASE + WHEN [l6].[Level2_Required_Id] IS NOT NULL AND [l6].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l6].[Id] END - WHERE [l3].[Level3_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse4Id] IS NOT NULL AND CASE - WHEN [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l2].[Id] + WHERE [l4].[Level3_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse4Id] IS NOT NULL AND CASE + WHEN [l3].[Level2_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l3].[Id] END IS NOT NULL AND (CASE - WHEN [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l2].[Id] - END = [l3].[OneToMany_Optional_Inverse4Id] OR (CASE - WHEN [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l2].[Id] - END IS NULL AND [l3].[OneToMany_Optional_Inverse4Id] IS NULL)) + WHEN [l3].[Level2_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l3].[Id] + END = [l4].[OneToMany_Optional_Inverse4Id] OR (CASE + WHEN [l3].[Level2_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [l3].[Id] + END IS NULL AND [l4].[OneToMany_Optional_Inverse4Id] IS NULL)) ) AS [s] - WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] ) AS [s0] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index 67f7f8464cd..8eae5943feb 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -2771,6 +2771,34 @@ ORDER BY [c0].[CustomerID] """); } + public override async Task Nested_SelectMany_with_DefaultIfEmpty(bool async) + { + await base.Nested_SelectMany_with_DefaultIfEmpty(async); + + AssertSql( + @"SELECT [s].[City], [s].[Address] +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c1].[Address], [c1].[City], [c0].[City] AS [City0] + FROM [Customers] AS [c0] + LEFT JOIN [Customers] AS [c1] ON [c0].[Address] = [c1].[Address] +) AS [s] ON [c].[City] = [s].[City0]"); + } + + public override async Task Nested_SelectMany_with_anonymous_type_and_DefaultIfEmpty(bool async) + { + await base.Nested_SelectMany_with_anonymous_type_and_DefaultIfEmpty(async); + + AssertSql( + @"SELECT [c].[City], [c].[Address] +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[City] + FROM [Customers] AS [c0] + LEFT JOIN [Customers] AS [c1] ON [c0].[Address] = [c1].[Address] +) AS [s] ON [c].[City] = [s].[City]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs index 91cbbc47835..c349464482a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs @@ -153,6 +153,35 @@ SELECT rtrim(rtrim(strftime('%H:%M:%f', "o"."OrderDate"), '0'), '.') """); } + + public override async Task Nested_SelectMany_with_DefaultIfEmpty(bool async) + { + await base.Nested_SelectMany_with_DefaultIfEmpty(async); + + AssertSql( + @"SELECT ""s"".""City"", ""s"".""Address"" +FROM ""Customers"" AS ""c"" +INNER JOIN ( + SELECT ""c1"".""Address"", ""c1"".""City"", ""c0"".""City"" AS ""City0"" + FROM ""Customers"" AS ""c0"" + LEFT JOIN ""Customers"" AS ""c1"" ON ""c0"".""Address"" = ""c1"".""Address"" +) AS ""s"" ON ""c"".""City"" = ""s"".""City0"""); + } + + public override async Task Nested_SelectMany_with_anonymous_type_and_DefaultIfEmpty(bool async) + { + await base.Nested_SelectMany_with_anonymous_type_and_DefaultIfEmpty(async); + + AssertSql( + @"SELECT ""c"".""City"", ""c"".""Address"" +FROM ""Customers"" AS ""c"" +INNER JOIN ( + SELECT ""c0"".""City"" + FROM ""Customers"" AS ""c0"" + LEFT JOIN ""Customers"" AS ""c1"" ON ""c0"".""Address"" = ""c1"".""Address"" +) AS ""s"" ON ""c"".""City"" = ""s"".""City"""); + } + public override async Task SelectMany_with_collection_being_correlated_subquery_which_references_non_mapped_properties_from_inner_and_outer_entity( bool async)