From 03541adb3b3ec5cdbe19d59d5d5439fd62619f5a Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 2 May 2023 13:02:24 -0700 Subject: [PATCH] Fix to #30575 - Multiple LeftJoins (GroupJoins) lead to GroupJoin Exception when the same where is used twice (#30794) 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 add a step that scans entire query and identifies external parameters before we try to normalize GJSMDIE into LeftJoins, so that those external parameters are not counted as correlated. Fixes #30575 --- ...yableMethodNormalizingExpressionVisitor.cs | 18 +- .../Query/ComplexNavigationsQueryTestBase.cs | 135 ++++++++ .../ComplexNavigationsQuerySqlServerTest.cs | 130 ++++++++ ...NavigationsSharedTypeQuerySqlServerTest.cs | 304 ++++++++++++++++++ 4 files changed, 585 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs index 659fd7cdb2d..279fd693946 100644 --- a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs @@ -675,6 +675,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp private sealed class SelectManyVerifyingExpressionVisitor : ExpressionVisitor { + private static readonly bool UseOldBehavior30575 + = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue30575", out var enabled30575) && enabled30575; + private readonly List _allowedParameters = new(); private readonly ISet _allowedMethods = new HashSet { nameof(Queryable.Where), nameof(Queryable.AsQueryable) }; @@ -774,9 +777,20 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp protected override Expression VisitParameter(ParameterExpression parameterExpression) { - if (_allowedParameters.Contains(parameterExpression)) + if (!UseOldBehavior30575) { - return parameterExpression; + if (_allowedParameters.Contains(parameterExpression) + || parameterExpression.Name?.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal) == true) + { + return parameterExpression; + } + } + else + { + if (_allowedParameters.Contains(parameterExpression)) + { + return parameterExpression; + } } if (parameterExpression == _rootParameter) diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index cea1656b7f6..3a0af8d4125 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -3785,4 +3785,139 @@ 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 85e32974927..414de3d9fb3 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 ab52e848f35..faf0ea49e10 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); }