Skip to content

Commit 14b7740

Browse files
committed
NH-2486, NH-2645, NH-2380 - Ability to perform Distinct(...) after projection.
1 parent 4c38acb commit 14b7740

File tree

5 files changed

+124
-15
lines changed

5 files changed

+124
-15
lines changed

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ public void CountDistinctProperty_ReturnsNumberOfDistinctEntriesForThatProperty(
2222
.Distinct()
2323
.Count();
2424

25-
Console.WriteLine(result);
26-
2725
Assert.That(result, Is.EqualTo(387));
2826
}
2927

@@ -35,8 +33,6 @@ public void CountProperty_ReturnsNumberOfNonNullEntriesForThatProperty()
3533
.Select(x => x.ShippingDate)
3634
.Count();
3735

38-
Console.WriteLine(result);
39-
4036
Assert.That(result, Is.EqualTo(809));
4137
}
4238

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System;
2+
using System.Linq;
3+
using NHibernate.Cfg;
4+
using NUnit.Framework;
5+
using SharpTestsEx;
6+
7+
namespace NHibernate.Test.Linq.ByMethod
8+
{
9+
[TestFixture]
10+
public class DistinctTests : LinqTestCase
11+
{
12+
public class OrderDto
13+
{
14+
public DateTime? ShippingDate { get; set; }
15+
public DateTime? OrderDate { get; set; }
16+
}
17+
18+
private static T Transform<T>(T value)
19+
{
20+
return value;
21+
}
22+
23+
[Test]
24+
public void DistinctOnAnonymousTypeProjection()
25+
{
26+
//NH-2380
27+
var result = db.Orders
28+
.Select(x => new {x.ShippingDate})
29+
.Distinct()
30+
.ToArray();
31+
32+
result.Length.Should().Be.EqualTo(388);
33+
}
34+
35+
[Test]
36+
public void DistinctOnComplexAnonymousTypeProjection()
37+
{
38+
//NH-2380
39+
var result = db.Orders
40+
.Select(x => new
41+
{
42+
x.ShippingDate,
43+
x.OrderDate
44+
})
45+
.Distinct()
46+
.ToArray();
47+
48+
result.Length.Should().Be.EqualTo(774);
49+
}
50+
51+
[Test]
52+
public void DistinctOnTypeProjection()
53+
{
54+
//NH-2486
55+
OrderDto[] result = db.Orders
56+
.Select(x => new OrderDto
57+
{
58+
ShippingDate = x.ShippingDate
59+
})
60+
.Distinct()
61+
.ToArray();
62+
63+
result.Length.Should().Be.EqualTo(388);
64+
}
65+
66+
[Test]
67+
public void DistinctOnTypeProjectionTwoProperty()
68+
{
69+
//NH-2486
70+
OrderDto[] result = db.Orders
71+
.Select(x => new OrderDto
72+
{
73+
ShippingDate = x.ShippingDate,
74+
OrderDate = x.OrderDate
75+
})
76+
.Distinct()
77+
.ToArray();
78+
79+
result.Length.Should().Be.EqualTo(774);
80+
}
81+
82+
[Test]
83+
public void DistinctOnTypeProjectionWithCustomProjectionMethods()
84+
{
85+
//NH-2645
86+
OrderDto[] result = db.Orders
87+
.Select(x => new OrderDto
88+
{
89+
ShippingDate = Transform(x.ShippingDate),
90+
OrderDate = Transform(x.OrderDate)
91+
})
92+
.Distinct()
93+
.ToArray();
94+
95+
result.Length.Should().Be.EqualTo(774);
96+
}
97+
}
98+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@
684684
<Compile Include="NHSpecificTest\AccessAndCorrectPropertyName\Model.cs" />
685685
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Domain.cs" />
686686
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Fixture.cs" />
687+
<Compile Include="Linq\ByMethod\DistinctTests.cs" />
687688
<Compile Include="Component\Basic\ComponentWithUniqueConstraintTests.cs" />
688689
<Compile Include="NHSpecificTest\NH1082\SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs" />
689690
<Compile Include="NHSpecificTest\NH2756\Fixture.cs" />

src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@
22
using System.Linq;
33
using System.Linq.Expressions;
44
using NHibernate.Hql.Ast;
5+
using NHibernate.Linq.Expressions;
56
using Remotion.Linq.Parsing;
67

78
namespace NHibernate.Linq.Visitors
89
{
910
public class SelectClauseVisitor : ExpressionTreeVisitor
1011
{
12+
private readonly HqlTreeBuilder _hqlTreeBuilder = new HqlTreeBuilder();
1113
private HashSet<Expression> _hqlNodes;
1214
private readonly ParameterExpression _inputParameter;
1315
private readonly VisitorParameters _parameters;
1416
private int _iColumn;
1517
private List<HqlExpression> _hqlTreeNodes = new List<HqlExpression>();
18+
private readonly HqlGeneratorExpressionTreeVisitor _hqlVisitor;
1619

1720
public SelectClauseVisitor(System.Type inputType, VisitorParameters parameters)
1821
{
1922
_inputParameter = Expression.Parameter(inputType, "input");
2023
_parameters = parameters;
24+
_hqlVisitor = new HqlGeneratorExpressionTreeVisitor(_parameters);
2125
}
2226

2327
public LambdaExpression ProjectionExpression { get; private set; }
@@ -29,19 +33,32 @@ public IEnumerable<HqlExpression> GetHqlNodes()
2933

3034
public void Visit(Expression expression)
3135
{
32-
// First, find the sub trees that can be expressed purely in HQL
36+
var distinct = expression as NhDistinctExpression;
37+
if (distinct != null)
38+
{
39+
expression = distinct.Expression;
40+
}
41+
42+
// Find the sub trees that can be expressed purely in HQL
3343
_hqlNodes = new SelectClauseHqlNominator(_parameters).Nominate(expression);
3444

3545
// Now visit the tree
36-
Expression projection = VisitExpression(expression);
46+
var projection = VisitExpression(expression);
3747

3848
if ((projection != expression) && !_hqlNodes.Contains(expression))
3949
{
4050
ProjectionExpression = Expression.Lambda(projection, _inputParameter);
4151
}
4252

43-
// Finally, handle any boolean results in the output nodes
53+
// Handle any boolean results in the output nodes
4454
_hqlTreeNodes = BooleanToCaseConvertor.Convert(_hqlTreeNodes).ToList();
55+
56+
if (distinct != null)
57+
{
58+
var treeNodes = new List<HqlTreeNode>(_hqlTreeNodes.Count + 1) {_hqlTreeBuilder.Distinct()};
59+
treeNodes.AddRange(_hqlTreeNodes);
60+
_hqlTreeNodes = new List<HqlExpression>(1) {_hqlTreeBuilder.ExpressionSubTreeHolder(treeNodes)};
61+
}
4562
}
4663

4764
public override Expression VisitExpression(Expression expression)
@@ -53,13 +70,10 @@ public override Expression VisitExpression(Expression expression)
5370

5471
if (_hqlNodes.Contains(expression))
5572
{
56-
// Pure HQL evaluation - TODO - cache the Visitor?
57-
var hqlVisitor = new HqlGeneratorExpressionTreeVisitor(_parameters);
58-
59-
_hqlTreeNodes.Add(hqlVisitor.Visit(expression).AsExpression());
73+
// Pure HQL evaluation
74+
_hqlTreeNodes.Add(_hqlVisitor.Visit(expression).AsExpression());
6075

61-
return Expression.Convert(
62-
Expression.ArrayIndex(_inputParameter, Expression.Constant(_iColumn++)), expression.Type);
76+
return Expression.Convert(Expression.ArrayIndex(_inputParameter, Expression.Constant(_iColumn++)), expression.Type);
6377
}
6478

6579
// Can't handle this node with HQL. Just recurse down, and emit the expression

src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -853,8 +853,8 @@ private SqlString GenerateSelectSizeString(ISessionImplementor sessionImplemento
853853
protected virtual string GetCountSqlSelectClause()
854854
{
855855
// NH: too many "if" when each collection can have its persister
856-
if (isCollectionIntegerIndex) return string.Format("coalesce(max({0}) + 1, 0)", IndexColumnNames[0]); // Do we need this "optimization"?
857-
return string.Format("count({0})", HasIndex ? GetIndexCountExpression() : ElementColumnNames[0]);
856+
if (isCollectionIntegerIndex) return string.Format("coalesce(max({0}) + 1, 0)", IndexColumnNames[0]); // Do we need this "optimization"?
857+
return string.Format("count({0})", HasIndex ? GetIndexCountExpression() : ElementColumnNames[0]);
858858
}
859859

860860
private string GetIndexCountExpression()

0 commit comments

Comments
 (0)