Skip to content

Commit a7deeb0

Browse files
authored
Get rid of select queries for each ManyToMany not found ignored element in hql (#3394)
1 parent 8f05f67 commit a7deeb0

File tree

4 files changed

+124
-73
lines changed

4 files changed

+124
-73
lines changed

src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs

+53-32
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,28 @@
99

1010

1111
using System;
12-
using NHibernate.Cfg;
1312
using NHibernate.Criterion;
1413
using NHibernate.Transform;
1514
using NUnit.Framework;
1615

1716
namespace NHibernate.Test.NHSpecificTest.NH750
1817
{
1918
using System.Threading.Tasks;
20-
[TestFixture]
19+
[TestFixture(0)]
20+
[TestFixture(1)]
21+
[TestFixture(2)]
2122
public class ManyToManyNotFoundIgnoreFixtureAsync : BugTestCase
2223
{
2324
private int id1;
2425
private int id2;
26+
private int _drive2Id;
27+
private readonly int _drivesCount;
28+
private int DrivesCountWithOneIgnored => _drivesCount == 0? 0 : _drivesCount - 1;
29+
30+
public ManyToManyNotFoundIgnoreFixtureAsync(int drivesCount)
31+
{
32+
_drivesCount = drivesCount;
33+
}
2534

2635
protected override void OnSetUp()
2736
{
@@ -34,12 +43,12 @@ protected override void OnSetUp()
3443
using (var t = s.BeginTransaction())
3544
{
3645
s.Save(dr1);
37-
s.Save(dr2);
46+
_drive2Id = (int)s.Save(dr2);
3847
s.Save(dr3);
39-
dv1.Drives.Add(dr1);
40-
dv1.Drives.Add(dr2);
41-
dv2.Drives.Add(dr1);
42-
dv2.Drives.Add(dr3);
48+
AddDrive(dv1, dr2);
49+
AddDrive(dv1, dr1);
50+
AddDrive(dv2, dr3);
51+
AddDrive(dv2, dr1);
4352

4453
id1 = (int) s.Save(dv1);
4554
id2 = (int) s.Save(dv2);
@@ -51,15 +60,22 @@ protected override void OnSetUp()
5160
}
5261
}
5362

63+
private void AddDrive(Device dv, Drive drive)
64+
{
65+
if(dv.Drives.Count >= _drivesCount)
66+
return;
67+
dv.Drives.Add(drive);
68+
}
69+
5470
protected override void OnTearDown()
5571
{
56-
using (ISession s = Sfi.OpenSession())
57-
using (var t = s.BeginTransaction())
58-
{
59-
s.Delete("from Device");
60-
s.Delete("from Drive");
61-
t.Commit();
62-
}
72+
using var s = Sfi.OpenSession();
73+
using var t = s.BeginTransaction();
74+
75+
s.CreateSQLQuery("delete from DriveOfDevice").ExecuteUpdate();
76+
s.Delete("from Device");
77+
s.Delete("from Drive");
78+
t.Commit();
6379
}
6480

6581
[Test]
@@ -73,9 +89,9 @@ public async Task DeviceOfDriveAsync()
7389
dv2 = (Device) await (s.LoadAsync(typeof(Device), id2));
7490
}
7591

76-
Assert.That(dv1.Drives, Has.Count.EqualTo(2).And.None.Null);
92+
Assert.That(dv1.Drives, Has.Count.EqualTo(_drivesCount).And.None.Null);
7793
// Verify one is missing
78-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
94+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
7995

8096
//Make sure that flush didn't touch not-found="ignore" records for not modified collection
8197
using (var s = Sfi.OpenSession())
@@ -86,18 +102,23 @@ public async Task DeviceOfDriveAsync()
86102
await (t.CommitAsync());
87103
}
88104

89-
await (VerifyResultAsync(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection"));
105+
await (VerifyResultAsync(expectedInCollection: DrivesCountWithOneIgnored, expectedInDb: _drivesCount, msg: "not modified collection"));
106+
107+
// Many-to-many clears collection and recreates it so not-found ignore records are lost
108+
// Note: It's not the case when no valid records are present, so loaded Drives collection is empty
109+
// Just skip this check in this case:
110+
if (_drivesCount < 2)
111+
return;
90112

91-
//Many-to-many clears collection and recreates it so not-found ignore records are lost
92113
using (var s = Sfi.OpenSession())
93114
using (var t = s.BeginTransaction())
94115
{
95116
dv2 = await (s.GetAsync<Device>(dv2.Id));
96-
dv2.Drives.Add(dv1.Drives[1]);
117+
dv2.Drives.Add(await (s.LoadAsync<Drive>(_drive2Id)));
97118
await (t.CommitAsync());
98119
}
99120

100-
await (VerifyResultAsync(2, 2, msg: "modified collection"));
121+
await (VerifyResultAsync(_drivesCount, _drivesCount, msg: "modified collection"));
101122

102123
async Task VerifyResultAsync(int expectedInCollection, int expectedInDb, string msg)
103124
{
@@ -127,23 +148,23 @@ public async Task QueryOverFetchAsync()
127148
.SingleOrDefaultAsync());
128149

129150
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
130-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
151+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
131152
}
132153
}
133154

134155
[Test]
135156
public async Task HqlFetchAsync()
136157
{
137-
using (var s = OpenSession())
138-
{
139-
var dv2 = await (s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id")
140-
.SetResultTransformer(Transformers.DistinctRootEntity)
141-
.SetParameter("id", id2)
142-
.UniqueResultAsync<Device>());
143-
144-
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
145-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
146-
}
158+
using var log = new SqlLogSpy();
159+
using var s = OpenSession();
160+
var dv2 = await (s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id")
161+
.SetResultTransformer(Transformers.DistinctRootEntity)
162+
.SetParameter("id", id2)
163+
.UniqueResultAsync<Device>());
164+
165+
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
166+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
167+
Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1));
147168
}
148169

149170
[Test]
@@ -155,7 +176,7 @@ public async Task LazyLoadAsync()
155176
await (NHibernateUtil.InitializeAsync(dv2.Drives));
156177

157178
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
158-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
179+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
159180
}
160181
}
161182
}

src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs

+53-32
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
using System;
2-
using NHibernate.Cfg;
32
using NHibernate.Criterion;
43
using NHibernate.Transform;
54
using NUnit.Framework;
65

76
namespace NHibernate.Test.NHSpecificTest.NH750
87
{
9-
[TestFixture]
8+
[TestFixture(0)]
9+
[TestFixture(1)]
10+
[TestFixture(2)]
1011
public class ManyToManyNotFoundIgnoreFixture : BugTestCase
1112
{
1213
private int id1;
1314
private int id2;
15+
private int _drive2Id;
16+
private readonly int _drivesCount;
17+
private int DrivesCountWithOneIgnored => _drivesCount == 0? 0 : _drivesCount - 1;
18+
19+
public ManyToManyNotFoundIgnoreFixture(int drivesCount)
20+
{
21+
_drivesCount = drivesCount;
22+
}
1423

1524
protected override void OnSetUp()
1625
{
@@ -23,12 +32,12 @@ protected override void OnSetUp()
2332
using (var t = s.BeginTransaction())
2433
{
2534
s.Save(dr1);
26-
s.Save(dr2);
35+
_drive2Id = (int)s.Save(dr2);
2736
s.Save(dr3);
28-
dv1.Drives.Add(dr1);
29-
dv1.Drives.Add(dr2);
30-
dv2.Drives.Add(dr1);
31-
dv2.Drives.Add(dr3);
37+
AddDrive(dv1, dr2);
38+
AddDrive(dv1, dr1);
39+
AddDrive(dv2, dr3);
40+
AddDrive(dv2, dr1);
3241

3342
id1 = (int) s.Save(dv1);
3443
id2 = (int) s.Save(dv2);
@@ -40,15 +49,22 @@ protected override void OnSetUp()
4049
}
4150
}
4251

52+
private void AddDrive(Device dv, Drive drive)
53+
{
54+
if(dv.Drives.Count >= _drivesCount)
55+
return;
56+
dv.Drives.Add(drive);
57+
}
58+
4359
protected override void OnTearDown()
4460
{
45-
using (ISession s = Sfi.OpenSession())
46-
using (var t = s.BeginTransaction())
47-
{
48-
s.Delete("from Device");
49-
s.Delete("from Drive");
50-
t.Commit();
51-
}
61+
using var s = Sfi.OpenSession();
62+
using var t = s.BeginTransaction();
63+
64+
s.CreateSQLQuery("delete from DriveOfDevice").ExecuteUpdate();
65+
s.Delete("from Device");
66+
s.Delete("from Drive");
67+
t.Commit();
5268
}
5369

5470
[Test]
@@ -62,9 +78,9 @@ public void DeviceOfDrive()
6278
dv2 = (Device) s.Load(typeof(Device), id2);
6379
}
6480

65-
Assert.That(dv1.Drives, Has.Count.EqualTo(2).And.None.Null);
81+
Assert.That(dv1.Drives, Has.Count.EqualTo(_drivesCount).And.None.Null);
6682
// Verify one is missing
67-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
83+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
6884

6985
//Make sure that flush didn't touch not-found="ignore" records for not modified collection
7086
using (var s = Sfi.OpenSession())
@@ -75,18 +91,23 @@ public void DeviceOfDrive()
7591
t.Commit();
7692
}
7793

78-
VerifyResult(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection");
94+
VerifyResult(expectedInCollection: DrivesCountWithOneIgnored, expectedInDb: _drivesCount, msg: "not modified collection");
95+
96+
// Many-to-many clears collection and recreates it so not-found ignore records are lost
97+
// Note: It's not the case when no valid records are present, so loaded Drives collection is empty
98+
// Just skip this check in this case:
99+
if (_drivesCount < 2)
100+
return;
79101

80-
//Many-to-many clears collection and recreates it so not-found ignore records are lost
81102
using (var s = Sfi.OpenSession())
82103
using (var t = s.BeginTransaction())
83104
{
84105
dv2 = s.Get<Device>(dv2.Id);
85-
dv2.Drives.Add(dv1.Drives[1]);
106+
dv2.Drives.Add(s.Load<Drive>(_drive2Id));
86107
t.Commit();
87108
}
88109

89-
VerifyResult(2, 2, msg: "modified collection");
110+
VerifyResult(_drivesCount, _drivesCount, msg: "modified collection");
90111

91112
void VerifyResult(int expectedInCollection, int expectedInDb, string msg)
92113
{
@@ -116,23 +137,23 @@ public void QueryOverFetch()
116137
.SingleOrDefault();
117138

118139
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
119-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
140+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
120141
}
121142
}
122143

123144
[Test]
124145
public void HqlFetch()
125146
{
126-
using (var s = OpenSession())
127-
{
128-
var dv2 = s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id")
129-
.SetResultTransformer(Transformers.DistinctRootEntity)
130-
.SetParameter("id", id2)
131-
.UniqueResult<Device>();
132-
133-
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
134-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
135-
}
147+
using var log = new SqlLogSpy();
148+
using var s = OpenSession();
149+
var dv2 = s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id")
150+
.SetResultTransformer(Transformers.DistinctRootEntity)
151+
.SetParameter("id", id2)
152+
.UniqueResult<Device>();
153+
154+
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
155+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
156+
Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1));
136157
}
137158

138159
[Test]
@@ -144,7 +165,7 @@ public void LazyLoad()
144165
NHibernateUtil.Initialize(dv2.Drives);
145166

146167
Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True);
147-
Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null);
168+
Assert.That(dv2.Drives, Has.Count.EqualTo(DrivesCountWithOneIgnored).And.None.Null);
148169
}
149170
}
150171
}

src/NHibernate/Engine/TableGroupJoinHelper.cs

+15-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using NHibernate.Persister.Collection;
55
using NHibernate.Persister.Entity;
66
using NHibernate.SqlCommand;
7+
using NHibernate.Type;
78

89
namespace NHibernate.Engine
910
{
@@ -68,14 +69,17 @@ private static bool NeedsTableGroupJoin(IReadOnlyList<IJoin> joins, SqlString[]
6869

6970
foreach (var join in joins)
7071
{
71-
var entityPersister = GetEntityPersister(join.Joinable, out var isManyToMany);
72+
var entityPersister = GetEntityPersister(join.Joinable, out var manyToManyType);
73+
if (manyToManyType?.IsNullable == true)
74+
return true;
75+
7276
if (entityPersister?.HasSubclassJoins(includeSubclasses && isSubclassIncluded(join.Alias)) != true)
7377
continue;
7478

7579
if (hasWithClause)
7680
return true;
7781

78-
if (!isManyToMany // many-to-many keys are stored in separate table
82+
if (manyToManyType == null // many-to-many keys are stored in separate table
7983
&& entityPersister.ColumnsDependOnSubclassJoins(join.RHSColumns))
8084
{
8185
return true;
@@ -94,14 +98,14 @@ private static SqlString GetTableGroupJoinWithClause(SqlString[] withClauseFragm
9498
var isAssociationJoin = lhsColumns.Length > 0;
9599
if (isAssociationJoin)
96100
{
97-
var entityPersister = GetEntityPersister(first.Joinable, out var isManyToMany);
101+
var entityPersister = GetEntityPersister(first.Joinable, out var manyToManyType);
98102
string rhsAlias = first.Alias;
99103
string[] rhsColumns = first.RHSColumns;
100104
for (int j = 0; j < lhsColumns.Length; j++)
101105
{
102106
fromFragment.Add(lhsColumns[j])
103107
.Add("=")
104-
.Add((entityPersister == null || isManyToMany) // many-to-many keys are stored in separate table
108+
.Add((entityPersister == null || manyToManyType != null) // many-to-many keys are stored in separate table
105109
? rhsAlias
106110
: entityPersister.GenerateTableAliasForColumn(rhsAlias, rhsColumns[j]))
107111
.Add(".")
@@ -116,15 +120,18 @@ private static SqlString GetTableGroupJoinWithClause(SqlString[] withClauseFragm
116120
return fromFragment.ToSqlString();
117121
}
118122

119-
private static AbstractEntityPersister GetEntityPersister(IJoinable joinable, out bool isManyToMany)
123+
private static AbstractEntityPersister GetEntityPersister(IJoinable joinable, out EntityType manyToManyType)
120124
{
121-
isManyToMany = false;
125+
manyToManyType = null;
122126
if (!joinable.IsCollection)
123127
return joinable as AbstractEntityPersister;
124128

125129
var collection = (IQueryableCollection) joinable;
126-
isManyToMany = collection.IsManyToMany;
127-
return collection.ElementType.IsEntityType ? collection.ElementPersister as AbstractEntityPersister : null;
130+
if (!collection.ElementType.IsEntityType)
131+
return null;
132+
if (collection.IsManyToMany)
133+
manyToManyType = (EntityType) collection.ElementType;
134+
return collection.ElementPersister as AbstractEntityPersister;
128135
}
129136

130137
private static void AppendWithClause(SqlStringBuilder fromFragment, bool hasConditions, SqlString[] withClauseFragments)

0 commit comments

Comments
 (0)