Skip to content

Commit 1dfdc65

Browse files
authored
Support Aggregate subqueries with paging on MS SQL Server (#2518)
1 parent b21a978 commit 1dfdc65

File tree

5 files changed

+133
-11
lines changed

5 files changed

+133
-11
lines changed

src/NHibernate.Test/Async/Linq/WhereSubqueryTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,50 @@ where subquery.Any(x => x.OrderId == order.OrderId)
466466
Assert.That(query.Count, Is.EqualTo(61));
467467
}
468468

469+
[Test]
470+
public async Task OrdersWithSubquery9CountAsync()
471+
{
472+
if (Dialect is MySQLDialect)
473+
Assert.Ignore("MySQL does not support LIMIT in subqueries.");
474+
475+
if (!Dialect.SupportsScalarSubSelects)
476+
Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries");
477+
478+
var ordersQuery = db.Orders
479+
.Where(x => x.Employee.EmployeeId > 5)
480+
.OrderByDescending(x => x.OrderId)
481+
.Take(2);
482+
483+
var orderLines = await (db.OrderLines
484+
.Where(x => ordersQuery.Count(o => o == x.Order) > 0)
485+
.OrderBy(x => x.Id)
486+
.ToListAsync());
487+
488+
Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines));
489+
}
490+
491+
[Test]
492+
public async Task OrdersWithSubquery9SumAsync()
493+
{
494+
if (Dialect is MySQLDialect)
495+
Assert.Ignore("MySQL does not support LIMIT in subqueries.");
496+
497+
if (!Dialect.SupportsScalarSubSelects)
498+
Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries");
499+
500+
var ordersQuery = db.Orders
501+
.Where(x => x.Employee.EmployeeId > 5)
502+
.OrderByDescending(x => x.OrderId)
503+
.Take(2);
504+
505+
var orderLines = await (db.OrderLines
506+
.Where(x => ordersQuery.Where(o => o == x.Order).Sum(o => o.Freight.Value) > 0)
507+
.OrderBy(x => x.Id)
508+
.ToListAsync());
509+
510+
Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines));
511+
}
512+
469513
[Test(Description = "GH2479")]
470514
public async Task OrdersWithSubquery11Async()
471515
{

src/NHibernate.Test/Linq/ByMethod/DistinctTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ public void DistinctOnAnonymousTypeProjection()
3232
Assert.That(result.Length, Is.EqualTo(388));
3333
}
3434

35+
[Test]
36+
public void DistinctAndOrderByOnAnonymousTypeProjection()
37+
{
38+
var result = db.Orders
39+
.Where(x => x.ShippingDate != null)
40+
.Select(x => new { x.ShippingDate })
41+
.OrderByDescending(x => x.ShippingDate)
42+
.Distinct()
43+
.ToArray();
44+
45+
var expectedResults = result
46+
.OrderByDescending(x => x.ShippingDate)
47+
.Distinct()
48+
.ToArray();
49+
50+
Assert.That(result.Length, Is.EqualTo(387));
51+
CollectionAssert.AreEqual(expectedResults, result);
52+
}
53+
3554
[Test]
3655
public void DistinctOnComplexAnonymousTypeProjection()
3756
{

src/NHibernate.Test/Linq/WhereSubqueryTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,50 @@ public void OrdersWithSubquery9A()
553553
Assert.That(orderLines.Count, Is.EqualTo(711), nameof(orderLines));
554554
}
555555

556+
[Test]
557+
public void OrdersWithSubquery9Count()
558+
{
559+
if (Dialect is MySQLDialect)
560+
Assert.Ignore("MySQL does not support LIMIT in subqueries.");
561+
562+
if (!Dialect.SupportsScalarSubSelects)
563+
Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries");
564+
565+
var ordersQuery = db.Orders
566+
.Where(x => x.Employee.EmployeeId > 5)
567+
.OrderByDescending(x => x.OrderId)
568+
.Take(2);
569+
570+
var orderLines = db.OrderLines
571+
.Where(x => ordersQuery.Count(o => o == x.Order) > 0)
572+
.OrderBy(x => x.Id)
573+
.ToList();
574+
575+
Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines));
576+
}
577+
578+
[Test]
579+
public void OrdersWithSubquery9Sum()
580+
{
581+
if (Dialect is MySQLDialect)
582+
Assert.Ignore("MySQL does not support LIMIT in subqueries.");
583+
584+
if (!Dialect.SupportsScalarSubSelects)
585+
Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries");
586+
587+
var ordersQuery = db.Orders
588+
.Where(x => x.Employee.EmployeeId > 5)
589+
.OrderByDescending(x => x.OrderId)
590+
.Take(2);
591+
592+
var orderLines = db.OrderLines
593+
.Where(x => ordersQuery.Where(o => o == x.Order).Sum(o => o.Freight.Value) > 0)
594+
.OrderBy(x => x.Id)
595+
.ToList();
596+
597+
Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines));
598+
}
599+
556600
[Test(Description = "GH2479")]
557601
public void OrdersWithSubquery10()
558602
{

src/NHibernate/Linq/GroupBy/PagingRewriter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Linq;
2+
using NHibernate.Linq.ReWriters;
23
using NHibernate.Linq.Visitors;
34
using Remotion.Linq;
45
using Remotion.Linq.Clauses;
@@ -53,7 +54,7 @@ private static void FlattenSubQuery(SubQueryExpression subQueryExpression, Query
5354
var where = new WhereClause(new SubQueryExpression(newSubQueryModel));
5455
queryModel.BodyClauses.Add(where);
5556

56-
if (!queryModel.BodyClauses.OfType<OrderByClause>().Any())
57+
if (RemoveUnnecessaryBodyOperators.IsOrderByNeeded(queryModel) && !queryModel.BodyClauses.OfType<OrderByClause>().Any())
5758
{
5859
var orderByClauses = subQueryModel.BodyClauses.OfType<OrderByClause>();
5960
foreach (var orderByClause in orderByClauses)

src/NHibernate/Linq/ReWriters/RemoveUnnecessaryBodyOperators.cs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Linq;
3+
using NHibernate.Linq.Expressions;
34
using NHibernate.Linq.Visitors;
45
using Remotion.Linq;
56
using Remotion.Linq.Clauses;
@@ -21,20 +22,33 @@ public static void ReWrite(QueryModel queryModel)
2122

2223
internal static void RemoveUnnecessaryOrderByClauses(QueryModel queryModel)
2324
{
24-
if (queryModel.ResultOperators.Count == 1 &&
25-
queryModel.ResultOperators.All(
26-
r => r is ContainsResultOperator || r is AnyResultOperator || r is AllResultOperator))
25+
if (IsOrderByNeeded(queryModel))
26+
return;
27+
28+
// For these operators, we can remove any order-by clause
29+
var bodyClauses = queryModel.BodyClauses;
30+
for (int i = bodyClauses.Count - 1; i >= 0; i--)
2731
{
28-
// For these operators, we can remove any order-by clause
29-
var bodyClauses = queryModel.BodyClauses;
30-
for (int i = bodyClauses.Count - 1; i >= 0; i--)
31-
{
32-
if (bodyClauses[i] is OrderByClause)
33-
bodyClauses.RemoveAt(i);
34-
}
32+
if (bodyClauses[i] is OrderByClause)
33+
bodyClauses.RemoveAt(i);
3534
}
3635
}
3736

37+
internal static bool IsOrderByNeeded(QueryModel queryModel)
38+
{
39+
switch (queryModel.ResultOperators.Count)
40+
{
41+
case 1:
42+
var r = queryModel.ResultOperators[0];
43+
return !(r is AnyResultOperator || r is AllResultOperator || r is ContainsResultOperator);
44+
case 0:
45+
var s = queryModel.SelectClause.Selector;
46+
return !(s is NhAggregatedExpression) || s is NhDistinctExpression;
47+
}
48+
49+
return true;
50+
}
51+
3852
public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index)
3953
{
4054
if (resultOperator is CountResultOperator || resultOperator is LongCountResultOperator)

0 commit comments

Comments
 (0)