Skip to content

Commit 7a43e33

Browse files
committed
Merge pull request #433 from PleasantD/NH-3800
NH-3681 - Allow the GroupBySelectClauseRewriter to correctly match the group by elements NH-3800 - Unwrapping constant ArrayIndex expressions to their inner expressions
2 parents adca869 + 727797a commit 7a43e33

File tree

10 files changed

+397
-9
lines changed

10 files changed

+397
-9
lines changed

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -507,14 +507,22 @@ public void ProjectingWithSubQueriesFilteredByTheAggregateKey()
507507
Assert.That(result[15].FirstOrder, Is.EqualTo(10255));
508508
}
509509

510-
[Test(Description = "NH-3681"), KnownBug("NH-3681 not yet fixed", "NHibernate.HibernateException")]
510+
[Test(Description = "NH-3681")]
511511
public void SelectManyGroupByAggregateProjection()
512512
{
513513
var result = (from o in db.Orders
514-
from ol in o.OrderLines
515-
group ol by ol.Product.ProductId
516-
into grp
517-
select new {ProductId = grp.Key, Sum = grp.Sum(x => x.UnitPrice)}
514+
from ol in o.OrderLines
515+
group ol by ol.Product.ProductId
516+
into grp
517+
select new
518+
{
519+
ProductId = grp.Key,
520+
Sum = grp.Sum(x => x.UnitPrice),
521+
Count = grp.Count(),
522+
Avg = grp.Average(x => x.UnitPrice),
523+
Min = grp.Min(x => x.UnitPrice),
524+
Max = grp.Max(x => x.UnitPrice),
525+
}
518526
).ToList();
519527

520528
Assert.That(result.Count, Is.EqualTo(77));
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace NHibernate.Test.NHSpecificTest.NH3800
7+
{
8+
public class Project
9+
{
10+
public Project()
11+
{
12+
Components = new List<Component>();
13+
}
14+
15+
public virtual Guid Id { get; set; }
16+
public virtual string Name { get; set; }
17+
public virtual IList<Component> Components { get; set; }
18+
}
19+
20+
public class Component
21+
{
22+
public virtual Guid Id { get; set; }
23+
public virtual string Name { get; set; }
24+
public virtual Project Project { get; set; }
25+
}
26+
27+
public class TimeRecord
28+
{
29+
public TimeRecord()
30+
{
31+
Components = new List<Component>();
32+
Tags = new List<Tag>();
33+
}
34+
35+
public virtual Guid Id { get; set; }
36+
public virtual double TimeInHours { get; set; }
37+
public virtual Project Project { get; set; }
38+
public virtual IList<Component> Components { get; set; }
39+
public virtual IList<Tag> Tags { get; set; }
40+
41+
}
42+
43+
public class Tag
44+
{
45+
public virtual Guid Id { get; set; }
46+
public virtual string Name { get; set; }
47+
}
48+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Linq.Expressions;
6+
using System.Reflection;
7+
using System.Text;
8+
using System.Threading;
9+
using NHibernate.Linq;
10+
using NHibernate.Test.ExceptionsTest;
11+
using NHibernate.Test.MappingByCode;
12+
using NUnit.Framework;
13+
14+
namespace NHibernate.Test.NHSpecificTest.NH3800
15+
{
16+
[TestFixture]
17+
public class Fixture : BugTestCase
18+
{
19+
protected override void OnSetUp()
20+
{
21+
var tagA = new Tag() { Name = "A" };
22+
var tagB = new Tag() { Name = "B" };
23+
24+
var project1 = new Project { Name = "ProjectOne" };
25+
var compP1_x = new Component() { Name = "PONEx", Project = project1 };
26+
var compP1_y = new Component() { Name = "PONEy", Project = project1 };
27+
28+
var project2 = new Project { Name = "ProjectTwo" };
29+
var compP2_x = new Component() { Name = "PTWOx", Project = project2 };
30+
var compP2_y = new Component() { Name = "PTWOy", Project = project2 };
31+
32+
using (var session = OpenSession())
33+
using (var transaction = session.BeginTransaction())
34+
{
35+
session.Save(tagA);
36+
session.Save(tagB);
37+
session.Save(project1);
38+
session.Save(compP1_x);
39+
session.Save(compP1_y);
40+
session.Save(project2);
41+
session.Save(compP2_x);
42+
session.Save(compP2_y);
43+
44+
session.Save(new TimeRecord { TimeInHours = 1, Project = null, Components = { }, Tags = { tagA } });
45+
session.Save(new TimeRecord { TimeInHours = 2, Project = null, Components = { }, Tags = { tagB } });
46+
47+
session.Save(new TimeRecord { TimeInHours = 3, Project = project1, Tags = { tagA, tagB } });
48+
session.Save(new TimeRecord { TimeInHours = 4, Project = project1, Components = { compP1_x }, Tags = { tagB } });
49+
session.Save(new TimeRecord { TimeInHours = 5, Project = project1, Components = { compP1_y }, Tags = { tagA } });
50+
session.Save(new TimeRecord { TimeInHours = 6, Project = project1, Components = { compP1_x, compP1_y }, Tags = { } });
51+
52+
session.Save(new TimeRecord { TimeInHours = 7, Project = project2, Components = { }, Tags = { tagA, tagB } });
53+
session.Save(new TimeRecord { TimeInHours = 8, Project = project2, Components = { compP2_x }, Tags = { tagB } });
54+
session.Save(new TimeRecord { TimeInHours = 9, Project = project2, Components = { compP2_y }, Tags = { tagA } });
55+
session.Save(new TimeRecord { TimeInHours = 10, Project = project2, Components = { compP2_x, compP2_y }, Tags = { } });
56+
57+
transaction.Commit();
58+
}
59+
}
60+
61+
protected override void OnTearDown()
62+
{
63+
using (var session = OpenSession())
64+
using (var transaction = session.BeginTransaction())
65+
{
66+
session.Delete("from TimeRecord");
67+
session.Delete("from Component");
68+
session.Delete("from Project");
69+
session.Delete("from Tag");
70+
71+
transaction.Commit();
72+
}
73+
}
74+
75+
[Test]
76+
public void ExpectedHql()
77+
{
78+
using (var session = OpenSession())
79+
using (var transaction = session.BeginTransaction())
80+
{
81+
var baseQuery = session.Query<TimeRecord>();
82+
83+
Assert.That(baseQuery.Sum(x => x.TimeInHours), Is.EqualTo(55));
84+
85+
var query = session.CreateQuery(@"
86+
select c.Id, count(t), sum(cast(t.TimeInHours as big_decimal))
87+
from TimeRecord t
88+
left join t.Components as c
89+
group by c.Id");
90+
91+
var results = query.List<object[]>();
92+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 4, 2, 2, 2, 2 }));
93+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 13, 10, 11, 18, 19 }));
94+
95+
Assert.That(results.Sum(x => (decimal?)x[2]), Is.EqualTo(71));
96+
97+
transaction.Rollback();
98+
}
99+
}
100+
101+
[Test]
102+
public void PureLinq()
103+
{
104+
using (var session = OpenSession())
105+
using (var transaction = session.BeginTransaction())
106+
{
107+
var baseQuery = session.Query<TimeRecord>();
108+
var query = from t in baseQuery
109+
from c in t.Components.Select(x => (object)x.Id).DefaultIfEmpty()
110+
let r = new object[] { c, t }
111+
group r by r[0]
112+
into g
113+
select new[] { g.Key, g.Select(x => x[1]).Count(), g.Select(x => x[1]).Sum(x => (decimal?)((TimeRecord)x).TimeInHours) };
114+
115+
var results = query.ToList();
116+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 4, 2, 2, 2, 2 }));
117+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 13, 10, 11, 18, 19 }));
118+
119+
Assert.That(results.Sum(x => (decimal?)x[2]), Is.EqualTo(71));
120+
121+
transaction.Rollback();
122+
}
123+
}
124+
125+
[Test]
126+
public void MethodGroup()
127+
{
128+
using (var session = OpenSession())
129+
using (var transaction = session.BeginTransaction())
130+
{
131+
var baseQuery = session.Query<TimeRecord>();
132+
var query = baseQuery
133+
.SelectMany(t => t.Components.Select(c => c.Id).DefaultIfEmpty().Select(c => new object[] { c, t }))
134+
.GroupBy(g => g[0], g => (TimeRecord)g[1])
135+
.Select(g => new[] { g.Key, g.Count(), g.Sum(x => (decimal?)x.TimeInHours) });
136+
137+
var results = query.ToList();
138+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 4, 2, 2, 2, 2 }));
139+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 13, 10, 11, 18, 19 }));
140+
141+
Assert.That(results.Sum(x => (decimal?)x[2]), Is.EqualTo(71));
142+
143+
transaction.Rollback();
144+
}
145+
}
146+
147+
[Test]
148+
public void ComplexExample()
149+
{
150+
using (var session = OpenSession())
151+
using (var transaction = session.BeginTransaction())
152+
{
153+
var baseQuery = session.Query<TimeRecord>();
154+
155+
Assert.That(baseQuery.Sum(x => x.TimeInHours), Is.EqualTo(55));
156+
157+
var query = baseQuery.Select(t => new object[] { t })
158+
.SelectMany(t => ((TimeRecord)t[0]).Components.Select(c => (object)c.Id).DefaultIfEmpty().Select(c => new[] { t[0], c }))
159+
.SelectMany(t => ((TimeRecord)t[0]).Tags.Select(x => (object)x.Id).DefaultIfEmpty().Select(x => new[] { t[0], t[1], x }))
160+
.GroupBy(j => new[] { ((TimeRecord)j[0]).Project.Id, j[1], j[2] }, j => (TimeRecord)j[0])
161+
.Select(g => new object[] { g.Key, g.Count(), g.Sum(t => (decimal?)t.TimeInHours) });
162+
163+
var results = query.ToList();
164+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }));
165+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 1, 2, 3, 3, 4, 5, 6, 6, 7, 7, 8, 9, 10, 10 }));
166+
167+
Assert.That(results.Sum(x => (decimal?)x[2]), Is.EqualTo(81));
168+
169+
transaction.Rollback();
170+
}
171+
}
172+
173+
[Test]
174+
public void OuterJoinGroupingWithSubQueryInProjection()
175+
{
176+
using (var session = OpenSession())
177+
using (var transaction = session.BeginTransaction())
178+
{
179+
var baseQuery = session.Query<TimeRecord>();
180+
var query = baseQuery
181+
.SelectMany(t => t.Components.Select(c => c.Name).DefaultIfEmpty().Select(c => new object[] { c, t }))
182+
.GroupBy(g => g[0], g => (TimeRecord)g[1])
183+
.Select(g => new[] { g.Key, g.Count(), session.Query<Component>().Count(c => c.Name == (string)g.Key) });
184+
185+
var results = query.ToList();
186+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 4, 2, 2, 2, 2 }));
187+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 0, 1, 1, 1, 1 }));
188+
189+
transaction.Rollback();
190+
}
191+
}
192+
}
193+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
namespace="NHibernate.Test.NHSpecificTest.NH3800"
4+
assembly="NHibernate.Test">
5+
6+
<class name="Project">
7+
<id name="Id" column="ProjectId">
8+
<generator class="guid.comb"/>
9+
</id>
10+
<property name="Name" not-null="true"/>
11+
<bag name="Components" inverse="true" lazy="true" fetch="select">
12+
<key>
13+
<column name="ProjectId" not-null="true" />
14+
</key>
15+
<one-to-many class="Component" />
16+
</bag>
17+
</class>
18+
19+
<class name="Component">
20+
<id name="Id" column="ComponentId">
21+
<generator class="guid.comb"/>
22+
</id>
23+
<property name="Name" not-null="true"/>
24+
<many-to-one name="Project" column="ProjectId" class="Project" not-null="true"/>
25+
</class>
26+
27+
<class name="TimeRecord">
28+
<id name="Id" column="TimeRecordId">
29+
<generator class="guid.comb"/>
30+
</id>
31+
<property name="TimeInHours" not-null="true"/>
32+
<many-to-one name="Project" column="ProjectId" class="Project" />
33+
<bag name="Components" inverse="false" lazy="true" fetch="select">
34+
<key>
35+
<column name="TimeRecordId" not-null="true" />
36+
</key>
37+
<many-to-many class="Component">
38+
<column name="ComponentId" not-null="true" />
39+
</many-to-many>
40+
</bag>
41+
<bag name="Tags" inverse="false" lazy="true" fetch="select">
42+
<key>
43+
<column name="TimeRecordId" not-null="true" />
44+
</key>
45+
<many-to-many class="Tag">
46+
<column name="TagId" not-null="true" />
47+
</many-to-many>
48+
</bag>
49+
</class>
50+
51+
<class name="Tag">
52+
<id name="Id" column="TagId">
53+
<generator class="guid.comb"/>
54+
</id>
55+
<property name="Name" not-null="true"/>
56+
</class>
57+
58+
</hibernate-mapping>

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,8 @@
12631263
<Compile Include="NHSpecificTest\NH2860\SampleTest.cs" />
12641264
<Compile Include="NHSpecificTest\NH3641\Domain.cs" />
12651265
<Compile Include="NHSpecificTest\NH3641\TestFixture.cs" />
1266+
<Compile Include="NHSpecificTest\NH3800\Domain.cs" />
1267+
<Compile Include="NHSpecificTest\NH3800\Fixture.cs" />
12661268
<Compile Include="NHSpecificTest\Properties\CompositePropertyRefTest.cs" />
12671269
<Compile Include="NHSpecificTest\Properties\DynamicEntityTest.cs" />
12681270
<Compile Include="NHSpecificTest\Properties\Model.cs" />
@@ -3147,6 +3149,9 @@
31473149
</EmbeddedResource>
31483150
<EmbeddedResource Include="VersionTest\Db\MsSQL\ProductWithVersionAndLazyProperty.hbm.xml" />
31493151
<EmbeddedResource Include="NHSpecificTest\NH3754\Mappings.hbm.xml" />
3152+
<EmbeddedResource Include="NHSpecificTest\NH3800\Mappings.hbm.xml">
3153+
<SubType>Designer</SubType>
3154+
</EmbeddedResource>
31503155
<EmbeddedResource Include="LazyComponentTest\Person.hbm.xml" />
31513156
<EmbeddedResource Include="NHSpecificTest\NH3372\Mappings.hbm.xml" />
31523157
<EmbeddedResource Include="NHSpecificTest\NH3567\Mappings.hbm.xml" />

src/NHibernate/Linq/ExpressionExtensions.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System;
2+
using System.Linq;
23
using System.Linq.Expressions;
34
using Remotion.Linq.Clauses;
45
using Remotion.Linq.Clauses.Expressions;
@@ -32,5 +33,25 @@ public static bool IsGroupingKeyOf(this MemberExpression expression,GroupResultO
3233

3334
return query.QueryModel.ResultOperators.Contains(groupBy);
3435
}
36+
37+
public static bool IsGroupingElementOf(this QuerySourceReferenceExpression expression, GroupResultOperator groupBy)
38+
{
39+
var fromClause = expression.ReferencedQuerySource as MainFromClause;
40+
if (fromClause == null) return false;
41+
42+
var innerQuerySource = fromClause.FromExpression as QuerySourceReferenceExpression;
43+
if (innerQuerySource == null) return false;
44+
45+
if (innerQuerySource.ReferencedQuerySource.ItemName != groupBy.ItemName
46+
|| innerQuerySource.ReferencedQuerySource.ItemType != groupBy.ItemType) return false;
47+
48+
var innerFromClause = innerQuerySource.ReferencedQuerySource as MainFromClause;
49+
if (innerFromClause == null) return false;
50+
51+
var query = innerFromClause.FromExpression as SubQueryExpression;
52+
if (query == null) return false;
53+
54+
return query.QueryModel.ResultOperators.Contains(groupBy);
55+
}
3556
}
3657
}

0 commit comments

Comments
 (0)