From 7733ec8bc0a76c2a8ef673c7c80012e5667a4cdd Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 28 Mar 2023 17:21:52 -0700 Subject: [PATCH] Fix to #30575 - Multiple LeftJoins (GroupJoins) lead to GroupJoin Exception when the same where is used twice Problem is in QueryableMethodNormalizingExpressionVisitor and specifically part where we convert from GroupJoin-SelectMany-DefaultIfEmpty into left join (SelectManyVerifyingExpressionVisitor). We check if the collection selector is correlated, and we do that by looking at parameters in that lambda. Problem is that the affected queries reference outside variable that gets parameterized and that breaks the correlation finding logic. Fix is to check if a parameter has a prefix associated with external parameter when we count correlated parameters. Fixes #30575 --- ...yableMethodNormalizingExpressionVisitor.cs | 3 +- .../Query/ComplexNavigationsQueryTestBase.cs | 134 ++++++++ .../ComplexNavigationsQuerySqlServerTest.cs | 130 ++++++++ ...NavigationsSharedTypeQuerySqlServerTest.cs | 304 ++++++++++++++++++ 4 files changed, 570 insertions(+), 1 deletion(-) diff --git a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs index b8ae88c3639..2537e4f4fbc 100644 --- a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs @@ -774,7 +774,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp protected override Expression VisitParameter(ParameterExpression parameterExpression) { - if (_allowedParameters.Contains(parameterExpression)) + if (_allowedParameters.Contains(parameterExpression) + || parameterExpression.Name?.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal) == true) { return parameterExpression; } diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index cea1656b7f6..529d3213ae0 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -3785,4 +3785,138 @@ public virtual Task Prune_does_not_throw_null_ref(bool async) select l2.Level1_Required_Id).DefaultIfEmpty() from l1 in ss.Set().Where(x => x.Id != ids) select l1); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure(bool async) + { + var prm = 10; + + return AssertQuery( + async, + ss => from l1 in ss.Set() + join l2 in ss.Set() on l1.Id equals l2.Level1_Optional_Id into grouping + from l2 in grouping.Where(x => x.Id != prm).DefaultIfEmpty() + select new { Id1 = l1.Id, Id2 = (int?)l2.Id }, + elementSorter: e => (e.Id1, e.Id2), + elementAsserter: (e, a) => + { + Assert.Equal(e.Id1, a.Id1); + Assert.Equal(e.Id2, a.Id2); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupJoin_SelectMany_with_predicate_using_closure(bool async) + { + var prm = 10; + + return AssertQuery( + async, + ss => from l1 in ss.Set() + join l2 in ss.Set() on l1.Id equals l2.Level1_Optional_Id into grouping + from l2 in grouping.Where(x => x.Id != prm) + select new { Id1 = l1.Id, Id2 = l2.Id }, + elementSorter: e => (e.Id1, e.Id2), + elementAsserter: (e, a) => + { + Assert.Equal(e.Id1, a.Id1); + Assert.Equal(e.Id2, a.Id2); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested(bool async) + { + var prm1 = 10; + var prm2 = 20; + + return AssertQuery( + async, + ss => from l1 in ss.Set() + join l2 in ss.Set() on l1.Id equals l2.Level1_Optional_Id into grouping1 + from l2 in grouping1.Where(x => x.Id != prm1).DefaultIfEmpty() + join l3 in ss.Set() on l2.Id equals l3.Level2_Optional_Id into grouping2 + from l3 in grouping2.Where(x => x.Id != prm2).DefaultIfEmpty() + select new { Id1 = l1.Id, Id2 = (int?)l2.Id, Id3 = (int?)l3.Id }, + elementSorter: e => (e.Id1, e.Id2, e.Id3), + elementAsserter: (e, a) => + { + Assert.Equal(e.Id1, a.Id1); + Assert.Equal(e.Id2, a.Id2); + Assert.Equal(e.Id3, a.Id3); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupJoin_SelectMany_with_predicate_using_closure_nested(bool async) + { + var prm1 = 10; + var prm2 = 20; + + return AssertQuery( + async, + ss => from l1 in ss.Set() + join l2 in ss.Set() on l1.Id equals l2.Level1_Optional_Id into grouping1 + from l2 in grouping1.Where(x => x.Id != prm1) + join l3 in ss.Set() on l2.Id equals l3.Level2_Optional_Id into grouping2 + from l3 in grouping2.Where(x => x.Id != prm2) + select new { Id1 = l1.Id, Id2 = l2.Id, Id3 = l3.Id }, + elementSorter: e => (e.Id1, e.Id2, e.Id3), + elementAsserter: (e, a) => + { + Assert.Equal(e.Id1, a.Id1); + Assert.Equal(e.Id2, a.Id2); + Assert.Equal(e.Id3, a.Id3); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested_same_param(bool async) + { + var prm = 10; + + return AssertQuery( + async, + ss => from l1 in ss.Set() + join l2 in ss.Set() on l1.Id equals l2.Level1_Optional_Id into grouping1 + from l2 in grouping1.Where(x => x.Id != prm).DefaultIfEmpty() + join l3 in ss.Set() on l2.Id equals l3.Level2_Optional_Id into grouping2 + from l3 in grouping2.Where(x => x.Id != prm).DefaultIfEmpty() + select new { Id1 = l1.Id, Id2 = (int?)l2.Id, Id3 = (int?)l3.Id }, + elementSorter: e => (e.Id1, e.Id2, e.Id3), + elementAsserter: (e, a) => + { + Assert.Equal(e.Id1, a.Id1); + Assert.Equal(e.Id2, a.Id2); + Assert.Equal(e.Id3, a.Id3); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupJoin_SelectMany_with_predicate_using_closure_nested_same_param(bool async) + { + var prm = 10; + + return AssertQuery( + async, + ss => from l1 in ss.Set() + join l2 in ss.Set() on l1.Id equals l2.Level1_Optional_Id into grouping1 + from l2 in grouping1.Where(x => x.Id != prm) + join l3 in ss.Set() on l2.Id equals l3.Level2_Optional_Id into grouping2 + from l3 in grouping2.Where(x => x.Id != prm) + select new { Id1 = l1.Id, Id2 = l2.Id, Id3 = l3.Id }, + elementSorter: e => (e.Id1, e.Id2, e.Id3), + elementAsserter: (e, a) => + { + Assert.Equal(e.Id1, a.Id1); + Assert.Equal(e.Id2, a.Id2); + Assert.Equal(e.Id3, a.Id3); + }); + } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index d55fd460371..c8ce6aa41b7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -4576,6 +4576,136 @@ public override async Task Multiple_required_navigation_with_EF_Property_Include AssertSql(); } + public override async Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure(bool async) + { + await base.GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], [t].[Id] AS [Id2] +FROM [LevelOne] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[Level1_Optional_Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] <> @__prm_0 +) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_with_predicate_using_closure(bool async) + { + await base.GroupJoin_SelectMany_with_predicate_using_closure(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], [t].[Id] AS [Id2] +FROM [LevelOne] AS [l] +INNER JOIN ( + SELECT [l0].[Id], [l0].[Level1_Optional_Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] <> @__prm_0 +) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested(bool async) + { + await base.GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested(async); + + AssertSql( +""" +@__prm1_0='10' +@__prm2_1='20' + +SELECT [l].[Id] AS [Id1], [t].[Id] AS [Id2], [t0].[Id] AS [Id3] +FROM [LevelOne] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[Level1_Optional_Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] <> @__prm1_0 +) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] +LEFT JOIN ( + SELECT [l1].[Id], [l1].[Level2_Optional_Id] + FROM [LevelThree] AS [l1] + WHERE [l1].[Id] <> @__prm2_1 +) AS [t0] ON [t].[Id] = [t0].[Level2_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_with_predicate_using_closure_nested(bool async) + { + await base.GroupJoin_SelectMany_with_predicate_using_closure_nested(async); + + AssertSql( +""" +@__prm1_0='10' +@__prm2_1='20' + +SELECT [l].[Id] AS [Id1], [t].[Id] AS [Id2], [t0].[Id] AS [Id3] +FROM [LevelOne] AS [l] +INNER JOIN ( + SELECT [l0].[Id], [l0].[Level1_Optional_Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] <> @__prm1_0 +) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] +INNER JOIN ( + SELECT [l1].[Id], [l1].[Level2_Optional_Id] + FROM [LevelThree] AS [l1] + WHERE [l1].[Id] <> @__prm2_1 +) AS [t0] ON [t].[Id] = [t0].[Level2_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested_same_param(bool async) + { + await base.GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested_same_param(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], [t].[Id] AS [Id2], [t0].[Id] AS [Id3] +FROM [LevelOne] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[Level1_Optional_Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] <> @__prm_0 +) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] +LEFT JOIN ( + SELECT [l1].[Id], [l1].[Level2_Optional_Id] + FROM [LevelThree] AS [l1] + WHERE [l1].[Id] <> @__prm_0 +) AS [t0] ON [t].[Id] = [t0].[Level2_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_with_predicate_using_closure_nested_same_param(bool async) + { + await base.GroupJoin_SelectMany_with_predicate_using_closure_nested_same_param(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], [t].[Id] AS [Id2], [t0].[Id] AS [Id3] +FROM [LevelOne] AS [l] +INNER JOIN ( + SELECT [l0].[Id], [l0].[Level1_Optional_Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] <> @__prm_0 +) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] +INNER JOIN ( + SELECT [l1].[Id], [l1].[Level2_Optional_Id] + FROM [LevelThree] AS [l1] + WHERE [l1].[Id] <> @__prm_0 +) AS [t0] ON [t].[Id] = [t0].[Level2_Optional_Id] +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index 1c2cbc0bf24..fb1b4127769 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -7857,6 +7857,310 @@ public override async Task Multiple_required_navigation_with_EF_Property_Include AssertSql(); } + public override async Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure(bool async) + { + await base.GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END AS [Id2] +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [t].[Id] AS [Id0], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t] ON [l0].[Id] = CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END + WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND (CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END <> @__prm_0 OR CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END IS NULL) +) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_with_predicate_using_closure(bool async) + { + await base.GroupJoin_SelectMany_with_predicate_using_closure(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END AS [Id2] +FROM [Level1] AS [l] +INNER JOIN ( + SELECT [t].[Id] AS [Id0], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t] ON [l0].[Id] = CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END + WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND (CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END <> @__prm_0 OR CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END IS NULL) +) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested(bool async) + { + await base.GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested(async); + + AssertSql( +""" +@__prm1_0='10' +@__prm2_1='20' + +SELECT [l].[Id] AS [Id1], CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END AS [Id2], CASE + WHEN [t1].[Level2_Required_Id] IS NOT NULL AND [t1].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t1].[Id1] +END AS [Id3] +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [t].[Id] AS [Id0], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t] ON [l0].[Id] = CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END + WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND (CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END <> @__prm1_0 OR CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END IS NULL) +) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] +LEFT JOIN ( + SELECT [t3].[Id] AS [Id1], [t3].[Level2_Optional_Id], [t3].[Level2_Required_Id], [t3].[OneToMany_Required_Inverse3Id] + FROM [Level1] AS [l2] + LEFT JOIN ( + SELECT [l3].[Id], [l3].[OneToOne_Required_PK_Date], [l3].[Level1_Required_Id], [l3].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l3] + WHERE [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t2] ON [l2].[Id] = CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END + LEFT JOIN ( + SELECT [l4].[Id], [l4].[Level2_Optional_Id], [l4].[Level2_Required_Id], [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 [t3] ON CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END = CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END + WHERE [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL AND (CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END <> @__prm2_1 OR CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END IS NULL) +) AS [t1] ON CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END = [t1].[Level2_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_with_predicate_using_closure_nested(bool async) + { + await base.GroupJoin_SelectMany_with_predicate_using_closure_nested(async); + + AssertSql( +""" +@__prm1_0='10' +@__prm2_1='20' + +SELECT [l].[Id] AS [Id1], CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END AS [Id2], CASE + WHEN [t1].[Level2_Required_Id] IS NOT NULL AND [t1].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t1].[Id1] +END AS [Id3] +FROM [Level1] AS [l] +INNER JOIN ( + SELECT [t].[Id] AS [Id0], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t] ON [l0].[Id] = CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END + WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND (CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END <> @__prm1_0 OR CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END IS NULL) +) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] +INNER JOIN ( + SELECT [t3].[Id] AS [Id1], [t3].[Level2_Optional_Id], [t3].[Level2_Required_Id], [t3].[OneToMany_Required_Inverse3Id] + FROM [Level1] AS [l2] + LEFT JOIN ( + SELECT [l3].[Id], [l3].[OneToOne_Required_PK_Date], [l3].[Level1_Required_Id], [l3].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l3] + WHERE [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t2] ON [l2].[Id] = CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END + LEFT JOIN ( + SELECT [l4].[Id], [l4].[Level2_Optional_Id], [l4].[Level2_Required_Id], [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 [t3] ON CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END = CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END + WHERE [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL AND (CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END <> @__prm2_1 OR CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END IS NULL) +) AS [t1] ON CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END = [t1].[Level2_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested_same_param(bool async) + { + await base.GroupJoin_SelectMany_DefaultIfEmpty_with_predicate_using_closure_nested_same_param(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END AS [Id2], CASE + WHEN [t1].[Level2_Required_Id] IS NOT NULL AND [t1].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t1].[Id1] +END AS [Id3] +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [t].[Id] AS [Id0], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t] ON [l0].[Id] = CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END + WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND (CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END <> @__prm_0 OR CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END IS NULL) +) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] +LEFT JOIN ( + SELECT [t3].[Id] AS [Id1], [t3].[Level2_Optional_Id], [t3].[Level2_Required_Id], [t3].[OneToMany_Required_Inverse3Id] + FROM [Level1] AS [l2] + LEFT JOIN ( + SELECT [l3].[Id], [l3].[OneToOne_Required_PK_Date], [l3].[Level1_Required_Id], [l3].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l3] + WHERE [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t2] ON [l2].[Id] = CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END + LEFT JOIN ( + SELECT [l4].[Id], [l4].[Level2_Optional_Id], [l4].[Level2_Required_Id], [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 [t3] ON CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END = CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END + WHERE [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL AND (CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END <> @__prm_0 OR CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END IS NULL) +) AS [t1] ON CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END = [t1].[Level2_Optional_Id] +"""); + } + + public override async Task GroupJoin_SelectMany_with_predicate_using_closure_nested_same_param(bool async) + { + await base.GroupJoin_SelectMany_with_predicate_using_closure_nested_same_param(async); + + AssertSql( +""" +@__prm_0='10' + +SELECT [l].[Id] AS [Id1], CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END AS [Id2], CASE + WHEN [t1].[Level2_Required_Id] IS NOT NULL AND [t1].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t1].[Id1] +END AS [Id3] +FROM [Level1] AS [l] +INNER JOIN ( + SELECT [t].[Id] AS [Id0], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t] ON [l0].[Id] = CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END + WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND (CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END <> @__prm_0 OR CASE + WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] + END IS NULL) +) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] +INNER JOIN ( + SELECT [t3].[Id] AS [Id1], [t3].[Level2_Optional_Id], [t3].[Level2_Required_Id], [t3].[OneToMany_Required_Inverse3Id] + FROM [Level1] AS [l2] + LEFT JOIN ( + SELECT [l3].[Id], [l3].[OneToOne_Required_PK_Date], [l3].[Level1_Required_Id], [l3].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l3] + WHERE [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t2] ON [l2].[Id] = CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END + LEFT JOIN ( + SELECT [l4].[Id], [l4].[Level2_Optional_Id], [l4].[Level2_Required_Id], [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 [t3] ON CASE + WHEN [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t2].[Id] + END = CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END + WHERE [t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL AND (CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END <> @__prm_0 OR CASE + WHEN [t3].[Level2_Required_Id] IS NOT NULL AND [t3].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t3].[Id] + END IS NULL) +) AS [t1] ON CASE + WHEN [t0].[OneToOne_Required_PK_Date] IS NOT NULL AND [t0].[Level1_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t0].[Id0] +END = [t1].[Level2_Optional_Id] +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); }