From 73f4269127f1318d8226ad97040499206b2947f6 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 15 Aug 2019 12:20:12 -0700 Subject: [PATCH] Query: Convert lateral joins into predicate joins only when appropriate - Validate inner select expression after extracting join predicate that it does not contain reference to outer. - Also apply same logic for generating collections in projection. - Identify DefaultIfEmpty in collectionSelector of SelectMany accurately. This fix up a lot of N+1 evaluation queries. Resolves #17112 Resolves #16311 --- ...yableMethodTranslatingExpressionVisitor.cs | 43 +-- .../Query/SqlExpressions/SelectExpression.cs | 139 ++++++--- .../Query/SimpleQueryCosmosTest.cs | 24 ++ .../Query/SimpleQueryInMemoryTest.cs | 24 ++ .../Query/GearsOfWarQueryTestBase.cs | 14 +- .../Query/SimpleQueryTestBase.Select.cs | 66 ++++- .../Query/SimpleQueryTestBase.cs | 6 +- .../Query/AsyncSimpleQuerySqlServerTest.cs | 2 +- .../Query/GearsOfWarQuerySqlServerTest.cs | 266 ++++++------------ .../Query/SimpleQuerySqlServerTest.Select.cs | 66 ++++- .../Query/SimpleQuerySqlServerTest.cs | 7 +- .../Query/GearsOfWarQuerySqliteTest.cs | 15 + .../Query/SimpleQuerySqliteTest.cs | 13 + 13 files changed, 421 insertions(+), 264 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 389676feeb9..66fbb112299 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -722,19 +722,11 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s protected override ShapedQueryExpression TranslateSelectMany( ShapedQueryExpression source, LambdaExpression collectionSelector, LambdaExpression resultSelector) { - var defaultIfEmpty = false; - if (collectionSelector.Body is MethodCallExpression collectionEndingMethod - && collectionEndingMethod.Method.IsGenericMethod - && collectionEndingMethod.Method.GetGenericMethodDefinition() == QueryableMethods.DefaultIfEmptyWithoutArgument) - { - defaultIfEmpty = true; - collectionSelector = Expression.Lambda(collectionEndingMethod.Arguments[0], collectionSelector.Parameters); - } - - var correlated = new CorrelationFindingExpressionVisitor().IsCorrelated(collectionSelector); + var (newCollectionSelector, correlated, defaultIfEmpty) + = new CorrelationFindingExpressionVisitor().IsCorrelated(collectionSelector); if (correlated) { - var collectionSelectorBody = RemapLambdaBody(source, collectionSelector); + var collectionSelectorBody = RemapLambdaBody(source, newCollectionSelector); if (Visit(collectionSelectorBody) is ShapedQueryExpression inner) { var transparentIdentifierType = TransparentIdentifierFactory.Create( @@ -763,7 +755,7 @@ protected override ShapedQueryExpression TranslateSelectMany( } else { - if (Visit(collectionSelector.Body) is ShapedQueryExpression inner) + if (Visit(newCollectionSelector.Body) is ShapedQueryExpression inner) { if (defaultIfEmpty) { @@ -791,28 +783,43 @@ protected override ShapedQueryExpression TranslateSelectMany( private class CorrelationFindingExpressionVisitor : ExpressionVisitor { private ParameterExpression _outerParameter; - private bool _isCorrelated; + private bool _correlated; + private bool _defaultIfEmpty; - public bool IsCorrelated(LambdaExpression lambdaExpression) + public (LambdaExpression, bool, bool) IsCorrelated(LambdaExpression lambdaExpression) { Debug.Assert(lambdaExpression.Parameters.Count == 1, "Multiparameter lambda passed to CorrelationFindingExpressionVisitor"); - _isCorrelated = false; + + _correlated = false; + _defaultIfEmpty = false; _outerParameter = lambdaExpression.Parameters[0]; - Visit(lambdaExpression.Body); + var result = Visit(lambdaExpression.Body); - return _isCorrelated; + return (Expression.Lambda(result, _outerParameter), _correlated, _defaultIfEmpty); } protected override Expression VisitParameter(ParameterExpression parameterExpression) { if (parameterExpression == _outerParameter) { - _isCorrelated = true; + _correlated = true; } return base.VisitParameter(parameterExpression); } + + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Method.IsGenericMethod + && methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.DefaultIfEmptyWithoutArgument) + { + _defaultIfEmpty = true; + return Visit(methodCallExpression.Arguments[0]); + } + + return base.VisitMethodCall(methodCallExpression); + } } protected override ShapedQueryExpression TranslateSelectMany(ShapedQueryExpression source, LambdaExpression selector) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index c7ad425fe85..ef69d44f5e1 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -761,53 +761,58 @@ public Expression ApplyCollectionJoin( } var joinPredicate = TryExtractJoinKey(innerSelectExpression); - if (joinPredicate != null) - { - if (innerSelectExpression.Offset != null - || innerSelectExpression.Limit != null - || innerSelectExpression.IsDistinct - || innerSelectExpression.Predicate != null - || innerSelectExpression.Tables.Count > 1 - || innerSelectExpression.GroupBy.Count > 1) - { - var sqlRemappingVisitor = new SqlRemappingVisitor(innerSelectExpression.PushdownIntoSubquery(), - (SelectExpression)innerSelectExpression.Tables[0]); - joinPredicate = sqlRemappingVisitor.Remap(joinPredicate); - } - - var leftJoinExpression = new LeftJoinExpression(innerSelectExpression.Tables.Single(), joinPredicate); - _tables.Add(leftJoinExpression); + var containsOuterReference = new SelectExpressionCorrelationFindingExpressionVisitor(Tables) + .ContainsOuterReference(innerSelectExpression); + if (containsOuterReference && joinPredicate != null) + { + innerSelectExpression.ApplyPredicate(joinPredicate); + joinPredicate = null; + } - foreach (var ordering in innerSelectExpression.Orderings) - { - AppendOrdering(ordering.Update(MakeNullable(ordering.Expression))); - } + if (innerSelectExpression.Offset != null + || innerSelectExpression.Limit != null + || innerSelectExpression.IsDistinct + || innerSelectExpression.Predicate != null + || innerSelectExpression.Tables.Count > 1 + || innerSelectExpression.GroupBy.Count > 1) + { + var sqlRemappingVisitor = new SqlRemappingVisitor(innerSelectExpression.PushdownIntoSubquery(), + (SelectExpression)innerSelectExpression.Tables[0]); + joinPredicate = sqlRemappingVisitor.Remap(joinPredicate); + } - var indexOffset = _projection.Count; - foreach (var projection in innerSelectExpression.Projection) - { - AddToProjection(MakeNullable(projection.Expression)); - } + var joinExpression = joinPredicate == null + ? (TableExpressionBase)new LeftJoinLateralExpression(innerSelectExpression.Tables.Single()) + : new LeftJoinExpression(innerSelectExpression.Tables.Single(), joinPredicate); + _tables.Add(joinExpression); - foreach (var identifier in innerSelectExpression._identifier.Concat(innerSelectExpression._childIdentifiers)) - { - var updatedColumn = MakeNullable(identifier); - _childIdentifiers.Add(updatedColumn); - AppendOrdering(new OrderingExpression(updatedColumn, ascending: true)); - } + foreach (var ordering in innerSelectExpression.Orderings) + { + AppendOrdering(ordering.Update(MakeNullable(ordering.Expression))); + } - var shaperRemapper = new ShaperRemappingExpressionVisitor(this, innerSelectExpression, indexOffset); - innerShaper = shaperRemapper.Visit(innerShaper); - selfIdentifier = shaperRemapper.Visit(selfIdentifier); + var indexOffset = _projection.Count; + foreach (var projection in innerSelectExpression.Projection) + { + AddToProjection(MakeNullable(projection.Expression)); + } - return new RelationalCollectionShaperExpression( - collectionId, parentIdentifier, outerIdentifier, selfIdentifier, innerShaper, navigation, elementType); + foreach (var identifier in innerSelectExpression._identifier.Concat(innerSelectExpression._childIdentifiers)) + { + var updatedColumn = MakeNullable(identifier); + _childIdentifiers.Add(updatedColumn); + AppendOrdering(new OrderingExpression(updatedColumn, ascending: true)); } - throw new InvalidOperationException("CollectionJoin: Unable to identify correlation predicate to convert to Left Join"); + var shaperRemapper = new ShaperRemappingExpressionVisitor(this, innerSelectExpression, indexOffset); + innerShaper = shaperRemapper.Visit(innerShaper); + selfIdentifier = shaperRemapper.Visit(selfIdentifier); + + return new RelationalCollectionShaperExpression( + collectionId, parentIdentifier, outerIdentifier, selfIdentifier, innerShaper, navigation, elementType); } - private SqlExpression MakeNullable(SqlExpression sqlExpression) + private static SqlExpression MakeNullable(SqlExpression sqlExpression) => sqlExpression is ColumnExpression column ? column.MakeNullable() : sqlExpression; private Expression GetIdentifierAccessor(IEnumerable identifyingProjection) @@ -882,7 +887,9 @@ private object GetProjectionIndex(ProjectionBindingExpression projectionBindingE private SqlExpression TryExtractJoinKey(SelectExpression selectExpression) { - if (selectExpression.Predicate != null) + if (selectExpression.Limit == null + && selectExpression.Offset == null + && selectExpression.Predicate != null) { var joinPredicate = TryExtractJoinKey(selectExpression, selectExpression.Predicate, out var predicate); selectExpression.Predicate = predicate; @@ -962,6 +969,44 @@ private bool ContainsTableReference(TableExpressionBase table) ? ((SelectExpression)Tables[0]).ContainsTableReference(table) : Tables.Any(te => ReferenceEquals(te is JoinExpressionBase jeb ? jeb.Table : te, table)); + private class SelectExpressionCorrelationFindingExpressionVisitor : ExpressionVisitor + { + private readonly IReadOnlyList _tables; + private bool _containsOuterReference; + + public SelectExpressionCorrelationFindingExpressionVisitor(IReadOnlyList tables) + { + _tables = tables; + } + + public bool ContainsOuterReference(SelectExpression selectExpression) + { + _containsOuterReference = false; + + Visit(selectExpression); + + return _containsOuterReference; + } + + public override Expression Visit(Expression expression) + { + if (_containsOuterReference) + { + return expression; + } + + if (expression is ColumnExpression columnExpression + && _tables.Contains(columnExpression.Table)) + { + _containsOuterReference = true; + + return expression; + } + + return base.Visit(expression); + } + } + private enum JoinType { InnerJoin, @@ -981,12 +1026,20 @@ private void AddJoin( if (joinType == JoinType.InnerJoinLateral || joinType == JoinType.LeftJoinLateral) { joinPredicate = TryExtractJoinKey(innerSelectExpression); - // TODO: Make sure that innerSelectExpression does not contain any reference from this SelectExpression if (joinPredicate != null) { - AddJoin(joinType == JoinType.InnerJoinLateral ? JoinType.InnerJoin : JoinType.LeftJoin, - innerSelectExpression, transparentIdentifierType, joinPredicate); - return; + var containsOuterReference = new SelectExpressionCorrelationFindingExpressionVisitor(Tables) + .ContainsOuterReference(innerSelectExpression); + if (containsOuterReference) + { + innerSelectExpression.ApplyPredicate(joinPredicate); + } + else + { + AddJoin(joinType == JoinType.InnerJoinLateral ? JoinType.InnerJoin : JoinType.LeftJoin, + innerSelectExpression, transparentIdentifierType, joinPredicate); + return; + } } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs index 4e7e7eb8795..21bfeea249d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs @@ -4084,6 +4084,30 @@ public override Task Multiple_select_many_with_predicate(bool isAsync) return base.Multiple_select_many_with_predicate(isAsync); } + [ConditionalTheory(Skip = "Issue#14935")] + public override Task SelectMany_correlated_with_outer_1(bool isAsync) + { + return base.SelectMany_correlated_with_outer_1(isAsync); + } + + [ConditionalTheory(Skip = "Issue#14935")] + public override Task SelectMany_correlated_with_outer_2(bool isAsync) + { + return base.SelectMany_correlated_with_outer_2(isAsync); + } + + [ConditionalTheory(Skip = "Issue#14935")] + public override Task SelectMany_correlated_with_outer_3(bool isAsync) + { + return base.SelectMany_correlated_with_outer_3(isAsync); + } + + [ConditionalTheory(Skip = "Issue#14935")] + public override Task SelectMany_correlated_with_outer_4(bool isAsync) + { + return base.SelectMany_correlated_with_outer_4(isAsync); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index ee13697ea18..47d9b39462c 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -405,6 +405,30 @@ public override Task SelectMany_without_result_selector_collection_navigation_co return base.SelectMany_without_result_selector_collection_navigation_composed(isAsync); } + [ConditionalTheory(Skip = "Issue#16963")] + public override Task SelectMany_correlated_with_outer_1(bool isAsync) + { + return base.SelectMany_correlated_with_outer_1(isAsync); + } + + [ConditionalTheory(Skip = "Issue#16963")] + public override Task SelectMany_correlated_with_outer_2(bool isAsync) + { + return base.SelectMany_correlated_with_outer_2(isAsync); + } + + [ConditionalTheory(Skip = "Issue#16963")] + public override Task SelectMany_correlated_with_outer_3(bool isAsync) + { + return base.SelectMany_correlated_with_outer_3(isAsync); + } + + [ConditionalTheory(Skip = "Issue#16963")] + public override Task SelectMany_correlated_with_outer_4(bool isAsync) + { + return base.SelectMany_correlated_with_outer_4(isAsync); + } + #endregion } } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index ba1a64adc78..af2ffaff4b0 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -4893,7 +4893,7 @@ orderby w.IsAutomatic }); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_inner_subquery_selector_references_outer_qsre(bool isAsync) { @@ -4919,7 +4919,7 @@ from o in gs.OfType() }); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_inner_subquery_predicate_references_outer_qsre(bool isAsync) { @@ -4945,7 +4945,7 @@ from o in gs.OfType() }); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_nested_inner_subquery_references_outer_qsre_one_level_up(bool isAsync) { @@ -4984,7 +4984,7 @@ from o in gs.OfType() }); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_nested_inner_subquery_references_outer_qsre_two_levels_up(bool isAsync) { @@ -5729,7 +5729,7 @@ public virtual Task Where_required_navigation_on_derived_type(bool isAsync) lls => lls.Where(ll => ll is LocustCommander ? ((LocustCommander)ll).HighCommand.IsOperational : false)); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Outer_parameter_in_join_key(bool isAsync) { @@ -5748,7 +5748,7 @@ join g in gs on o.FullName equals g.FullName elementAsserter: (e, a) => CollectionAsserter(elementSorter: ee => ee)(e.Collection, a.Collection)); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Outer_parameter_in_join_key_inner_and_outer(bool isAsync) { @@ -5786,7 +5786,7 @@ join g in gs on o.FullName equals g.FullName into grouping elementAsserter: (e, a) => CollectionAsserter(elementSorter: ee => ee)(e.Collection, a.Collection)); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Outer_parameter_in_group_join_with_DefaultIfEmpty(bool isAsync) { diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs index d8c3ac7bce0..0ff6f16a979 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs @@ -592,7 +592,7 @@ public virtual Task Select_nested_collection_count_using_anonymous_type(bool isA e => e.Count); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory(Skip = "Issue#16314")] [MemberData(nameof(IsAsyncData))] public virtual Task Select_nested_collection_deep(bool isAsync) { @@ -1229,5 +1229,69 @@ public virtual Task SelectMany_without_result_selector_collection_navigation_com isAsync, cs => cs.SelectMany(c => c.Orders.Select(o => o.CustomerID))); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_correlated_with_outer_1(bool isAsync) + { + return AssertQuery( + isAsync, + (cs, os) => from c in cs + from o in os.Where(o => c.CustomerID == o.CustomerID).Select(o => c.City) + select new + { + c, + o + }, + entryCount: 89); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_correlated_with_outer_2(bool isAsync) + { + return AssertQuery( + isAsync, + (cs, os) => from c in cs + from o in os.Where(o => c.CustomerID == o.CustomerID).OrderBy(o => c.City).Take(2) + select new + { + c, + o + }, + entryCount: 266); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_correlated_with_outer_3(bool isAsync) + { + return AssertQuery( + isAsync, + (cs, os) => from c in cs + from o in os.Where(o => c.CustomerID == o.CustomerID).Select(o => c.City).DefaultIfEmpty() + select new + { + c, + o + }, + entryCount: 91); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_correlated_with_outer_4(bool isAsync) + { + return AssertQuery( + isAsync, + (cs, os) => from c in cs + from o in os.Where(o => c.CustomerID == o.CustomerID).OrderBy(o => c.City).Take(2).DefaultIfEmpty() + select new + { + c, + o + }, + entryCount: 268); + } } } diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs index fbb114df3dd..b59438211d6 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs @@ -1919,7 +1919,7 @@ from c in cs entryCount: 1); } - [ConditionalTheory(Skip = "Issue#16311")] + [ConditionalTheory(Skip = "Issue#16314")] [MemberData(nameof(IsAsyncData))] public virtual Task Select_subquery_recursive_trivial(bool isAsync) { @@ -3266,14 +3266,14 @@ public virtual Task SelectMany_Joined_Take(bool isAsync) isAsync, (cs, os) => from c in cs - from o in os.Where(o => o.CustomerID == c.CustomerID).Take(1000) + from o in os.Where(o => o.CustomerID == c.CustomerID).Take(4) select new { c.ContactName, o }, e => e.o.OrderID, - entryCount: 830); + entryCount: 342); } [ConditionalTheory] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs index 58d8f8b10ae..e62cdccb77d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs @@ -28,7 +28,7 @@ public AsyncSimpleQuerySqlServerTest( //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - [ConditionalFact(Skip = "Issue#16311")] + [ConditionalFact(Skip = "Issue#16314")] public Task Query_compiler_concurrency() { const int threadCount = 50; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 40ee9635649..521e9b27521 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -4904,25 +4904,15 @@ public override async Task Correlated_collections_inner_subquery_selector_refere await base.Correlated_collections_inner_subquery_selector_references_outer_qsre(isAsync); AssertSql( - @"SELECT [o].[FullName], [o].[Nickname], [o].[SquadId] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer'", - // - @"@_outer_FullName='Damon Baird' (Size = 4000) -@_outer_Nickname='Baird' (Size = 450) -@_outer_SquadId='1' - -SELECT [g].[FullName] AS [ReportName], @_outer_FullName AS [OfficerName] + @"SELECT [g].[FullName], [g].[Nickname], [g].[SquadId], [t].[FullName], [t].[FullName0], [t].[Nickname], [t].[SquadId] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ((@_outer_Nickname = [g].[LeaderNickname]) AND (@_outer_SquadId = [g].[LeaderSquadId]))", - // - @"@_outer_FullName='Marcus Fenix' (Size = 4000) -@_outer_Nickname='Marcus' (Size = 450) -@_outer_SquadId='1' - -SELECT [g].[FullName] AS [ReportName], @_outer_FullName AS [OfficerName] -FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ((@_outer_Nickname = [g].[LeaderNickname]) AND (@_outer_SquadId = [g].[LeaderSquadId]))"); +OUTER APPLY ( + SELECT [g0].[FullName], [g].[FullName] AS [FullName0], [g0].[Nickname], [g0].[SquadId] + FROM [Gears] AS [g0] + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') AND ((([g].[Nickname] = [g0].[LeaderNickname]) AND [g0].[LeaderNickname] IS NOT NULL) AND ([g].[SquadId] = [g0].[LeaderSquadId])) +) AS [t] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); } public override async Task Correlated_collections_inner_subquery_predicate_references_outer_qsre(bool isAsync) @@ -4930,25 +4920,15 @@ public override async Task Correlated_collections_inner_subquery_predicate_refer await base.Correlated_collections_inner_subquery_predicate_references_outer_qsre(isAsync); AssertSql( - @"SELECT [o].[FullName], [o].[Nickname], [o].[SquadId] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer'", - // - @"@_outer_Nickname='Baird' (Size = 450) -@_outer_SquadId='1' -@_outer_FullName='Damon Baird' (Size = 4000) - -SELECT [g].[FullName] AS [ReportName] -FROM [Gears] AS [g] -WHERE ([g].[Discriminator] IN (N'Officer', N'Gear') AND ((@_outer_Nickname = [g].[LeaderNickname]) AND (@_outer_SquadId = [g].[LeaderSquadId]))) AND (@_outer_FullName <> N'Foo')", - // - @"@_outer_Nickname='Marcus' (Size = 450) -@_outer_SquadId='1' -@_outer_FullName='Marcus Fenix' (Size = 4000) - -SELECT [g].[FullName] AS [ReportName] + @"SELECT [g].[FullName], [g].[Nickname], [g].[SquadId], [t].[FullName], [t].[Nickname], [t].[SquadId] FROM [Gears] AS [g] -WHERE ([g].[Discriminator] IN (N'Officer', N'Gear') AND ((@_outer_Nickname = [g].[LeaderNickname]) AND (@_outer_SquadId = [g].[LeaderSquadId]))) AND (@_outer_FullName <> N'Foo')"); +OUTER APPLY ( + SELECT [g0].[FullName], [g0].[Nickname], [g0].[SquadId] + FROM [Gears] AS [g0] + WHERE ([g0].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[FullName] <> N'Foo')) AND ((([g].[Nickname] = [g0].[LeaderNickname]) AND [g0].[LeaderNickname] IS NOT NULL) AND ([g].[SquadId] = [g0].[LeaderSquadId])) +) AS [t] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); } public override async Task Correlated_collections_nested_inner_subquery_references_outer_qsre_one_level_up(bool isAsync) @@ -4956,48 +4936,20 @@ public override async Task Correlated_collections_nested_inner_subquery_referenc await base.Correlated_collections_nested_inner_subquery_references_outer_qsre_one_level_up(isAsync); AssertSql( - @"SELECT [o].[FullName], [o].[Nickname], [o].[SquadId] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer' -ORDER BY [o].[Nickname], [o].[SquadId]", - // - @"SELECT [t].[Nickname], [t].[SquadId], [o.Reports].[FullName], [o.Reports].[Nickname], [o.Reports].[LeaderNickname], [o.Reports].[LeaderSquadId] -FROM [Gears] AS [o.Reports] -INNER JOIN ( - SELECT [o0].[Nickname], [o0].[SquadId] - FROM [Gears] AS [o0] - WHERE [o0].[Discriminator] = N'Officer' -) AS [t] ON ([o.Reports].[LeaderNickname] = [t].[Nickname]) AND ([o.Reports].[LeaderSquadId] = [t].[SquadId]) -WHERE [o.Reports].[Discriminator] IN (N'Officer', N'Gear') AND ([o.Reports].[FullName] <> N'Foo') -ORDER BY [t].[Nickname], [t].[SquadId]", - // - @"@_outer_Nickname='Paduk' (Size = 4000) -@_outer_FullName='Garron Paduk' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname AS [Nickname] -FROM [Weapons] AS [w] -WHERE (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL) AND (@_outer_FullName = [w].[OwnerFullName])", - // - @"@_outer_Nickname='Baird' (Size = 4000) -@_outer_FullName='Damon Baird' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname AS [Nickname] -FROM [Weapons] AS [w] -WHERE (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL) AND (@_outer_FullName = [w].[OwnerFullName])", - // - @"@_outer_Nickname='Cole Train' (Size = 4000) -@_outer_FullName='Augustus Cole' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname AS [Nickname] -FROM [Weapons] AS [w] -WHERE (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL) AND (@_outer_FullName = [w].[OwnerFullName])", - // - @"@_outer_Nickname='Dom' (Size = 4000) -@_outer_FullName='Dominic Santiago' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname AS [Nickname] -FROM [Weapons] AS [w] -WHERE (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL) AND (@_outer_FullName = [w].[OwnerFullName])"); + @"SELECT [g].[FullName], [g].[Nickname], [g].[SquadId], [t0].[FullName], [t0].[Nickname], [t0].[SquadId], [t0].[Name], [t0].[Nickname0], [t0].[Id] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [g0].[FullName], [g0].[Nickname], [g0].[SquadId], [t].[Name], [t].[Nickname] AS [Nickname0], [t].[Id], [g0].[LeaderNickname], [g0].[LeaderSquadId] + FROM [Gears] AS [g0] + OUTER APPLY ( + SELECT [w].[Name], [g0].[Nickname], [w].[Id] + FROM [Weapons] AS [w] + WHERE (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL) AND (([g0].[FullName] = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL) + ) AS [t] + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') AND ([g0].[FullName] <> N'Foo') +) AS [t0] ON (([g].[Nickname] = [t0].[LeaderNickname]) AND [t0].[LeaderNickname] IS NOT NULL) AND ([g].[SquadId] = [t0].[LeaderSquadId]) +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[Nickname], [g].[SquadId], [t0].[Nickname], [t0].[SquadId], [t0].[Id]"); } public override async Task Correlated_collections_nested_inner_subquery_references_outer_qsre_two_levels_up(bool isAsync) @@ -5005,51 +4957,20 @@ public override async Task Correlated_collections_nested_inner_subquery_referenc await base.Correlated_collections_nested_inner_subquery_references_outer_qsre_two_levels_up(isAsync); AssertSql( - @"SELECT [o].[FullName], [o].[Nickname], [o].[SquadId] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer'", - // - @"@_outer_Nickname='Baird' (Size = 450) -@_outer_SquadId='1' - -SELECT [g].[FullName] -FROM [Gears] AS [g] -WHERE ([g].[Discriminator] IN (N'Officer', N'Gear') AND ((@_outer_Nickname = [g].[LeaderNickname]) AND (@_outer_SquadId = [g].[LeaderSquadId]))) AND ([g].[FullName] <> N'Foo')", - // - @"@_outer_Nickname1='Baird' (Size = 4000) -@_outer_FullName='Garron Paduk' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname1 AS [Nickname] -FROM [Weapons] AS [w] -WHERE (@_outer_FullName = [w].[OwnerFullName]) AND (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL)", - // - @"@_outer_Nickname='Marcus' (Size = 450) -@_outer_SquadId='1' - -SELECT [g].[FullName] + @"SELECT [g].[FullName], [g].[Nickname], [g].[SquadId], [t0].[FullName], [t0].[Nickname], [t0].[SquadId], [t0].[Name], [t0].[Nickname0], [t0].[Id] FROM [Gears] AS [g] -WHERE ([g].[Discriminator] IN (N'Officer', N'Gear') AND ((@_outer_Nickname = [g].[LeaderNickname]) AND (@_outer_SquadId = [g].[LeaderSquadId]))) AND ([g].[FullName] <> N'Foo')", - // - @"@_outer_Nickname1='Marcus' (Size = 4000) -@_outer_FullName='Augustus Cole' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname1 AS [Nickname] -FROM [Weapons] AS [w] -WHERE (@_outer_FullName = [w].[OwnerFullName]) AND (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL)", - // - @"@_outer_Nickname1='Marcus' (Size = 4000) -@_outer_FullName='Damon Baird' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname1 AS [Nickname] -FROM [Weapons] AS [w] -WHERE (@_outer_FullName = [w].[OwnerFullName]) AND (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL)", - // - @"@_outer_Nickname1='Marcus' (Size = 4000) -@_outer_FullName='Dominic Santiago' (Size = 450) - -SELECT [w].[Name], @_outer_Nickname1 AS [Nickname] -FROM [Weapons] AS [w] -WHERE (@_outer_FullName = [w].[OwnerFullName]) AND (([w].[Name] <> N'Bar') OR [w].[Name] IS NULL)"); +OUTER APPLY ( + SELECT [g0].[FullName], [g0].[Nickname], [g0].[SquadId], [t].[Name], [t].[Nickname] AS [Nickname0], [t].[Id] + FROM [Gears] AS [g0] + LEFT JOIN ( + SELECT [w].[Name], [g].[Nickname], [w].[Id], [w].[OwnerFullName] + FROM [Weapons] AS [w] + WHERE ([w].[Name] <> N'Bar') OR [w].[Name] IS NULL + ) AS [t] ON [g0].[FullName] = [t].[OwnerFullName] + WHERE ([g0].[Discriminator] IN (N'Gear', N'Officer') AND ([g0].[FullName] <> N'Foo')) AND ((([g].[Nickname] = [g0].[LeaderNickname]) AND [g0].[LeaderNickname] IS NOT NULL) AND ([g].[SquadId] = [g0].[LeaderSquadId])) +) AS [t0] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[Nickname], [g].[SquadId], [t0].[Nickname], [t0].[SquadId], [t0].[Id]"); } public override async Task Correlated_collections_on_select_many(bool isAsync) @@ -5619,24 +5540,19 @@ public override async Task Outer_parameter_in_join_key(bool isAsync) await base.Outer_parameter_in_join_key(isAsync); AssertSql( - @"SELECT [o].[FullName] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer' -ORDER BY [o].[Nickname]", - // - @"@_outer_FullName='Damon Baird' (Size = 450) - -SELECT [t].[Note] -FROM [Tags] AS [t] -INNER JOIN [Gears] AS [g] ON @_outer_FullName = [g].[FullName] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear')", - // - @"@_outer_FullName='Marcus Fenix' (Size = 450) - -SELECT [t].[Note] -FROM [Tags] AS [t] -INNER JOIN [Gears] AS [g] ON @_outer_FullName = [g].[FullName] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear')"); + @"SELECT [g].[Nickname], [g].[SquadId], [t1].[Note], [t1].[Id], [t1].[Nickname], [t1].[SquadId] +FROM [Gears] AS [g] +OUTER APPLY ( + SELECT [t].[Note], [t].[Id], [t0].[Nickname], [t0].[SquadId] + FROM [Tags] AS [t] + INNER JOIN ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOrBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank] + FROM [Gears] AS [g0] + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') + ) AS [t0] ON [g].[FullName] = [t0].[FullName] +) AS [t1] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[Nickname], [g].[SquadId], [t1].[Id], [t1].[Nickname], [t1].[SquadId]"); } public override async Task Outer_parameter_in_join_key_inner_and_outer(bool isAsync) @@ -5644,26 +5560,19 @@ public override async Task Outer_parameter_in_join_key_inner_and_outer(bool isAs await base.Outer_parameter_in_join_key_inner_and_outer(isAsync); AssertSql( - @"SELECT [o].[FullName], [o].[Nickname] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer' -ORDER BY [o].[Nickname]", - // - @"@_outer_FullName='Damon Baird' (Size = 4000) -@_outer_Nickname='Baird' (Size = 4000) - -SELECT [t].[Note] -FROM [Tags] AS [t] -INNER JOIN [Gears] AS [g] ON @_outer_FullName = @_outer_Nickname -WHERE [g].[Discriminator] IN (N'Officer', N'Gear')", - // - @"@_outer_FullName='Marcus Fenix' (Size = 4000) -@_outer_Nickname='Marcus' (Size = 4000) - -SELECT [t].[Note] -FROM [Tags] AS [t] -INNER JOIN [Gears] AS [g] ON @_outer_FullName = @_outer_Nickname -WHERE [g].[Discriminator] IN (N'Officer', N'Gear')"); + @"SELECT [g].[Nickname], [g].[SquadId], [t1].[Note], [t1].[Id], [t1].[Nickname], [t1].[SquadId] +FROM [Gears] AS [g] +OUTER APPLY ( + SELECT [t].[Note], [t].[Id], [t0].[Nickname], [t0].[SquadId] + FROM [Tags] AS [t] + INNER JOIN ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOrBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank] + FROM [Gears] AS [g0] + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') + ) AS [t0] ON [g].[FullName] = [g].[Nickname] +) AS [t1] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[Nickname], [g].[SquadId], [t1].[Id], [t1].[Nickname], [t1].[SquadId]"); } public override async Task Outer_parameter_in_group_join_key(bool isAsync) @@ -5702,30 +5611,19 @@ public override async Task Outer_parameter_in_group_join_with_DefaultIfEmpty(boo await base.Outer_parameter_in_group_join_with_DefaultIfEmpty(isAsync); AssertSql( - @"SELECT [o].[FullName] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer' -ORDER BY [o].[Nickname]", - // - @"@_outer_FullName1='Damon Baird' (Size = 450) - -SELECT [t].[Note] -FROM [Tags] AS [t] -LEFT JOIN ( - SELECT [g].* - FROM [Gears] AS [g] - WHERE [g].[Discriminator] IN (N'Officer', N'Gear') -) AS [t0] ON @_outer_FullName1 = [t0].[FullName]", - // - @"@_outer_FullName1='Marcus Fenix' (Size = 450) - -SELECT [t].[Note] -FROM [Tags] AS [t] -LEFT JOIN ( - SELECT [g].* - FROM [Gears] AS [g] - WHERE [g].[Discriminator] IN (N'Officer', N'Gear') -) AS [t0] ON @_outer_FullName1 = [t0].[FullName]"); + @"SELECT [g].[Nickname], [g].[SquadId], [t1].[Note], [t1].[Id] +FROM [Gears] AS [g] +OUTER APPLY ( + SELECT [t].[Note], [t].[Id] + FROM [Tags] AS [t] + LEFT JOIN ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOrBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank] + FROM [Gears] AS [g0] + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') + ) AS [t0] ON [g].[FullName] = [t0].[FullName] +) AS [t1] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[Nickname], [g].[SquadId], [t1].[Id]"); } public override async Task Include_with_concat(bool isAsync) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs index 713d1499933..dc87617b8c6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs @@ -327,11 +327,11 @@ public override void Select_nested_collection_multi_level() AssertSql( @"SELECT [c].[CustomerID], [t].[OrderDate], [t].[OrderID] FROM [Customers] AS [c] -LEFT JOIN ( - SELECT TOP(3) [o].[OrderDate], [o].[OrderID], [o].[CustomerID] +OUTER APPLY ( + SELECT TOP(3) [o].[OrderDate], [o].[OrderID] FROM [Orders] AS [o] - WHERE [o].[OrderID] < 10500 -) AS [t] ON [c].[CustomerID] = [t].[CustomerID] + WHERE (([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL) AND ([o].[OrderID] < 10500) +) AS [t] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY [c].[CustomerID], [t].[OrderID]"); } @@ -1007,5 +1007,63 @@ public override async Task SelectMany_without_result_selector_collection_navigat FROM [Customers] AS [c] INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID]"); } + + public override async Task SelectMany_correlated_with_outer_1(bool isAsync) + { + await base.SelectMany_correlated_with_outer_1(isAsync); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [t].[City] AS [o] +FROM [Customers] AS [c] +CROSS APPLY ( + SELECT [c].[City], [o].[OrderID], [o].[CustomerID] + FROM [Orders] AS [o] + WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL +) AS [t]"); + } + + public override async Task SelectMany_correlated_with_outer_2(bool isAsync) + { + await base.SelectMany_correlated_with_outer_2(isAsync); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] +FROM [Customers] AS [c] +CROSS APPLY ( + SELECT TOP(2) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[City] + FROM [Orders] AS [o] + WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL + ORDER BY [c].[City] +) AS [t]"); + } + + public override async Task SelectMany_correlated_with_outer_3(bool isAsync) + { + await base.SelectMany_correlated_with_outer_3(isAsync); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [t].[City] AS [o] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT [c].[City], [o].[OrderID], [o].[CustomerID] + FROM [Orders] AS [o] + WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL +) AS [t]"); + } + + public override async Task SelectMany_correlated_with_outer_4(bool isAsync) + { + await base.SelectMany_correlated_with_outer_4(isAsync); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT TOP(2) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[City] + FROM [Orders] AS [o] + WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL + ORDER BY [c].[City] +) AS [t]"); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index d7d02d035a6..5c0790401f8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -1950,10 +1950,11 @@ public override async Task SelectMany_Joined_Take(bool isAsync) AssertSql( @"SELECT [c].[ContactName], [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] FROM [Customers] AS [c] -INNER JOIN ( - SELECT TOP(1000) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +CROSS APPLY ( + SELECT TOP(4) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + WHERE ([o].[CustomerID] = [c].[CustomerID]) AND [o].[CustomerID] IS NOT NULL +) AS [t]"); } public override async Task Take_with_single(bool isAsync) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index de279e580c3..e903d44fbfa 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -50,5 +50,20 @@ public GearsOfWarQuerySqliteTest(GearsOfWarQuerySqliteFixture fixture) // Skip for SQLite. Issue #14935. Cannot eval 'where ([m].Timeline >= x)' public override Task DateTimeOffset_Contains_Less_than_Greater_than(bool isAsync) => null; + + // Sqlite does not support lateral joins + public override Task Correlated_collections_inner_subquery_predicate_references_outer_qsre(bool isAsync) => null; + + public override Task Correlated_collections_inner_subquery_selector_references_outer_qsre(bool isAsync) => null; + + public override Task Correlated_collections_nested_inner_subquery_references_outer_qsre_one_level_up(bool isAsync) => null; + + public override Task Correlated_collections_nested_inner_subquery_references_outer_qsre_two_levels_up(bool isAsync) => null; + + public override Task Outer_parameter_in_group_join_with_DefaultIfEmpty(bool isAsync) => null; + + public override Task Outer_parameter_in_join_key(bool isAsync) => null; + + public override Task Outer_parameter_in_join_key_inner_and_outer(bool isAsync) => null; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs index 5e5153f761b..d3b330abddc 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs @@ -1067,6 +1067,19 @@ public override Task KeylessEntity_simple(bool isAsync) public override Task KeylessEntity_where_simple(bool isAsync) => base.KeylessEntity_where_simple(isAsync); + // Sqlite does not support lateral joins + public override void Select_nested_collection_multi_level() + { + } + + public override Task SelectMany_correlated_with_outer_1(bool isAsync) => null; + + public override Task SelectMany_correlated_with_outer_2(bool isAsync) => null; + + public override Task SelectMany_correlated_with_outer_3(bool isAsync) => null; + + public override Task SelectMany_correlated_with_outer_4(bool isAsync) => null; + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); }