Skip to content

Commit a06e6db

Browse files
Add a test case for nhibernate#3306
1 parent c49a2a9 commit a06e6db

File tree

2 files changed

+269
-0
lines changed

2 files changed

+269
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH3306
4+
{
5+
// Entities copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted.
6+
7+
public class UEntity
8+
{
9+
public virtual UKey Id { get; set; }
10+
11+
public virtual ISet<LEntity> LEntities { get; protected set; } = new HashSet<LEntity>();
12+
13+
public override string ToString() => $"[{nameof(UEntity)}#{Id}]";
14+
}
15+
16+
/// <summary>
17+
/// Represents the ID of a <see cref="UEntity"/>. This is a reference to a <see cref="PEntity"/> and an ID which
18+
/// is unique only within a <see cref="PEntity"/>.
19+
/// </summary>
20+
public class UKey
21+
{
22+
public virtual PEntity PEntity { get; set; }
23+
24+
public virtual long Id { get; set; }
25+
26+
public override string ToString() => $"[{nameof(UKey)}#{nameof(PEntity)}={PEntity}, {nameof(Id)}={Id}]";
27+
28+
public override bool Equals(object obj)
29+
{
30+
if (obj is null) return false;
31+
if (PEntity is null) return false;
32+
if (!(obj is UKey uKey)) return false;
33+
if (uKey.PEntity is null) return false;
34+
35+
return uKey.Id == Id && uKey.PEntity.Id == PEntity.Id;
36+
}
37+
38+
public override int GetHashCode() => Id.GetHashCode() ^ (PEntity?.Id).GetValueOrDefault().GetHashCode();
39+
}
40+
41+
public class PEntity
42+
{
43+
public virtual long Id { get; set; }
44+
45+
public virtual string PStatus { get; set; }
46+
47+
public override string ToString() => $"[{nameof(PEntity)}#{Id}]";
48+
}
49+
50+
public class DEntity
51+
{
52+
public virtual long Id { get; set; }
53+
54+
public virtual long LinkedEntityId { get; set; }
55+
56+
public virtual string LinkedEntityType { get; set; }
57+
58+
public virtual TEntity LinkedTEntity { get; protected set; }
59+
}
60+
61+
public class LEntity
62+
{
63+
public virtual long Id { get; set; }
64+
65+
public virtual string TStatus { get; set; }
66+
67+
public virtual TEntity TEntity { get; set; }
68+
69+
public virtual UEntity UEntity { get; set; }
70+
}
71+
72+
public class TEntity
73+
{
74+
public virtual long Id { get; set; }
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Mapping.ByCode;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.NHSpecificTest.GH3306
9+
{
10+
/// <summary>
11+
/// Fixture using 'by code' mappings
12+
/// </summary>
13+
/// <remarks>
14+
/// This fixture is identical to <see cref="Fixture" /> except the <see cref="Entity" /> mapping is performed
15+
/// by code in the GetMappings method, and does not require the <c>Mappings.hbm.xml</c> file. Use this approach
16+
/// if you prefer.
17+
/// </remarks>
18+
[TestFixture]
19+
public class ByCodeFixture : TestCaseMappingByCode
20+
{
21+
protected override HbmMapping GetMappings()
22+
{
23+
// Mapping copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted.
24+
var mapper = new ModelMapper();
25+
mapper.Class<UEntity>(rc =>
26+
{
27+
rc.ComponentAsId(x => x.Id, m =>
28+
{
29+
m.ManyToOne(x => x.PEntity, p => p.Column("p_id"));
30+
m.Property(x => x.Id, p => p.Column("id"));
31+
});
32+
33+
rc.Set(x => x.LEntities,
34+
m =>
35+
{
36+
m.Inverse(true);
37+
m.Key(k =>
38+
{
39+
k.Columns(c => c.Name("p_id"), c => c.Name("u_id"));
40+
k.NotNullable(true);
41+
});
42+
},
43+
a => a.OneToMany());
44+
});
45+
46+
mapper.Class<PEntity>(rc =>
47+
{
48+
rc.Id(x => x.Id, p => p.Column("id"));
49+
rc.Property(x => x.PStatus, p => p.Column("p_status"));
50+
});
51+
52+
mapper.Class<DEntity>(rc =>
53+
{
54+
rc.Id(x => x.Id, p => p.Column("id"));
55+
rc.Property(x => x.LinkedEntityId, p => p.Column("linked_entity_id"));
56+
rc.Property(x => x.LinkedEntityType, p => p.Column("linked_entity_type"));
57+
58+
rc.ManyToOne(x => x.LinkedTEntity,
59+
m =>
60+
{
61+
m.Cascade(Mapping.ByCode.Cascade.None);
62+
m.Update(false);
63+
m.Insert(false);
64+
m.NotFound(NotFoundMode.Ignore);
65+
m.NotNullable(false);
66+
m.Fetch(FetchKind.Join);
67+
m.Formula("CASE WHEN linked_entity_type = 'T' THEN linked_entity_id ELSE NULL END");
68+
});
69+
});
70+
71+
mapper.Class<LEntity>(rc =>
72+
{
73+
rc.Id(x => x.Id, p => p.Column("id"));
74+
rc.Property(x => x.TStatus, p => p.Column("t_status"));
75+
rc.ManyToOne(x => x.TEntity, m => m.Column("t_id"));
76+
rc.ManyToOne(x => x.UEntity, m => m.Columns(c => c.Name("p_id"), c => c.Name("u_id")));
77+
});
78+
79+
mapper.Class<TEntity>(rc =>
80+
{
81+
rc.Id(x => x.Id, p => p.Column("id"));
82+
});
83+
84+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
85+
}
86+
87+
protected override void OnSetUp()
88+
{
89+
using (var session = OpenSession())
90+
using (var transaction = session.BeginTransaction())
91+
{
92+
/*INSERT INTO t_entity(id) VALUES(1), (2), (3);
93+
INSERT INTO p_entity(id, p_status) VALUES(1, 'Good PStatus'), (2, 'Bad PStatus');
94+
INSERT INTO u_entity(p_id, id) VALUES(1, 1), (1, 2), (1, 3), (2, 1);
95+
INSERT INTO l_entity(id, t_status, t_id, p_id, u_id) VALUES
96+
(1, 'Good TStatus', 1, 1, 1),
97+
(2, 'Bad TStatus', 1, 3, 2),
98+
(3, 'Good TStatus', 1, 2, 2),
99+
(4, 'Good TStatus', 2, 2, 3);
100+
INSERT INTO d_entity(id, linked_entity_id, linked_entity_type) VALUES
101+
(1, 1, 'T'),
102+
(2, 2, 'T'),
103+
(3, 3, 'T'),
104+
(4, 0, 'Z');*/
105+
// Adapted from the SQL provided by https://github.com/craigfowler/NHibernateInvalidSql.
106+
var t1 = new TEntity { Id = 1 };
107+
var t2 = new TEntity { Id = 2 };
108+
var t3 = new TEntity { Id = 3 };
109+
session.Save(t1);
110+
session.Save(t2);
111+
session.Save(t3);
112+
113+
var goodP = new PEntity { Id = 1, PStatus = "Good PStatus" };
114+
var badP = new PEntity { Id = 2, PStatus = "Bad PStatus" };
115+
session.Save(goodP);
116+
session.Save(badP);
117+
118+
var u1 = new UEntity { Id = new UKey { PEntity = goodP, Id = 1 } };
119+
var u2 = new UEntity { Id = new UKey { PEntity = goodP, Id = 2 } };
120+
var u3 = new UEntity { Id = new UKey { PEntity = goodP, Id = 3 } };
121+
var u4 = new UEntity { Id = new UKey { PEntity = badP, Id = 1 } };
122+
session.Save(u1);
123+
session.Save(u2);
124+
session.Save(u3);
125+
session.Save(u4);
126+
127+
session.Save(new LEntity { Id = 1, TStatus = "Good TStatus", TEntity = t1, UEntity = u1 });
128+
session.Save(new LEntity { Id = 2, TStatus = "Bad TStatus", TEntity = t1, UEntity = u3 });
129+
session.Save(new LEntity { Id = 3, TStatus = "Good TStatus", TEntity = t1, UEntity = u4 });
130+
session.Save(new LEntity { Id = 4, TStatus = "Good TStatus", TEntity = t1, UEntity = u3 });
131+
132+
session.Save(new DEntity { Id = 1, LinkedEntityType = "T", LinkedEntityId = 1 });
133+
session.Save(new DEntity { Id = 2, LinkedEntityType = "T", LinkedEntityId = 2 });
134+
session.Save(new DEntity { Id = 3, LinkedEntityType = "T", LinkedEntityId = 3 });
135+
session.Save(new DEntity { Id = 4, LinkedEntityType = "Z", LinkedEntityId = 0 });
136+
137+
transaction.Commit();
138+
}
139+
}
140+
141+
protected override void OnTearDown()
142+
{
143+
using (var session = OpenSession())
144+
using (var transaction = session.BeginTransaction())
145+
{
146+
// The HQL delete does all the job inside the database without loading the entities, but it does
147+
// not handle delete order for avoiding violating constraints if any. Use
148+
// session.Delete("from System.Object");
149+
// instead if in need of having NHbernate ordering the deletes, but this will cause
150+
// loading the entities in the session.
151+
session.CreateQuery("delete from DEntity").ExecuteUpdate();
152+
session.CreateQuery("delete from LEntity").ExecuteUpdate();
153+
session.CreateQuery("delete from UEntity").ExecuteUpdate();
154+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
155+
156+
transaction.Commit();
157+
}
158+
}
159+
160+
[Test]
161+
public void InvalidSql()
162+
{
163+
using (var session = OpenSession())
164+
using (var transaction = session.BeginTransaction())
165+
{
166+
// Test copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted.
167+
var pEntities = session.Query<PEntity>().Where(p => p.PStatus == "Good PStatus");
168+
var dEntities = session.Query<DEntity>().Where(d => d.Id == 1);
169+
170+
// The reason for the three separate .Where clauses is that in our own impl, these three expressions come from three
171+
// separate & independent specification classes. Each represents an independent business rule.
172+
var query = session
173+
.Query<UEntity>()
174+
.Where(u => pEntities.Any(p => u.Id.PEntity == p))
175+
.Where(u => dEntities.Any(d => u.LEntities.Any(l => l.TEntity == d.LinkedTEntity)))
176+
// The part of this query which states !u.LEntities.Any() is not strictly necessary, but works
177+
// around some other suboptimal SQL in other scenarios.
178+
.Where(u => !u.LEntities.Any() || u.LEntities.All(l => l.TStatus != "Bad TStatus"));
179+
180+
List<UEntity> uEntities = null;
181+
Assert.That(
182+
() =>
183+
{
184+
uEntities = query.ToList();
185+
}, Throws.Nothing);
186+
Assert.That(uEntities, Has.Count.EqualTo(1));
187+
Assert.That(uEntities[0].Id, Has.Property(nameof(UKey.Id)).EqualTo(1).And.Property(nameof(UKey.PEntity)).Property(nameof(PEntity.Id)).EqualTo(1));
188+
189+
transaction.Commit();
190+
}
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)