Skip to content

Commit 3a5f949

Browse files
authored
Fix wrong inner join fetch on not lazy component key-many-to-one property in Criteria (#2513)
Fixes #766
1 parent 199a1aa commit 3a5f949

File tree

4 files changed

+405
-3
lines changed

4 files changed

+405
-3
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Collections.Generic;
12+
using System.Linq;
13+
using NHibernate.Mapping.ByCode;
14+
using NUnit.Framework;
15+
16+
namespace NHibernate.Test.NHSpecificTest.NH3813
17+
{
18+
using System.Threading.Tasks;
19+
[TestFixture]
20+
public class KeyManyToOneInnerJoinFetchFixtureAsync : TestCaseMappingByCode
21+
{
22+
protected override Cfg.MappingSchema.HbmMapping GetMappings()
23+
{
24+
var mapper = new ModelMapper();
25+
26+
mapper.Class<FirstTable>(
27+
m =>
28+
{
29+
m.Lazy(false);
30+
31+
m.Id(
32+
i => i.Id,
33+
id =>
34+
{
35+
id.Column("ID");
36+
id.Generator(Generators.Identity);
37+
});
38+
m.Property(x => x.Name);
39+
40+
m.Bag(
41+
b => b.AssociationTableCollection,
42+
bm =>
43+
{
44+
bm.Inverse(true);
45+
bm.Key(k => k.Column("FirstTableID"));
46+
},
47+
mp => mp.OneToMany());
48+
});
49+
50+
mapper.Class<OtherTable>(
51+
m =>
52+
{
53+
m.Lazy(false);
54+
55+
m.Id(
56+
i => i.Id,
57+
id =>
58+
{
59+
id.Column("ID");
60+
id.Generator(Generators.Identity);
61+
});
62+
m.Property(x => x.Name);
63+
});
64+
65+
mapper.Class<AssociationTable>(
66+
m =>
67+
{
68+
m.ComposedId(
69+
i =>
70+
{
71+
i.ManyToOne(c => c.FirstTable, p => { p.Column("FirstTableID"); });
72+
i.ManyToOne(c => c.OtherTable, p => { p.Column("OtherTableID"); });
73+
});
74+
m.Property(x => x.Name);
75+
});
76+
77+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
78+
}
79+
80+
[Test]
81+
public async Task FetchQueryDoesNotContainInnerJoinAsync()
82+
{
83+
using (var logSpy = new SqlLogSpy())
84+
using (var session = OpenSession())
85+
{
86+
var q = await (session.QueryOver<FirstTable>()
87+
.Fetch(SelectMode.Fetch, f => f.AssociationTableCollection)
88+
.Left.JoinQueryOver(f => f.AssociationTableCollection).ListAsync());
89+
90+
var sql = logSpy.GetWholeLog();
91+
Assert.That(sql, Is.Not.Contains("inner join"));
92+
}
93+
}
94+
95+
[Test]
96+
public async Task FetchQueryDoesNotContainInnerJoinMultiAsync()
97+
{
98+
using (var logSpy = new SqlLogSpy())
99+
using (var session = OpenSession())
100+
{
101+
var q = await (session.QueryOver<FirstTable>()
102+
.Fetch(SelectMode.Fetch, f => f.AssociationTableCollection)
103+
.Left.JoinQueryOver<AssociationTable>(f => f.AssociationTableCollection)
104+
.Left.JoinQueryOver(a => a.OtherTable).ListAsync());
105+
106+
var sql = logSpy.GetWholeLog();
107+
108+
Assert.That(sql, Does.Not.Contain("inner join"));
109+
Assert.That(sql, Does.Match(@"join\s*AssociationTable").IgnoreCase);
110+
Assert.That(sql, Does.Match(@"join\s*OtherTable"));
111+
}
112+
}
113+
114+
[Test]
115+
public async Task FetchLoadsAllRecordsAsync()
116+
{
117+
IList<FirstTable> result = null;
118+
119+
using (var session = OpenSession())
120+
{
121+
// the query should return all records from the table with their collections fetched
122+
result = await (session.QueryOver<FirstTable>()
123+
.Fetch(SelectMode.Fetch, f => f.AssociationTableCollection)
124+
.Left.JoinQueryOver(f => f.AssociationTableCollection)
125+
.ListAsync());
126+
}
127+
128+
Assert.AreEqual(2, result.Count, "Query returned wrong number of records.");
129+
Assert.IsTrue(result.All(x => NHibernateUtil.IsInitialized(x.AssociationTableCollection)), "Not all collections have been initialized");
130+
}
131+
132+
[Test]
133+
public async Task FetchInitializesAllCollectionsAsync()
134+
{
135+
IList<FirstTable> result = null;
136+
137+
using (var session = OpenSession())
138+
{
139+
// load all records
140+
result = await (session.QueryOver<FirstTable>()
141+
.ListAsync());
142+
143+
// lazy-load the association collection
144+
await (session.QueryOver<FirstTable>()
145+
.Fetch(SelectMode.Fetch, f => f.AssociationTableCollection)
146+
.Left.JoinQueryOver(f => f.AssociationTableCollection)
147+
.ListAsync());
148+
}
149+
150+
Assert.IsTrue(result.All(x => NHibernateUtil.IsInitialized(x.AssociationTableCollection)), "Not all collections have been initialized");
151+
}
152+
153+
protected override void OnSetUp()
154+
{
155+
using (var s = OpenSession())
156+
using (var t = s.BeginTransaction())
157+
{
158+
// a record that has association records will be loaded regularly
159+
var withAssociations = new FirstTable();
160+
161+
var other1 = new OtherTable();
162+
var other2 = new OtherTable();
163+
164+
var assoc1 = new AssociationTable() {OtherTable = other1, FirstTable = withAssociations};
165+
var assoc2 = new AssociationTable() {OtherTable = other2, FirstTable = withAssociations};
166+
167+
withAssociations.AssociationTableCollection.Add(assoc1);
168+
withAssociations.AssociationTableCollection.Add(assoc2);
169+
s.Save(withAssociations);
170+
171+
// a record with no associations will have problems if inner joined to association table
172+
var withoutAssociations = new FirstTable();
173+
s.Save(withoutAssociations);
174+
175+
t.Commit();
176+
}
177+
}
178+
179+
protected override void OnTearDown()
180+
{
181+
using (var s = OpenSession())
182+
using (var t = s.BeginTransaction())
183+
{
184+
s.CreateQuery("delete from System.Object").ExecuteUpdate();
185+
t.Commit();
186+
}
187+
}
188+
}
189+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.NHSpecificTest.NH3813
4+
{
5+
public class AssociationTable
6+
{
7+
public virtual FirstTable FirstTable { get; set; }
8+
public virtual OtherTable OtherTable { get; set; }
9+
public virtual string Name { get; set; }
10+
11+
public override bool Equals(object obj)
12+
{
13+
return base.Equals(obj);
14+
}
15+
16+
public override int GetHashCode()
17+
{
18+
return base.GetHashCode();
19+
}
20+
}
21+
22+
public class FirstTable
23+
{
24+
public virtual int Id { get; set; }
25+
public virtual string Name { get; set; }
26+
27+
public virtual IList<AssociationTable> AssociationTableCollection { get; set; } = new List<AssociationTable>();
28+
}
29+
30+
public class OtherTable
31+
{
32+
public virtual int Id { get; set; }
33+
public virtual string Name { get; set; }
34+
}
35+
}

0 commit comments

Comments
 (0)