From 02f8d6f2000cf140102bf2194e7379c7812dc2ad Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Mon, 23 Dec 2024 22:10:25 +0100 Subject: [PATCH 1/5] Add tests for multiple `Take`s --- .../Query/NorthwindSelectQueryCosmosTest.cs | 16 ++++++++ .../Query/NorthwindSelectQueryTestBase.cs | 14 +++++++ .../NorthwindSelectQuerySqlServerTest.cs | 40 +++++++++++++++++++ .../Query/NorthwindSelectQuerySqliteTest.cs | 27 +++++++++++++ 4 files changed, 97 insertions(+) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs index 03c0f151db1..b05fbdf7475 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs @@ -1096,6 +1096,22 @@ public override async Task SelectMany_correlated_with_outer_7(bool async) AssertSql(); } + public override async Task SelectMany_with_multiple_Take(bool async) + { + // Cosmos client evaluation. Issue #17246. + await AssertTranslationFailed(() => base.SelectMany_with_multiple_Take(async)); + + AssertSql(); + } + + public override async Task Select_with_multiple_Take(bool async) + { + // Cosmos client evaluation. Issue #17246. + await AssertTranslationFailed(() => base.Select_with_multiple_Take(async)); + + AssertSql(); + } + public override async Task FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(bool async) { // Cosmos client evaluation. Issue #17246. diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index 80b469918a5..cbd036ad513 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -1441,6 +1441,20 @@ from o in ss.Set().Where(o => c.CustomerID.Length >= o.CustomerID.Length) AssertEqual(e.o, a.o); }); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_with_multiple_Take(bool async) + => AssertQuery( + async, + ss => ss.Set().SelectMany(c => c.Orders.OrderBy(o => o.OrderID).Take(5).Take(3))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_with_multiple_Take(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(o => o.CustomerID).Take(5).Take(3)); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index b838a85a7e7..f10bc37ce5d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -1336,6 +1336,46 @@ WHERE CAST(LEN([c].[CustomerID]) AS int) >= CAST(LEN([o].[CustomerID]) AS int) """); } + public override async Task SelectMany_with_multiple_Take(bool async) + { + await base.SelectMany_with_multiple_Take(async); + + AssertSql( + """ +SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] +FROM [Customers] AS [c] +CROSS APPLY ( + SELECT TOP(3) [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] + FROM ( + SELECT TOP(5) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[OrderID] + ) AS [o0] + ORDER BY [o0].[OrderID] +) AS [o1] +"""); + } + + public override async Task Select_with_multiple_Take(bool async) + { + await base.Select_with_multiple_Take(async); + + AssertSql( + """ +@p0='3' +@p='5' + +SELECT TOP(@p0) [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] +FROM ( + SELECT TOP(@p) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] + FROM [Customers] AS [c] + ORDER BY [c].[CustomerID] +) AS [c0] +ORDER BY [c0].[CustomerID] +"""); + } + public override async Task FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(bool async) { await base.FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs index 91cbbc47835..e3f14a289fe 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs @@ -207,6 +207,33 @@ public override async Task SelectMany_correlated_with_outer_7(bool async) (await Assert.ThrowsAsync( () => base.SelectMany_correlated_with_outer_7(async))).Message); + public override async Task SelectMany_with_multiple_Take(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.SelectMany_with_multiple_Take(async))).Message); + + public override async Task Select_with_multiple_Take(bool async) + { + await base.Select_with_multiple_Take(async); + + AssertSql( + """ +@p='5' +@p0='3' + +SELECT "c0"."CustomerID", "c0"."Address", "c0"."City", "c0"."CompanyName", "c0"."ContactName", "c0"."ContactTitle", "c0"."Country", "c0"."Fax", "c0"."Phone", "c0"."PostalCode", "c0"."Region" +FROM ( + SELECT "c"."CustomerID", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" + FROM "Customers" AS "c" + ORDER BY "c"."CustomerID" + LIMIT @p +) AS "c0" +ORDER BY "c0"."CustomerID" +LIMIT @p0 +"""); + } + public override async Task SelectMany_whose_selector_references_outer_source(bool async) => Assert.Equal( SqliteStrings.ApplyNotSupported, From 3bbb726f5357883d44ffa561ba5d94523bd839ec Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Mon, 23 Dec 2024 22:10:05 +0100 Subject: [PATCH 2/5] Optimize multiple consecutive `LIMIT` Fixes #35383 for trivial cases (constants/literals and repeated expressions). --- ...yableMethodTranslatingExpressionVisitor.cs | 48 +++++++++++++++++-- .../Query/SqlExpressions/SelectExpression.cs | 9 ++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index ee14b47e0a1..91b7b4c3cd1 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -654,7 +654,7 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression } selectExpression.ApplyOffset(translation); - selectExpression.ApplyLimit(TranslateExpression(Expression.Constant(1))!); + ApplyLimit(selectExpression, TranslateExpression(Expression.Constant(1))!); return source; } @@ -693,7 +693,7 @@ protected override ShapedQueryExpression TranslateExcept(ShapedQueryExpression s _queryCompilationContext.Logger.FirstWithoutOrderByAndFilterWarning(); } - selectExpression.ApplyLimit(TranslateExpression(Expression.Constant(1))!); + ApplyLimit(selectExpression, TranslateExpression(Expression.Constant(1))!); return source.ShaperExpression.Type != returnType ? source.UpdateShaperExpression(Expression.Convert(source.ShaperExpression, returnType)) @@ -940,7 +940,7 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK } selectExpression.ReverseOrderings(); - selectExpression.ApplyLimit(TranslateExpression(Expression.Constant(1))!); + ApplyLimit(selectExpression, TranslateExpression(Expression.Constant(1))!); return source.ShaperExpression.Type != returnType ? source.UpdateShaperExpression(Expression.Convert(source.ShaperExpression, returnType)) @@ -1208,7 +1208,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } var selectExpression = (SelectExpression)source.QueryExpression; - selectExpression.ApplyLimit(TranslateExpression(Expression.Constant(_subquery ? 1 : 2))!); + ApplyLimit(selectExpression, TranslateExpression(Expression.Constant(_subquery ? 1 : 2))!); return source.ShaperExpression.Type != returnType ? source.UpdateShaperExpression(Expression.Convert(source.ShaperExpression, returnType)) @@ -1258,11 +1258,49 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp _queryCompilationContext.Logger.RowLimitingOperationWithoutOrderByWarning(); } - selectExpression.ApplyLimit(translation); + ApplyLimit(selectExpression, translation); return source; } + private void ApplyLimit(SelectExpression selectExpression, SqlExpression limit) + { + var oldLimit = selectExpression.Limit; + + if (oldLimit is null) + { + selectExpression.SetLimit(limit); + return; + } + + if (oldLimit is SqlConstantExpression { Value: int oldConst } && limit is SqlConstantExpression { Value: int newConst }) + { + // if both the old and new limit are constants, use the smaller one + // (aka constant-fold LEAST(constA, constB)) + if (oldConst > newConst) + { + selectExpression.SetLimit(limit); + } + + return; + } + + if (oldLimit.Equals(limit)) + { + return; + } + + // if possible, use LEAST(oldLimit, limit); otherwise, use nested queries + if (_sqlTranslator.GenerateLeast([oldLimit, limit], limit.Type) is { } newLimit) + { + selectExpression.SetLimit(newLimit); + } + else + { + selectExpression.ApplyLimit(limit); + } + } + /// protected override ShapedQueryExpression? TranslateTakeWhile(ShapedQueryExpression source, LambdaExpression predicate) => null; diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 7d8ec03bdf8..8beb4742065 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1924,6 +1924,15 @@ public void ApplyLimit(SqlExpression sqlExpression) PushdownIntoSubquery(); } + SetLimit(sqlExpression); + } + + /// + /// Sets a new limit of the to limit the number of rows returned in the result set. + /// + /// An expression representing limit row count. + public void SetLimit(SqlExpression sqlExpression) + { Limit = sqlExpression; if (Offset is null && Limit is SqlConstantExpression { Value: 1 }) From 9cdc7a257b3bbfaf076b502f1688b54765e6073f Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Mon, 23 Dec 2024 22:43:48 +0100 Subject: [PATCH 3/5] Enable `Skip_navigation_order_by_single_or_default` test The new translation makes it work also in Sqlite. --- .../Query/ManyToManyNoTrackingQuerySqliteTest.cs | 6 ------ .../Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs | 5 ----- .../Query/ManyToManyQuerySqliteTest.cs | 6 ------ .../Query/ManyToManySplitQuerySqliteTest.cs | 5 ----- .../Query/TPCManyToManyNoTrackingQuerySqliteTest.cs | 6 ------ .../Query/TPCManyToManyQuerySqliteTest.cs | 6 ------ .../Query/TPTManyToManyNoTrackingQuerySqliteTest.cs | 6 ------ .../Query/TPTManyToManyQuerySqliteTest.cs | 6 ------ 8 files changed, 46 deletions(-) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingQuerySqliteTest.cs index 00c3ee62cbb..0a9cee13936 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingQuerySqliteTest.cs @@ -12,12 +12,6 @@ public class ManyToManyNoTrackingQuerySqliteTest(ManyToManyQuerySqliteFixture fi { // Sqlite does not support Apply operations - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); - public override async Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where(bool async) => Assert.Equal( SqliteStrings.ApplyNotSupported, diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs index 4ea7fc9278f..945144a5b28 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs @@ -12,9 +12,4 @@ public class ManyToManyNoTrackingSplitQuerySqliteTest(ManyToManySplitQuerySqlite { // Sqlite does not support Apply operations - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs index c8b39d88c06..12c5f7bcd80 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs @@ -10,12 +10,6 @@ namespace Microsoft.EntityFrameworkCore.Query; public class ManyToManyQuerySqliteTest(ManyToManyQuerySqliteFixture fixture) : ManyToManyQueryRelationalTestBase(fixture) { - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); - public override async Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where(bool async) => Assert.Equal( SqliteStrings.ApplyNotSupported, diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteTest.cs index 7c4798693cd..6df0b5114e8 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteTest.cs @@ -10,9 +10,4 @@ namespace Microsoft.EntityFrameworkCore.Query; public class ManyToManySplitQuerySqliteTest(ManyToManySplitQuerySqliteFixture fixture) : ManyToManyQueryRelationalTestBase(fixture) { - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqliteTest.cs index bbfee8c56b8..f51dc557445 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqliteTest.cs @@ -10,12 +10,6 @@ namespace Microsoft.EntityFrameworkCore.Query; public class TPCManyToManyNoTrackingQuerySqliteTest(TPCManyToManyQuerySqliteFixture fixture) : TPCManyToManyNoTrackingQueryRelationalTestBase(fixture) { - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); - public override async Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where(bool async) => Assert.Equal( SqliteStrings.ApplyNotSupported, diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyQuerySqliteTest.cs index b601dc89656..75e8499aff6 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPCManyToManyQuerySqliteTest.cs @@ -10,12 +10,6 @@ namespace Microsoft.EntityFrameworkCore.Query; public class TPCManyToManyQuerySqliteTest(TPCManyToManyQuerySqliteFixture fixture) : TPCManyToManyQueryRelationalTestBase(fixture) { - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); - public override async Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where(bool async) => Assert.Equal( SqliteStrings.ApplyNotSupported, diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqliteTest.cs index fed2ba01166..6e2ab0a472a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqliteTest.cs @@ -10,12 +10,6 @@ namespace Microsoft.EntityFrameworkCore.Query; public class TPTManyToManyNoTrackingQuerySqliteTest(TPTManyToManyQuerySqliteFixture fixture) : TPTManyToManyNoTrackingQueryRelationalTestBase(fixture) { - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); - public override async Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where(bool async) => Assert.Equal( SqliteStrings.ApplyNotSupported, diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyQuerySqliteTest.cs index 78a619f8dfa..48d9c46f338 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPTManyToManyQuerySqliteTest.cs @@ -10,12 +10,6 @@ namespace Microsoft.EntityFrameworkCore.Query; public class TPTManyToManyQuerySqliteTest(TPTManyToManyQuerySqliteFixture fixture) : TPTManyToManyQueryRelationalTestBase(fixture) { - public override async Task Skip_navigation_order_by_single_or_default(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Skip_navigation_order_by_single_or_default(async))).Message); - public override async Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where(bool async) => Assert.Equal( SqliteStrings.ApplyNotSupported, From 08c8569fb29a7343ed3281d425ae115f65478399 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Mon, 23 Dec 2024 22:43:56 +0100 Subject: [PATCH 4/5] Update baselines --- .../ManyToManyNoTrackingQuerySqlServerTest.cs | 12 +-- .../Query/ManyToManyQuerySqlServerTest.cs | 12 +-- .../NorthwindSelectQuerySqlServerTest.cs | 98 +++++++------------ ...CManyToManyNoTrackingQuerySqlServerTest.cs | 12 +-- .../Query/TPCManyToManyQuerySqlServerTest.cs | 12 +-- ...TManyToManyNoTrackingQuerySqlServerTest.cs | 12 +-- .../Query/TPTManyToManyQuerySqlServerTest.cs | 12 +-- .../TemporalManyToManyQuerySqlServerTest.cs | 12 +-- .../Query/NorthwindSelectQuerySqliteTest.cs | 40 ++++---- 9 files changed, 91 insertions(+), 131 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs index 386c1fe322a..bae3e0c8d57 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs @@ -300,17 +300,15 @@ public override async Task Skip_navigation_order_by_single_or_default(bool async """ SELECT [s0].[Id], [s0].[Name] FROM [EntityOnes] AS [e] -OUTER APPLY ( - SELECT TOP(1) [s].[Id], [s].[Name] +LEFT JOIN ( + SELECT [s].[Id], [s].[Name], [s].[LeftId] FROM ( - SELECT TOP(1) [e0].[Id], [e0].[Name] + SELECT [e0].[Id], [e0].[Name], [j].[LeftId], ROW_NUMBER() OVER(PARTITION BY [j].[LeftId] ORDER BY [e0].[Id]) AS [row] FROM [JoinOneSelfPayload] AS [j] INNER JOIN [EntityOnes] AS [e0] ON [j].[RightId] = [e0].[Id] - WHERE [e].[Id] = [j].[LeftId] - ORDER BY [e0].[Id] ) AS [s] - ORDER BY [s].[Id] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [e].[Id] = [s0].[LeftId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs index 222bfd862ef..05e481e565a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs @@ -299,17 +299,15 @@ public override async Task Skip_navigation_order_by_single_or_default(bool async """ SELECT [s0].[Id], [s0].[Name] FROM [EntityOnes] AS [e] -OUTER APPLY ( - SELECT TOP(1) [s].[Id], [s].[Name] +LEFT JOIN ( + SELECT [s].[Id], [s].[Name], [s].[LeftId] FROM ( - SELECT TOP(1) [e0].[Id], [e0].[Name] + SELECT [e0].[Id], [e0].[Name], [j].[LeftId], ROW_NUMBER() OVER(PARTITION BY [j].[LeftId] ORDER BY [e0].[Id]) AS [row] FROM [JoinOneSelfPayload] AS [j] INNER JOIN [EntityOnes] AS [e0] ON [j].[RightId] = [e0].[Id] - WHERE [e].[Id] = [j].[LeftId] - ORDER BY [e0].[Id] ) AS [s] - ORDER BY [s].[Id] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [e].[Id] = [s0].[LeftId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index f10bc37ce5d..922fa23af79 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -760,14 +760,10 @@ public override async Task Project_single_element_from_collection_with_OrderBy_T AssertSql( """ SELECT ( - SELECT TOP(1) [o0].[CustomerID] - FROM ( - SELECT TOP(1) [o].[CustomerID], [o].[OrderID] - FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID] - ORDER BY [o].[OrderID] - ) AS [o0] - ORDER BY [o0].[OrderID]) + SELECT TOP(1) [o].[CustomerID] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[OrderID]) FROM [Customers] AS [c] """); } @@ -828,14 +824,10 @@ public override async Task Project_single_element_from_collection_with_OrderBy_T AssertSql( """ SELECT ( - SELECT TOP(1) [o0].[CustomerID] - FROM ( - SELECT TOP(1) [o].[CustomerID], [o].[OrderID] - FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID] - ORDER BY [o].[OrderID] - ) AS [o0] - ORDER BY [o0].[OrderID]) + SELECT TOP(1) [o].[CustomerID] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[OrderID]) FROM [Customers] AS [c] WHERE [c].[CustomerID] = N'ALFKI' """); @@ -869,14 +861,10 @@ public override async Task Project_single_element_from_collection_with_multiple_ AssertSql( """ SELECT ( - SELECT TOP(1) [o0].[CustomerID] - FROM ( - SELECT TOP(2) [o].[CustomerID], [o].[OrderID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID] - ORDER BY [o].[OrderID], [o].[OrderDate] DESC - ) AS [o0] - ORDER BY [o0].[OrderID], [o0].[OrderDate] DESC) + SELECT TOP(1) [o].[CustomerID] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[OrderID], [o].[OrderDate] DESC) FROM [Customers] AS [c] """); } @@ -892,14 +880,10 @@ await base AssertSql( """ SELECT ( - SELECT TOP(1) [o0].[c] - FROM ( - SELECT TOP(2) CAST(LEN([o].[CustomerID]) AS int) AS [c], [o].[OrderID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID] - ORDER BY [o].[OrderID], [o].[OrderDate] DESC - ) AS [o0] - ORDER BY [o0].[OrderID], [o0].[OrderDate] DESC) + SELECT TOP(1) CAST(LEN([o].[CustomerID]) AS int) + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[OrderID], [o].[OrderDate] DESC) FROM [Customers] AS [c] """); } @@ -911,14 +895,10 @@ public override async Task Project_single_element_from_collection_with_multiple_ AssertSql( """ SELECT ( - SELECT TOP(1) [o0].[CustomerID] - FROM ( - SELECT TOP(2) [o].[CustomerID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID] - ORDER BY [o].[CustomerID], [o].[OrderDate] DESC - ) AS [o0] - ORDER BY [o0].[CustomerID], [o0].[OrderDate] DESC) + SELECT TOP(1) [o].[CustomerID] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[CustomerID], [o].[OrderDate] DESC) FROM [Customers] AS [c] """); } @@ -930,15 +910,11 @@ public override async Task Project_single_element_from_collection_with_OrderBy_o AssertSql( """ SELECT COALESCE(( - SELECT TOP(1) [s].[OrderID] - FROM ( - SELECT TOP(1) [o0].[OrderID], [p].[ProductName] - FROM [Order Details] AS [o0] - INNER JOIN [Products] AS [p] ON [o0].[ProductID] = [p].[ProductID] - WHERE [o].[OrderID] = [o0].[OrderID] - ORDER BY [p].[ProductName] - ) AS [s] - ORDER BY [s].[ProductName]), 0) + SELECT TOP(1) [o0].[OrderID] + FROM [Order Details] AS [o0] + INNER JOIN [Products] AS [p] ON [o0].[ProductID] = [p].[ProductID] + WHERE [o].[OrderID] = [o0].[OrderID] + ORDER BY [p].[ProductName]), 0) FROM [Orders] AS [o] WHERE [o].[OrderID] < 10300 """); @@ -953,17 +929,15 @@ public override async Task Project_single_element_from_collection_with_OrderBy_o """ SELECT [s0].[OrderID], [s0].[ProductID], [s0].[Discount], [s0].[Quantity], [s0].[UnitPrice] FROM [Orders] AS [o] -OUTER APPLY ( - SELECT TOP(1) [s].[OrderID], [s].[ProductID], [s].[Discount], [s].[Quantity], [s].[UnitPrice] +LEFT JOIN ( + SELECT [s].[OrderID], [s].[ProductID], [s].[Discount], [s].[Quantity], [s].[UnitPrice] FROM ( - SELECT TOP(1) [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice], [p].[ProductName] + SELECT [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice], ROW_NUMBER() OVER(PARTITION BY [o0].[OrderID] ORDER BY [p].[ProductName]) AS [row] FROM [Order Details] AS [o0] INNER JOIN [Products] AS [p] ON [o0].[ProductID] = [p].[ProductID] - WHERE [o].[OrderID] = [o0].[OrderID] - ORDER BY [p].[ProductName] ) AS [s] - ORDER BY [s].[ProductName] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [o].[OrderID] = [s0].[OrderID] WHERE [o].[OrderID] < 10250 """); } @@ -1344,16 +1318,14 @@ public override async Task SelectMany_with_multiple_Take(bool async) """ SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] FROM [Customers] AS [c] -CROSS APPLY ( - SELECT TOP(3) [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] +INNER JOIN ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] FROM ( - SELECT TOP(5) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID] - ORDER BY [o].[OrderID] ) AS [o0] - ORDER BY [o0].[OrderID] -) AS [o1] + WHERE [o0].[row] <= 3 +) AS [o1] ON [c].[CustomerID] = [o1].[CustomerID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs index 5ec6900fde0..c8d247745c4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs @@ -328,17 +328,15 @@ public override async Task Skip_navigation_order_by_single_or_default(bool async """ SELECT [s0].[Id], [s0].[Name] FROM [EntityOnes] AS [e] -OUTER APPLY ( - SELECT TOP(1) [s].[Id], [s].[Name] +LEFT JOIN ( + SELECT [s].[Id], [s].[Name], [s].[LeftId] FROM ( - SELECT TOP(1) [e0].[Id], [e0].[Name] + SELECT [e0].[Id], [e0].[Name], [j].[LeftId], ROW_NUMBER() OVER(PARTITION BY [j].[LeftId] ORDER BY [e0].[Id]) AS [row] FROM [JoinOneSelfPayload] AS [j] INNER JOIN [EntityOnes] AS [e0] ON [j].[RightId] = [e0].[Id] - WHERE [e].[Id] = [j].[LeftId] - ORDER BY [e0].[Id] ) AS [s] - ORDER BY [s].[Id] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [e].[Id] = [s0].[LeftId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs index 10a704eec28..523e4341106 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs @@ -328,17 +328,15 @@ public override async Task Skip_navigation_order_by_single_or_default(bool async """ SELECT [s0].[Id], [s0].[Name] FROM [EntityOnes] AS [e] -OUTER APPLY ( - SELECT TOP(1) [s].[Id], [s].[Name] +LEFT JOIN ( + SELECT [s].[Id], [s].[Name], [s].[LeftId] FROM ( - SELECT TOP(1) [e0].[Id], [e0].[Name] + SELECT [e0].[Id], [e0].[Name], [j].[LeftId], ROW_NUMBER() OVER(PARTITION BY [j].[LeftId] ORDER BY [e0].[Id]) AS [row] FROM [JoinOneSelfPayload] AS [j] INNER JOIN [EntityOnes] AS [e0] ON [j].[RightId] = [e0].[Id] - WHERE [e].[Id] = [j].[LeftId] - ORDER BY [e0].[Id] ) AS [s] - ORDER BY [s].[Id] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [e].[Id] = [s0].[LeftId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs index bc17e65bd09..639f906257c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs @@ -308,17 +308,15 @@ public override async Task Skip_navigation_order_by_single_or_default(bool async """ SELECT [s0].[Id], [s0].[Name] FROM [EntityOnes] AS [e] -OUTER APPLY ( - SELECT TOP(1) [s].[Id], [s].[Name] +LEFT JOIN ( + SELECT [s].[Id], [s].[Name], [s].[LeftId] FROM ( - SELECT TOP(1) [e0].[Id], [e0].[Name] + SELECT [e0].[Id], [e0].[Name], [j].[LeftId], ROW_NUMBER() OVER(PARTITION BY [j].[LeftId] ORDER BY [e0].[Id]) AS [row] FROM [JoinOneSelfPayload] AS [j] INNER JOIN [EntityOnes] AS [e0] ON [j].[RightId] = [e0].[Id] - WHERE [e].[Id] = [j].[LeftId] - ORDER BY [e0].[Id] ) AS [s] - ORDER BY [s].[Id] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [e].[Id] = [s0].[LeftId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs index 125860a2071..4162cf08b94 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs @@ -307,17 +307,15 @@ public override async Task Skip_navigation_order_by_single_or_default(bool async """ SELECT [s0].[Id], [s0].[Name] FROM [EntityOnes] AS [e] -OUTER APPLY ( - SELECT TOP(1) [s].[Id], [s].[Name] +LEFT JOIN ( + SELECT [s].[Id], [s].[Name], [s].[LeftId] FROM ( - SELECT TOP(1) [e0].[Id], [e0].[Name] + SELECT [e0].[Id], [e0].[Name], [j].[LeftId], ROW_NUMBER() OVER(PARTITION BY [j].[LeftId] ORDER BY [e0].[Id]) AS [row] FROM [JoinOneSelfPayload] AS [j] INNER JOIN [EntityOnes] AS [e0] ON [j].[RightId] = [e0].[Id] - WHERE [e].[Id] = [j].[LeftId] - ORDER BY [e0].[Id] ) AS [s] - ORDER BY [s].[Id] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [e].[Id] = [s0].[LeftId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs index a2d2ce977bc..a63c8b707c0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs @@ -333,17 +333,15 @@ public override async Task Skip_navigation_order_by_single_or_default(bool async """ SELECT [s0].[Id], [s0].[Name], [s0].[PeriodEnd], [s0].[PeriodStart] FROM [EntityOnes] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e] -OUTER APPLY ( - SELECT TOP(1) [s].[Id], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +LEFT JOIN ( + SELECT [s].[Id], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart], [s].[LeftId] FROM ( - SELECT TOP(1) [e0].[Id], [e0].[Name], [e0].[PeriodEnd], [e0].[PeriodStart] + SELECT [e0].[Id], [e0].[Name], [e0].[PeriodEnd], [e0].[PeriodStart], [j].[LeftId], ROW_NUMBER() OVER(PARTITION BY [j].[LeftId] ORDER BY [e0].[Id]) AS [row] FROM [JoinOneSelfPayload] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [j] INNER JOIN [EntityOnes] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e0] ON [j].[RightId] = [e0].[Id] - WHERE [e].[Id] = [j].[LeftId] - ORDER BY [e0].[Id] ) AS [s] - ORDER BY [s].[Id] -) AS [s0] + WHERE [s].[row] <= 1 +) AS [s0] ON [e].[Id] = [s0].[LeftId] """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs index e3f14a289fe..821598eb001 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs @@ -208,10 +208,23 @@ public override async Task SelectMany_correlated_with_outer_7(bool async) () => base.SelectMany_correlated_with_outer_7(async))).Message); public override async Task SelectMany_with_multiple_Take(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.SelectMany_with_multiple_Take(async))).Message); + { + await base.SelectMany_with_multiple_Take(async); + + AssertSql( + """ +SELECT "o1"."OrderID", "o1"."CustomerID", "o1"."EmployeeID", "o1"."OrderDate" +FROM "Customers" AS "c" +INNER JOIN ( + SELECT "o0"."OrderID", "o0"."CustomerID", "o0"."EmployeeID", "o0"."OrderDate" + FROM ( + SELECT "o"."OrderID", "o"."CustomerID", "o"."EmployeeID", "o"."OrderDate", ROW_NUMBER() OVER(PARTITION BY "o"."CustomerID" ORDER BY "o"."OrderID") AS "row" + FROM "Orders" AS "o" + ) AS "o0" + WHERE "o0"."row" <= 3 +) AS "o1" ON "c"."CustomerID" = "o1"."CustomerID" +"""); + } public override async Task Select_with_multiple_Take(bool async) { @@ -222,15 +235,10 @@ public override async Task Select_with_multiple_Take(bool async) @p='5' @p0='3' -SELECT "c0"."CustomerID", "c0"."Address", "c0"."City", "c0"."CompanyName", "c0"."ContactName", "c0"."ContactTitle", "c0"."Country", "c0"."Fax", "c0"."Phone", "c0"."PostalCode", "c0"."Region" -FROM ( - SELECT "c"."CustomerID", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" - FROM "Customers" AS "c" - ORDER BY "c"."CustomerID" - LIMIT @p -) AS "c0" -ORDER BY "c0"."CustomerID" -LIMIT @p0 +SELECT "c"."CustomerID", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" +FROM "Customers" AS "c" +ORDER BY "c"."CustomerID" +LIMIT min(@p, @p0) """); } @@ -302,12 +310,6 @@ public override async Task Reverse_in_SelectMany_with_Take(bool async) (await Assert.ThrowsAsync( () => base.Reverse_in_SelectMany_with_Take(async))).Message); - public override async Task Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault_2(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault_2(async))).Message); - public override Task Member_binding_after_ctor_arguments_fails_with_client_eval(bool async) => AssertTranslationFailed(() => base.Member_binding_after_ctor_arguments_fails_with_client_eval(async)); From 3e3c6952c2d6eaeb5008e4f1f6d084b80b8b7fd2 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Tue, 24 Dec 2024 11:09:14 +0100 Subject: [PATCH 5/5] Mark `SelectExpression.SetLimit` as internal --- src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 8beb4742065..b510c493bd2 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1931,6 +1931,7 @@ public void ApplyLimit(SqlExpression sqlExpression) /// Sets a new limit of the to limit the number of rows returned in the result set. /// /// An expression representing limit row count. + [EntityFrameworkInternal] public void SetLimit(SqlExpression sqlExpression) { Limit = sqlExpression;