From f8c7a579ff09e79cb94a9b034666008aee8b17a5 Mon Sep 17 00:00:00 2001 From: Derek Gray Date: Thu, 9 Feb 2017 14:27:58 -0600 Subject: [PATCH] Push down subquery before using COUNT/COUNT_BIG if the query has an offset (fixes #7523) --- .../RelationalResultOperatorHandler.cs | 10 ++++++ .../AsyncQueryTestBase.cs | 27 +++++++++++++++ .../QueryTestBase.cs | 14 ++++++++ .../QuerySqlServerTest.cs | 34 +++++++++++++++++++ 4 files changed, 85 insertions(+) diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs index a7f04c66046..dd6f558ffc3 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs @@ -379,6 +379,11 @@ var collectionSelectExpression private static Expression HandleCount(HandlerContext handlerContext) { + if (handlerContext.SelectExpression.Offset != null) + { + handlerContext.SelectExpression.PushDownSubquery(); + } + handlerContext.SelectExpression .SetProjectionExpression( new SqlFunctionExpression( @@ -579,6 +584,11 @@ private static Expression HandleLast(HandlerContext handlerContext) private static Expression HandleLongCount(HandlerContext handlerContext) { + if (handlerContext.SelectExpression.Offset != null) + { + handlerContext.SelectExpression.PushDownSubquery(); + } + handlerContext.SelectExpression .SetProjectionExpression( new SqlFunctionExpression( diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs index 61af66f21dc..21e00de0c17 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs @@ -3593,6 +3593,19 @@ public virtual async Task Select_bitwise_and_with_logical_and() } } + [ConditionalFact] + public virtual async Task Skip_CountAsync() + { + await AssertQuery( + cs => cs.Skip(7).CountAsync()); + } + + [ConditionalFact] + public virtual async Task Skip_LongCountAsync() + { + await AssertQuery( + cs => cs.Skip(7).LongCountAsync()); + } protected NorthwindContext CreateContext() { @@ -3620,6 +3633,20 @@ private async Task AssertQuery( } } + private async Task AssertQuery( + Func, Task> query, + bool assertOrder = false) + where TItem : class + { + using (var context = CreateContext()) + { + TestHelpers.AssertResults( + new[] { await query(NorthwindData.Set()) }, + new[] { await query(context.Set()) }, + assertOrder); + } + } + private async Task AssertQuery( Func, Task> query, bool assertOrder = false) diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs index 5278bbe3eaf..99624bd0644 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs @@ -6767,6 +6767,20 @@ from e2 in ClientDefaultIfEmpty(grouping) select new { City1 = e1.City, City2 = e2 != null ? e2.City : null }); } + [ConditionalFact] + public virtual void Skip_Count() + { + AssertQuery( + cs => cs.Skip(7).Count()); + } + + [ConditionalFact] + public virtual void Skip_LongCount() + { + AssertQuery( + cs => cs.Skip(7).LongCount()); + } + private static IEnumerable ClientDefaultIfEmpty(IEnumerable source) { return source?.Count() == 0 ? new[] { default(TElement) } : source; diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs index 164a0e22209..5d44aaec62e 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs @@ -6827,6 +6827,40 @@ FROM [Employees] AS [e1] Sql); } + public override void Skip_Count() + { + base.Skip_Count(); + + Assert.Equal( + @"@__p_0: 7 + +SELECT COUNT(*) +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 (SELECT 1) + OFFSET @__p_0 ROWS +) AS [t]", + Sql); + } + + public override void Skip_LongCount() + { + base.Skip_LongCount(); + + Assert.Equal( + @"@__p_0: 7 + +SELECT COUNT_BIG(*) +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 (SELECT 1) + OFFSET @__p_0 ROWS +) AS [t]", + Sql); + } + private const string FileLineEnding = @" ";