Skip to content

Commit 6214054

Browse files
authored
Merge pull request #522 from PleasantD/NH-3918
NH-3918 - Nominate equality expressions in SELECT clauses
2 parents a1e0eb3 + 130ae83 commit 6214054

File tree

5 files changed

+195
-3
lines changed

5 files changed

+195
-3
lines changed

src/NHibernate.Test/Linq/SelectionTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,35 @@ public void CanSelectConditionalEntity()
450450
Assert.That(fatherInsteadOfChild, Has.Exactly(2).With.Property("SerialNumber").EqualTo("5678"));
451451
}
452452

453+
[Test]
454+
public void CanSelectConditionalEntityWithCast()
455+
{
456+
var fatherInsteadOfChild = db.Mammals.Select(a => a.Father.SerialNumber == "5678" ? (object)a.Father : (object)a).ToList();
457+
Assert.That(fatherInsteadOfChild, Has.Exactly(2).With.Property("SerialNumber").EqualTo("5678"));
458+
}
459+
460+
[Test]
461+
public void CanSelectConditionalEntityValue()
462+
{
463+
var fatherInsteadOfChild = db.Animals.Select(a => a.Father.SerialNumber == "5678" ? a.Father.SerialNumber : a.SerialNumber).ToList();
464+
Assert.That(fatherInsteadOfChild, Has.Exactly(2).EqualTo("5678"));
465+
}
466+
467+
[Test]
468+
public void CanSelectConditionalEntityValueWithEntityComparison()
469+
{
470+
var father = db.Animals.Single(a => a.SerialNumber == "5678");
471+
var fatherInsteadOfChild = db.Animals.Select(a => a.Father == father ? a.Father.SerialNumber : a.SerialNumber).ToList();
472+
Assert.That(fatherInsteadOfChild, Has.Exactly(2).EqualTo("5678"));
473+
}
474+
475+
[Test]
476+
public void CanSelectConditionalEntityValueWithEntityComparisonRepeat()
477+
{
478+
// Check again in the same ISessionFactory to ensure caching doesn't cause failures
479+
CanSelectConditionalEntityValueWithEntityComparison();
480+
}
481+
453482
[Test]
454483
public void CanSelectConditionalObject()
455484
{
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Linq;
6+
using NHibernate.Mapping.ByCode;
7+
using NUnit.Framework;
8+
9+
namespace NHibernate.Test.NHSpecificTest.NH3918
10+
{
11+
public class ByCodeFixture : TestCaseMappingByCode
12+
{
13+
protected override HbmMapping GetMappings()
14+
{
15+
var mapper = new ModelMapper();
16+
mapper.Class<Owner>(rc =>
17+
{
18+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
19+
rc.Property(x => x.Name);
20+
});
21+
mapper.Class<Entity>(rc =>
22+
{
23+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
24+
rc.Property(x => x.Name);
25+
rc.ManyToOne(m => m.Owner, m =>
26+
{
27+
m.Column("OwnerId");
28+
});
29+
});
30+
31+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
32+
}
33+
34+
protected override void OnSetUp()
35+
{
36+
using (ISession session = OpenSession())
37+
using (ITransaction transaction = session.BeginTransaction())
38+
{
39+
var bob = CreateOwner(session, "Bob");
40+
var carl = CreateOwner(session, "Carl");
41+
var doug = CreateOwner(session, "Doug");
42+
43+
CreateEntity(session, "Test 1", bob);
44+
CreateEntity(session, "Test 2", carl);
45+
CreateEntity(session, "Test 3", doug);
46+
CreateEntity(session, "Test 4", bob);
47+
CreateEntity(session, "Test 5", carl);
48+
CreateEntity(session, "Test 6", doug);
49+
50+
session.Flush();
51+
transaction.Commit();
52+
}
53+
}
54+
55+
protected Owner CreateOwner(ISession session, string name)
56+
{
57+
var t = new Owner { Name = name };
58+
session.Save(t);
59+
return t;
60+
}
61+
62+
protected void CreateEntity(ISession session, string name, Owner owner)
63+
{
64+
var t = new Entity
65+
{
66+
Name = name,
67+
Owner = owner,
68+
};
69+
session.Save(t);
70+
}
71+
72+
protected override void OnTearDown()
73+
{
74+
using (ISession session = OpenSession())
75+
using (ITransaction transaction = session.BeginTransaction())
76+
{
77+
session.Delete("from Entity");
78+
session.Delete("from Owner");
79+
80+
session.Flush();
81+
transaction.Commit();
82+
}
83+
}
84+
85+
[Test]
86+
public void EntityComparisonTest()
87+
{
88+
using (ISession session = OpenSession())
89+
using (session.BeginTransaction())
90+
{
91+
var bob = session.Query<Owner>().Single(o => o.Name == "Bob");
92+
93+
var queryWithWhere = session.Query<Entity>()
94+
.Where(WhereExpression(bob))
95+
.Select(e => e.Name);
96+
var queryWithSelect = session.Query<Entity>()
97+
.Select(SelectExpression(bob));
98+
99+
var resultsFromWhere = queryWithWhere.ToList();
100+
var resultsFromSelect = queryWithSelect.ToList();
101+
102+
Assert.That(resultsFromSelect.Where(x => (bool)x[1]).Select(x => (string)x[0]), Is.EquivalentTo(resultsFromWhere));
103+
}
104+
}
105+
106+
[Test]
107+
public void EntityComparisonTestAgain()
108+
{
109+
// When the entire fixture is run this will execute the test again within the same ISessionFactory which will test caching
110+
EntityComparisonTest();
111+
}
112+
113+
protected Expression<Func<Entity, bool>> WhereExpression(Owner owner)
114+
{
115+
return e => e.Owner == owner;
116+
}
117+
118+
protected Expression<Func<Entity, object[]>> SelectExpression(Owner owner)
119+
{
120+
return e => new object[]
121+
{
122+
e.Name,
123+
e.Owner == owner
124+
};
125+
}
126+
}
127+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.NH3918
5+
{
6+
public interface IModelObject
7+
{
8+
Guid Id { get; set; }
9+
string Name { get; set; }
10+
}
11+
12+
public class Entity : IModelObject
13+
{
14+
public virtual Guid Id { get; set; }
15+
public virtual string Name { get; set; }
16+
17+
public virtual Owner Owner { get; set; }
18+
}
19+
20+
public class Owner : IModelObject
21+
{
22+
public virtual Guid Id { get; set; }
23+
public virtual string Name { get; set; }
24+
}
25+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,8 @@
736736
<Compile Include="NHSpecificTest\NH2204\Fixture.cs" />
737737
<Compile Include="NHSpecificTest\NH3912\BatcherLovingEntity.cs" />
738738
<Compile Include="NHSpecificTest\NH3912\ReusableBatcherFixture.cs" />
739+
<Compile Include="NHSpecificTest\NH3918\Model.cs" />
740+
<Compile Include="NHSpecificTest\NH3918\FixtureByCode.cs" />
739741
<Compile Include="NHSpecificTest\NH3414\Entity.cs" />
740742
<Compile Include="NHSpecificTest\NH3414\FixtureByCode.cs" />
741743
<Compile Include="NHSpecificTest\NH2218\Fixture.cs" />

src/NHibernate/Linq/Visitors/SelectClauseNominator.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public override Expression VisitExpression(Expression expression)
6161
return innerExpression;
6262
}
6363

64-
var projectConstantsInHql = _stateStack.Peek() || IsRegisteredFunction(expression);
64+
var projectConstantsInHql = _stateStack.Peek() || expression.NodeType == ExpressionType.Equal || IsRegisteredFunction(expression);
6565

6666
// Set some flags, unless we already have proper values for them:
6767
// projectConstantsInHql if they are inside a method call executed server side.
@@ -153,8 +153,17 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj
153153
if (expression.NodeType == ExpressionType.Call)
154154
{
155155
// Depends if it's in the function registry
156-
if (!IsRegisteredFunction(expression))
157-
return false;
156+
return IsRegisteredFunction(expression);
157+
}
158+
159+
if (expression.NodeType == ExpressionType.Conditional)
160+
{
161+
// Theoretically, any conditional that returns a CAST-able primitive should be constructable in HQL.
162+
// The type needs to be CAST-able because HQL wraps the CASE clause in a CAST and only supports
163+
// certain types (as defined by the HqlIdent constructor that takes a System.Type as the second argument).
164+
// However, this may still not cover all cases, so to limit the nomination of conditional expressions,
165+
// we will only consider those which are already getting constants projected into them.
166+
return projectConstantsInHql;
158167
}
159168

160169
// Assume all is good

0 commit comments

Comments
 (0)