Skip to content

Commit f5b97a8

Browse files
committed
Merge pull request #390 from jlevitt/nh3634
NH-3634 - Fix comparing property with component with nullable properties in Criteria/QueryOver
2 parents 5225d8f + 297a900 commit f5b97a8

File tree

10 files changed

+455
-10
lines changed

10 files changed

+455
-10
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace NHibernate.Test.NHSpecificTest.NH3634
2+
{
3+
class CachedPerson
4+
{
5+
public virtual int Id { get; set; }
6+
public virtual string Name { get; set; }
7+
public virtual Connection Connection { get; set; }
8+
}
9+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using NHibernate.Mapping.ByCode;
2+
using NHibernate.Mapping.ByCode.Conformist;
3+
4+
namespace NHibernate.Test.NHSpecificTest.NH3634
5+
{
6+
class CachedPersonMapper : ClassMapping<CachedPerson>
7+
{
8+
public CachedPersonMapper()
9+
{
10+
Id(p => p.Id, m => m.Generator(Generators.Identity));
11+
Lazy(false);
12+
Cache(m => m.Usage(CacheUsage.ReadWrite));
13+
Table("cachedpeople");
14+
Property(p => p.Name);
15+
Component(
16+
p => p.Connection,
17+
m =>
18+
{
19+
m.Class<Connection>();
20+
m.Property(c => c.ConnectionType, mapper => mapper.NotNullable(true));
21+
m.Property(c => c.Address, mapper => mapper.NotNullable(false));
22+
m.Property(c => c.PortName, mapper => mapper.NotNullable(false));
23+
});
24+
}
25+
}
26+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace NHibernate.Test.NHSpecificTest.NH3634
2+
{
3+
class Connection
4+
{
5+
public virtual string ConnectionType { get; set; }
6+
public virtual string Address { get; set; }
7+
public virtual string PortName { get; set; }
8+
}
9+
}
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
using NHibernate.Cfg.MappingSchema;
2+
using NHibernate.Criterion;
3+
using NHibernate.Mapping.ByCode;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.NHSpecificTest.NH3634
7+
{
8+
public class ByCodeFixture : TestCaseMappingByCode
9+
{
10+
protected override HbmMapping GetMappings()
11+
{
12+
var mapper = new ModelMapper();
13+
mapper.AddMapping<PersonMapper>();
14+
mapper.AddMapping<CachedPersonMapper>();
15+
16+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
17+
}
18+
19+
protected override void OnSetUp()
20+
{
21+
using (ISession session = OpenSession())
22+
using (ITransaction transaction = session.BeginTransaction())
23+
{
24+
var bobsConnection = new Connection
25+
{
26+
Address = "test.com",
27+
ConnectionType = "http",
28+
PortName = "80"
29+
};
30+
var e1 = new Person
31+
{
32+
Name = "Bob",
33+
Connection = bobsConnection
34+
};
35+
session.Save(e1);
36+
37+
var sallysConnection = new Connection
38+
{
39+
Address = "test.com",
40+
ConnectionType = "http",
41+
};
42+
var e2 = new Person
43+
{
44+
Name = "Sally",
45+
Connection = sallysConnection
46+
};
47+
session.Save(e2);
48+
49+
var cachedNullConnection = new Connection
50+
{
51+
Address = "test.com",
52+
ConnectionType = "http",
53+
};
54+
var cachedNullConnectionPerson = new CachedPerson
55+
{
56+
Name = "CachedNull",
57+
Connection = cachedNullConnection
58+
};
59+
var cachedNotNullConnection = new Connection
60+
{
61+
Address = "test.com",
62+
ConnectionType = "http",
63+
PortName = "port"
64+
};
65+
var cachedNotNullConnectionPerson = new CachedPerson
66+
{
67+
Name = "CachedNotNull",
68+
Connection = cachedNotNullConnection
69+
};
70+
session.Save(cachedNullConnectionPerson);
71+
session.Save(cachedNotNullConnectionPerson);
72+
73+
session.Flush();
74+
transaction.Commit();
75+
session.Evict(typeof(CachedPerson));
76+
}
77+
}
78+
79+
protected override void OnTearDown()
80+
{
81+
using (ISession session = OpenSession())
82+
using (ITransaction transaction = session.BeginTransaction())
83+
{
84+
session.Delete("from System.Object");
85+
session.Flush();
86+
transaction.Commit();
87+
}
88+
}
89+
90+
[Test]
91+
public void QueryOverComponentWithANullProperty()
92+
{
93+
// Broken at the time NH3634 was reported
94+
// Generates the following Rpc(exec sp_executesql)
95+
// SELECT this_.Id as Id0_0_,
96+
// this_.Name as Name0_0_,
97+
// this_.ConnectionType as Connecti3_0_0_,
98+
// this_.Address as Address0_0_,
99+
// this_.PortName as PortName0_0_
100+
// FROM people this_
101+
// WHERE this_.ConnectionType = @p0
102+
// and this_.Address = @p1
103+
// and this_.PortName = @p2
104+
//
105+
// @p0=N'http',@p1=N'test.com',@p2=NULL
106+
107+
using (ISession session = OpenSession())
108+
using (session.BeginTransaction())
109+
{
110+
var componentToCompare = new Connection
111+
{
112+
ConnectionType = "http",
113+
Address = "test.com",
114+
PortName = null
115+
};
116+
var sally = session.QueryOver<Person>()
117+
.Where(p => p.Connection == componentToCompare)
118+
.SingleOrDefault<Person>();
119+
120+
Assert.That(sally.Name, Is.EqualTo("Sally"));
121+
Assert.That(sally.Connection.PortName, Is.Null);
122+
}
123+
}
124+
125+
[Test]
126+
public void QueryAgainstComponentWithANullPropertyUsingCriteria()
127+
{
128+
// Broken at the time NH3634 was reported
129+
// Generates the following Rpc(exec sp_executesql)
130+
// SELECT this_.Id as Id0_0_,
131+
// this_.Name as Name0_0_,
132+
// this_.ConnectionType as Connecti3_0_0_,
133+
// this_.Address as Address0_0_,
134+
// this_.PortName as PortName0_0_
135+
// FROM people this_
136+
// WHERE this_.ConnectionType = @p0
137+
// and this_.Address = @p1
138+
// and this_.PortName = @p2
139+
//
140+
// @p0=N'http',@p1=N'test.com',@p2=NULL
141+
142+
using (ISession session = OpenSession())
143+
using (session.BeginTransaction())
144+
{
145+
var componentToCompare = new Connection
146+
{
147+
ConnectionType = "http",
148+
Address = "test.com",
149+
PortName = null
150+
};
151+
var sally = session.CreateCriteria<Person>()
152+
.Add(Restrictions.Eq("Connection", componentToCompare))
153+
.UniqueResult<Person>();
154+
155+
Assert.That(sally.Name, Is.EqualTo("Sally"));
156+
Assert.That(sally.Connection.PortName, Is.Null);
157+
}
158+
}
159+
160+
[Test]
161+
public void CachedQueryMissesWithDifferentNotNullComponent()
162+
{
163+
var componentToCompare = new Connection
164+
{
165+
ConnectionType = "http",
166+
Address = "test.com",
167+
PortName = null
168+
};
169+
170+
using (ISession session = OpenSession())
171+
using (ITransaction tx = session.BeginTransaction())
172+
{
173+
var cached = session.CreateCriteria<CachedPerson>()
174+
.Add(Restrictions.Eq("Connection", componentToCompare))
175+
.SetCacheable(true)
176+
.UniqueResult<CachedPerson>();
177+
178+
Assert.That(cached.Name, Is.EqualTo("CachedNull"));
179+
Assert.That(cached.Connection.PortName, Is.Null);
180+
181+
using (var dbCommand = session.Connection.CreateCommand())
182+
{
183+
dbCommand.CommandText = "DELETE FROM cachedpeople";
184+
tx.Enlist(dbCommand);
185+
dbCommand.ExecuteNonQuery();
186+
}
187+
188+
tx.Commit();
189+
}
190+
191+
componentToCompare.PortName = "port";
192+
using (ISession session = OpenSession())
193+
using (ITransaction tx = session.BeginTransaction())
194+
{
195+
//Cache should not return cached entity, because it no longer matches criteria
196+
var cachedPeople = session.CreateCriteria<CachedPerson>()
197+
.Add(Restrictions.Eq("Connection", componentToCompare))
198+
.SetCacheable(true)
199+
.List<CachedPerson>();
200+
201+
Assert.That(cachedPeople, Is.Empty);
202+
203+
tx.Commit();
204+
}
205+
}
206+
207+
[Test]
208+
public void CachedQueryMissesWithDifferentNullComponent()
209+
{
210+
var componentToCompare = new Connection
211+
{
212+
ConnectionType = "http",
213+
Address = "test.com",
214+
PortName = "port"
215+
};
216+
217+
using (ISession session = OpenSession())
218+
using (ITransaction tx = session.BeginTransaction())
219+
{
220+
var cached = session.CreateCriteria<CachedPerson>()
221+
.Add(Restrictions.Eq("Connection", componentToCompare))
222+
.SetCacheable(true)
223+
.UniqueResult<CachedPerson>();
224+
225+
Assert.That(cached.Name, Is.EqualTo("CachedNotNull"));
226+
Assert.That(cached.Connection.PortName, Is.Not.Null);
227+
228+
using (var dbCommand = session.Connection.CreateCommand())
229+
{
230+
dbCommand.CommandText = "DELETE FROM cachedpeople";
231+
tx.Enlist(dbCommand);
232+
dbCommand.ExecuteNonQuery();
233+
}
234+
235+
tx.Commit();
236+
}
237+
238+
componentToCompare.PortName = null;
239+
using (ISession session = OpenSession())
240+
using (ITransaction tx = session.BeginTransaction())
241+
{
242+
//Cache should not return cached entity, because it no longer matches criteria
243+
var cachedPeople = session.CreateCriteria<CachedPerson>()
244+
.Add(Restrictions.Eq("Connection", componentToCompare))
245+
.SetCacheable(true)
246+
.List<CachedPerson>();
247+
248+
Assert.That(cachedPeople, Is.Empty);
249+
250+
tx.Commit();
251+
}
252+
}
253+
254+
[Test]
255+
public void CachedQueryAgainstComponentWithANullPropertyUsingCriteria()
256+
{
257+
var componentToCompare = new Connection
258+
{
259+
ConnectionType = "http",
260+
Address = "test.com",
261+
PortName = null
262+
};
263+
264+
using (ISession session = OpenSession())
265+
using (ITransaction tx = session.BeginTransaction())
266+
{
267+
var cached = session.CreateCriteria<CachedPerson>()
268+
.Add(Restrictions.Eq("Connection", componentToCompare))
269+
.SetCacheable(true)
270+
.UniqueResult<CachedPerson>();
271+
272+
Assert.That(cached.Name, Is.EqualTo("CachedNull"));
273+
Assert.That(cached.Connection.PortName, Is.Null);
274+
275+
using (var dbCommand = session.Connection.CreateCommand())
276+
{
277+
dbCommand.CommandText = "DELETE FROM cachedpeople";
278+
tx.Enlist(dbCommand);
279+
dbCommand.ExecuteNonQuery();
280+
}
281+
282+
tx.Commit();
283+
}
284+
285+
using (ISession session = OpenSession())
286+
using (ITransaction tx = session.BeginTransaction())
287+
{
288+
//Should retreive from cache since we deleted directly from database.
289+
var cached = session.CreateCriteria<CachedPerson>()
290+
.Add(Restrictions.Eq("Connection", componentToCompare))
291+
.SetCacheable(true)
292+
.UniqueResult<CachedPerson>();
293+
294+
Assert.That(cached.Name, Is.EqualTo("CachedNull"));
295+
Assert.That(cached.Connection.PortName, Is.Null);
296+
297+
tx.Commit();
298+
}
299+
}
300+
301+
[Test]
302+
public void QueryOverANullComponentProperty()
303+
{
304+
// Works at the time NH3634 was reported
305+
// Generates the following SqlBatch:
306+
// SELECT this_.Id as Id0_0_,
307+
// this_.Name as Name0_0_,
308+
// this_.ConnectionType as Connecti3_0_0_,
309+
// this_.Address as Address0_0_,
310+
// this_.PortName as PortName0_0_
311+
// FROM people this_
312+
// WHERE this_.PortName is null
313+
314+
using (ISession session = OpenSession())
315+
using (session.BeginTransaction())
316+
{
317+
var sally = session.QueryOver<Person>()
318+
.Where(p => p.Connection.PortName == null)
319+
.And(p => p.Connection.Address == "test.com")
320+
.And(p => p.Connection.ConnectionType == "http")
321+
.SingleOrDefault<Person>();
322+
323+
Assert.That(sally.Name, Is.EqualTo("Sally"));
324+
Assert.That(sally.Connection.PortName, Is.Null);
325+
}
326+
}
327+
328+
[Test]
329+
public void QueryAgainstANullComponentPropertyUsingCriteriaApi()
330+
{
331+
using (ISession session = OpenSession())
332+
using (session.BeginTransaction())
333+
{
334+
var sally = session.CreateCriteria<Person>()
335+
.Add(Restrictions.Eq("Connection.PortName", null))
336+
.Add(Restrictions.Eq("Connection.Address", "test.com"))
337+
.Add(Restrictions.Eq("Connection.ConnectionType", "http"))
338+
.UniqueResult<Person>();
339+
340+
Assert.That(sally.Name, Is.EqualTo("Sally"));
341+
Assert.That(sally.Connection.PortName, Is.Null);
342+
}
343+
}
344+
}
345+
}

0 commit comments

Comments
 (0)