Skip to content

Commit a5c0c6b

Browse files
committed
Linq - add ability to select first or single element from grouping (NH-3180)
1 parent 6fab83f commit a5c0c6b

File tree

3 files changed

+88
-36
lines changed

3 files changed

+88
-36
lines changed

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

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Collections.Generic;
44
using System.Linq;
55
using System.Text.RegularExpressions;
6-
using NHibernate.Cfg;
76
using NHibernate.DomainModel.Northwind.Entities;
87
using NUnit.Framework;
98

@@ -12,11 +11,6 @@ namespace NHibernate.Test.Linq.ByMethod
1211
[TestFixture]
1312
public class GroupByTests : LinqTestCase
1413
{
15-
protected override void Configure(Configuration configuration)
16-
{
17-
configuration.SetProperty(Cfg.Environment.ShowSql, "true");
18-
}
19-
2014
[Test]
2115
public void SingleKeyGroupAndCount()
2216
{
@@ -272,6 +266,65 @@ into g
272266
}
273267

274268

269+
[Test]
270+
public void SelectFirstElementFromProductsGroupedByUnitPrice()
271+
{
272+
//NH-3180
273+
var result = db.Products
274+
.GroupBy(x => x.UnitPrice)
275+
.Select(x => new {x.Key, Count = x.Count()})
276+
.OrderByDescending(x => x.Key)
277+
.First();
278+
279+
Assert.That(result.Key, Is.EqualTo(263.5M));
280+
Assert.That(result.Count, Is.EqualTo(1));
281+
}
282+
283+
[Test]
284+
public void SelectFirstOrDefaultElementFromProductsGroupedByUnitPrice()
285+
{
286+
//NH-3180
287+
var result = db.Products
288+
.GroupBy(x => x.UnitPrice)
289+
.Select(x => new {x.Key, Count = x.Count()})
290+
.OrderByDescending(x => x.Key)
291+
.FirstOrDefault();
292+
293+
Assert.That(result.Key, Is.EqualTo(263.5M));
294+
Assert.That(result.Count, Is.EqualTo(1));
295+
}
296+
297+
298+
[Test]
299+
public void SelectSingleElementFromProductsGroupedByUnitPrice()
300+
{
301+
//NH-3180
302+
var result = db.Products
303+
.GroupBy(x => x.UnitPrice)
304+
.Select(x => new {x.Key, Count = x.Count()})
305+
.Where(x => x.Key == 263.5M)
306+
.OrderByDescending(x => x.Key)
307+
.Single();
308+
309+
Assert.That(result.Key, Is.EqualTo(263.5M));
310+
Assert.That(result.Count, Is.EqualTo(1));
311+
}
312+
313+
[Test]
314+
public void SelectSingleOrDefaultElementFromProductsGroupedByUnitPrice()
315+
{
316+
//NH-3180
317+
var result = db.Products
318+
.GroupBy(x => x.UnitPrice)
319+
.Select(x => new {x.Key, Count = x.Count()})
320+
.Where(x => x.Key == 263.5M)
321+
.OrderByDescending(x => x.Key)
322+
.SingleOrDefault();
323+
324+
Assert.That(result.Key, Is.EqualTo(263.5M));
325+
Assert.That(result.Count, Is.EqualTo(1));
326+
}
327+
275328
private static void CheckGrouping<TKey, TElement>(IEnumerable<IGrouping<TKey, TElement>> groupedItems, Func<TElement, TKey> groupBy)
276329
{
277330
var used = new HashSet<object>();
Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using NHibernate.Linq.Clauses;
45
using NHibernate.Linq.Visitors;
@@ -26,47 +27,44 @@ namespace NHibernate.Linq.GroupBy
2627
/// This class takes such queries, flattens out the re-linq sub-query and re-writes the outer select
2728
/// </para>
2829
/// </summary>
29-
public class AggregatingGroupByRewriter
30+
public static class AggregatingGroupByRewriter
3031
{
31-
private AggregatingGroupByRewriter() { }
32+
private static readonly ICollection<System.Type> AcceptableOuterResultOperators = new HashSet<System.Type>
33+
{
34+
typeof (SkipResultOperator),
35+
typeof (TakeResultOperator),
36+
typeof (FirstResultOperator),
37+
typeof (SingleResultOperator)
38+
};
3239

33-
public static void ReWrite(QueryModel queryModel)
40+
public static void ReWrite(QueryModel queryModel)
3441
{
3542
var subQueryExpression = queryModel.MainFromClause.FromExpression as SubQueryExpression;
3643

3744
if ((subQueryExpression != null) &&
3845
(subQueryExpression.QueryModel.ResultOperators.Count == 1) &&
3946
(subQueryExpression.QueryModel.ResultOperators[0] is GroupResultOperator))
4047
{
41-
FlattenSubQuery(subQueryExpression, queryModel);
48+
FlattenSubQuery(queryModel, subQueryExpression.QueryModel);
4249
}
4350
}
4451

45-
46-
private static readonly System.Type[] AcceptableOuterResultOperators = new[]
47-
{
48-
typeof (SkipResultOperator),
49-
typeof (TakeResultOperator),
50-
};
51-
52-
53-
private static void FlattenSubQuery(SubQueryExpression subQueryExpression, QueryModel queryModel)
52+
private static void FlattenSubQuery(QueryModel queryModel, QueryModel subQueryModel)
5453
{
55-
foreach (var resultOperator in queryModel.ResultOperators)
54+
foreach (var resultOperator in queryModel.ResultOperators.Where(resultOperator => !AcceptableOuterResultOperators.Contains(resultOperator.GetType())))
5655
{
57-
if (!AcceptableOuterResultOperators.Contains(resultOperator.GetType()))
58-
throw new NotImplementedException("Cannot use group by with the "
59-
+ resultOperator.GetType().Name + " result operator.");
56+
throw new NotImplementedException("Cannot use group by with the " + resultOperator.GetType().Name + " result operator.");
6057
}
6158

6259
// Move the result operator up.
63-
var groupBy = (GroupResultOperator) subQueryExpression.QueryModel.ResultOperators[0];
64-
queryModel.ResultOperators.Insert(0, groupBy);
60+
var groupBy = (GroupResultOperator) subQueryModel.ResultOperators[0];
61+
62+
queryModel.ResultOperators.Insert(0, groupBy);
6563

6664
for (int i = 0; i < queryModel.BodyClauses.Count; i++)
6765
{
6866
var clause = queryModel.BodyClauses[i];
69-
clause.TransformExpressions(s => GroupBySelectClauseRewriter.ReWrite(s, groupBy, subQueryExpression.QueryModel));
67+
clause.TransformExpressions(s => GroupBySelectClauseRewriter.ReWrite(s, groupBy, subQueryModel));
7068

7169
//all outer where clauses actually are having clauses
7270
var whereClause = clause as WhereClause;
@@ -77,21 +75,19 @@ private static void FlattenSubQuery(SubQueryExpression subQueryExpression, Query
7775
}
7876
}
7977

80-
foreach (var bodyClause in subQueryExpression.QueryModel.BodyClauses)
81-
{
78+
foreach (var bodyClause in subQueryModel.BodyClauses)
8279
queryModel.BodyClauses.Add(bodyClause);
83-
}
8480

8581
// Replace the outer select clause...
8682
queryModel.SelectClause.TransformExpressions(s =>
87-
GroupBySelectClauseRewriter.ReWrite(s, groupBy, subQueryExpression.QueryModel));
83+
GroupBySelectClauseRewriter.ReWrite(s, groupBy, subQueryModel));
8884

8985
// Point all query source references to the outer from clause
90-
queryModel.TransformExpressions(s =>
91-
new SwapQuerySourceVisitor(queryModel.MainFromClause, subQueryExpression.QueryModel.MainFromClause).Swap(s));
86+
var visitor = new SwapQuerySourceVisitor(queryModel.MainFromClause, subQueryModel.MainFromClause);
87+
queryModel.TransformExpressions(visitor.Swap);
9288

9389
// Replace the outer query source
94-
queryModel.MainFromClause = subQueryExpression.QueryModel.MainFromClause;
90+
queryModel.MainFromClause = subQueryModel.MainFromClause;
9591
}
9692
}
9793
}

src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFirstOrSingleBase.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Linq.Expressions;
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Linq.Expressions;
24
using System.Reflection;
35

46
namespace NHibernate.Linq.Visitors.ResultOperatorProcessors
@@ -7,9 +9,10 @@ public class ProcessFirstOrSingleBase
79
{
810
protected static void AddClientSideEval(MethodInfo target, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree)
911
{
10-
target = target.MakeGenericMethod(queryModelVisitor.CurrentEvaluationType.DataType);
12+
var type = queryModelVisitor.Model.SelectClause.Selector.Type;
13+
target = target.MakeGenericMethod(type);
1114

12-
var parameter = Expression.Parameter(queryModelVisitor.PreviousEvaluationType.DataType, null);
15+
var parameter = Expression.Parameter(typeof(IQueryable<>).MakeGenericType(type), null);
1316

1417
var lambda = Expression.Lambda(
1518
Expression.Call(

0 commit comments

Comments
 (0)