Skip to content

Commit efdeac8

Browse files
committed
Added batching cache operations support for subclasses
1 parent fb25801 commit efdeac8

File tree

6 files changed

+309
-4
lines changed

6 files changed

+309
-4
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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;
12+
using System.Collections;
13+
using System.Collections.Generic;
14+
using System.Linq;
15+
using NHibernate.Cache;
16+
using NHibernate.Cfg;
17+
using NHibernate.DomainModel;
18+
using NHibernate.Test.CacheTest.Caches;
19+
using NUnit.Framework;
20+
21+
namespace NHibernate.Test.CacheTest
22+
{
23+
using System.Threading.Tasks;
24+
[TestFixture]
25+
public class BatchableCacheSubclassFixtureAsync : TestCase
26+
{
27+
protected override IList Mappings
28+
{
29+
get
30+
{
31+
return new string[]
32+
{
33+
"FooBar.hbm.xml",
34+
"Baz.hbm.xml",
35+
"Qux.hbm.xml",
36+
"Glarch.hbm.xml",
37+
"Fum.hbm.xml",
38+
"Fumm.hbm.xml",
39+
"Fo.hbm.xml",
40+
"One.hbm.xml",
41+
"Many.hbm.xml",
42+
"Immutable.hbm.xml",
43+
"Fee.hbm.xml",
44+
"Vetoer.hbm.xml",
45+
"Holder.hbm.xml",
46+
"Location.hbm.xml",
47+
"Stuff.hbm.xml",
48+
"Container.hbm.xml",
49+
"Simple.hbm.xml"
50+
};
51+
}
52+
}
53+
54+
protected override void Configure(Configuration configuration)
55+
{
56+
configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "true");
57+
configuration.SetProperty(Cfg.Environment.UseQueryCache, "true");
58+
configuration.SetProperty(Cfg.Environment.CacheProvider, typeof(BatchableCacheProvider).AssemblyQualifiedName);
59+
}
60+
61+
protected override void OnSetUp()
62+
{
63+
using (var s = Sfi.OpenSession())
64+
using (var tx = s.BeginTransaction())
65+
{
66+
FooProxy flast = new Bar();
67+
s.Save(flast);
68+
for (int i = 0; i < 5; i++)
69+
{
70+
FooProxy foo = new Bar();
71+
s.Save(foo);
72+
flast.TheFoo = foo;
73+
flast = flast.TheFoo;
74+
flast.String = "foo" + (i + 1);
75+
}
76+
tx.Commit();
77+
}
78+
}
79+
80+
protected override void OnTearDown()
81+
{
82+
using (var s = Sfi.OpenSession())
83+
using (var tx = s.BeginTransaction())
84+
{
85+
s.Delete("from NHibernate.DomainModel.Foo as foo");
86+
tx.Commit();
87+
}
88+
}
89+
90+
[Test]
91+
public async Task BatchableRootEntityTestAsync()
92+
{
93+
var persister = Sfi.GetEntityPersister(typeof(Foo).FullName);
94+
Assert.That(persister.Cache.Cache, Is.Not.Null);
95+
Assert.That(persister.Cache.Cache, Is.TypeOf<BatchableCache>());
96+
var fooCache = (BatchableCache) persister.Cache.Cache;
97+
98+
persister = Sfi.GetEntityPersister(typeof(Bar).FullName);
99+
Assert.That(persister.Cache.Cache, Is.Not.Null);
100+
Assert.That(persister.Cache.Cache, Is.TypeOf<BatchableCache>());
101+
var barCache = (BatchableCache) persister.Cache.Cache;
102+
103+
Assert.That(barCache, Is.EqualTo(fooCache));
104+
105+
// Add Bar to cache
106+
using (var s = Sfi.OpenSession())
107+
using (var tx = s.BeginTransaction())
108+
{
109+
var list = await (s.CreateQuery("from foo in class NHibernate.DomainModel.Foo").ListAsync());
110+
Assert.AreEqual(6, list.Count);
111+
await (tx.CommitAsync());
112+
}
113+
114+
Assert.That(fooCache.PutCalls, Has.Count.EqualTo(6)); // Bar is not batchable
115+
Assert.That(fooCache.PutMultipleCalls, Has.Count.EqualTo(0));
116+
117+
// Batch fetch by two from cache
118+
using (var s = Sfi.OpenSession())
119+
using (var tx = s.BeginTransaction())
120+
{
121+
var enumerator =
122+
(await (s.CreateQuery("from foo in class NHibernate.DomainModel.Foo order by foo.String").EnumerableAsync())).GetEnumerator();
123+
var i = 1;
124+
while (enumerator.MoveNext())
125+
{
126+
BarProxy bar = (BarProxy) enumerator.Current;
127+
if (i % 2 == 0)
128+
{
129+
string theString = bar.String; // Load the entity
130+
}
131+
i++;
132+
}
133+
await (tx.CommitAsync());
134+
}
135+
136+
Assert.That(fooCache.GetMultipleCalls, Has.Count.EqualTo(3));
137+
138+
// Check that each key was used only once when retriving objects from the cache
139+
var uniqueKeys = new HashSet<string>();
140+
foreach (var keys in fooCache.GetMultipleCalls)
141+
{
142+
Assert.That(keys, Has.Length.EqualTo(2));
143+
foreach (var key in keys.OfType<CacheKey>().Select(o => (string) o.Key))
144+
{
145+
Assert.That(uniqueKeys, Does.Not.Contains(key));
146+
uniqueKeys.Add(key);
147+
}
148+
}
149+
}
150+
}
151+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using NHibernate.Cache;
6+
using NHibernate.Cfg;
7+
using NHibernate.DomainModel;
8+
using NHibernate.Test.CacheTest.Caches;
9+
using NUnit.Framework;
10+
11+
namespace NHibernate.Test.CacheTest
12+
{
13+
[TestFixture]
14+
public class BatchableCacheSubclassFixture : TestCase
15+
{
16+
protected override IList Mappings
17+
{
18+
get
19+
{
20+
return new string[]
21+
{
22+
"FooBar.hbm.xml",
23+
"Baz.hbm.xml",
24+
"Qux.hbm.xml",
25+
"Glarch.hbm.xml",
26+
"Fum.hbm.xml",
27+
"Fumm.hbm.xml",
28+
"Fo.hbm.xml",
29+
"One.hbm.xml",
30+
"Many.hbm.xml",
31+
"Immutable.hbm.xml",
32+
"Fee.hbm.xml",
33+
"Vetoer.hbm.xml",
34+
"Holder.hbm.xml",
35+
"Location.hbm.xml",
36+
"Stuff.hbm.xml",
37+
"Container.hbm.xml",
38+
"Simple.hbm.xml"
39+
};
40+
}
41+
}
42+
43+
protected override void Configure(Configuration configuration)
44+
{
45+
configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "true");
46+
configuration.SetProperty(Cfg.Environment.UseQueryCache, "true");
47+
configuration.SetProperty(Cfg.Environment.CacheProvider, typeof(BatchableCacheProvider).AssemblyQualifiedName);
48+
}
49+
50+
protected override void OnSetUp()
51+
{
52+
using (var s = Sfi.OpenSession())
53+
using (var tx = s.BeginTransaction())
54+
{
55+
FooProxy flast = new Bar();
56+
s.Save(flast);
57+
for (int i = 0; i < 5; i++)
58+
{
59+
FooProxy foo = new Bar();
60+
s.Save(foo);
61+
flast.TheFoo = foo;
62+
flast = flast.TheFoo;
63+
flast.String = "foo" + (i + 1);
64+
}
65+
tx.Commit();
66+
}
67+
}
68+
69+
protected override void OnTearDown()
70+
{
71+
using (var s = Sfi.OpenSession())
72+
using (var tx = s.BeginTransaction())
73+
{
74+
s.Delete("from NHibernate.DomainModel.Foo as foo");
75+
tx.Commit();
76+
}
77+
}
78+
79+
[Test]
80+
public void BatchableRootEntityTest()
81+
{
82+
var persister = Sfi.GetEntityPersister(typeof(Foo).FullName);
83+
Assert.That(persister.Cache.Cache, Is.Not.Null);
84+
Assert.That(persister.Cache.Cache, Is.TypeOf<BatchableCache>());
85+
var fooCache = (BatchableCache) persister.Cache.Cache;
86+
87+
persister = Sfi.GetEntityPersister(typeof(Bar).FullName);
88+
Assert.That(persister.Cache.Cache, Is.Not.Null);
89+
Assert.That(persister.Cache.Cache, Is.TypeOf<BatchableCache>());
90+
var barCache = (BatchableCache) persister.Cache.Cache;
91+
92+
Assert.That(barCache, Is.EqualTo(fooCache));
93+
94+
// Add Bar to cache
95+
using (var s = Sfi.OpenSession())
96+
using (var tx = s.BeginTransaction())
97+
{
98+
var list = s.CreateQuery("from foo in class NHibernate.DomainModel.Foo").List();
99+
Assert.AreEqual(6, list.Count);
100+
tx.Commit();
101+
}
102+
103+
Assert.That(fooCache.PutCalls, Has.Count.EqualTo(6)); // Bar is not batchable
104+
Assert.That(fooCache.PutMultipleCalls, Has.Count.EqualTo(0));
105+
106+
// Batch fetch by two from cache
107+
using (var s = Sfi.OpenSession())
108+
using (var tx = s.BeginTransaction())
109+
{
110+
var enumerator =
111+
s.CreateQuery("from foo in class NHibernate.DomainModel.Foo order by foo.String").Enumerable().GetEnumerator();
112+
var i = 1;
113+
while (enumerator.MoveNext())
114+
{
115+
BarProxy bar = (BarProxy) enumerator.Current;
116+
if (i % 2 == 0)
117+
{
118+
string theString = bar.String; // Load the entity
119+
}
120+
i++;
121+
}
122+
tx.Commit();
123+
}
124+
125+
Assert.That(fooCache.GetMultipleCalls, Has.Count.EqualTo(3));
126+
127+
// Check that each key was used only once when retriving objects from the cache
128+
var uniqueKeys = new HashSet<string>();
129+
foreach (var keys in fooCache.GetMultipleCalls)
130+
{
131+
Assert.That(keys, Has.Length.EqualTo(2));
132+
foreach (var key in keys.OfType<CacheKey>().Select(o => (string) o.Key))
133+
{
134+
Assert.That(uniqueKeys, Does.Not.Contains(key));
135+
uniqueKeys.Add(key);
136+
}
137+
}
138+
}
139+
}
140+
}

src/NHibernate/Async/Event/Default/DefaultLoadEventListener.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,7 @@ protected virtual async Task<object> LoadFromSecondLevelCacheAsync(LoadEvent @ev
412412
}
413413
ISessionFactoryImplementor factory = source.Factory;
414414
var batchSize = persister.GetBatchSize();
415-
// TODO: check for subclass support
416-
if (batchSize > 1 && persister.Cache.IsBatchingGetSupported() && !persister.EntityMetamodel.HasSubclasses)
415+
if (batchSize > 1 && persister.Cache.IsBatchingGetSupported())
417416
{
418417
// The first item in the array is the item that we want to load
419418
var entityBatch =

src/NHibernate/Engine/BatchFetchQueue.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ public void RemoveBatchLoadableEntityKey(EntityKey key)
140140
set.Remove(key);
141141
}
142142
}
143+
// A subclass will be added to the batch by the root entity name, when querying by the root entity.
144+
// When removing a subclass key, we need to consider that the subclass may not be batchable but
145+
// its root class may be. In order to prevent having in batch entity keys that are already loaded,
146+
// we have to try to remove the key by the root entity, even if the subclass is not batchable.
147+
if (key.RootEntityName != key.EntityName)
148+
{
149+
if (batchLoadableEntityKeys.TryGetValue(key.RootEntityName, out var set))
150+
{
151+
set.Remove(key);
152+
}
153+
}
143154
}
144155

145156
/// <summary>

src/NHibernate/Engine/EntityKey.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public string EntityName
6262
get { return entityName; }
6363
}
6464

65+
internal string RootEntityName
66+
{
67+
get { return rootEntityName; }
68+
}
69+
6570
public override bool Equals(object other)
6671
{
6772
var otherKey = other as EntityKey;

src/NHibernate/Event/Default/DefaultLoadEventListener.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,7 @@ protected virtual object LoadFromSecondLevelCache(LoadEvent @event, IEntityPersi
423423
}
424424
ISessionFactoryImplementor factory = source.Factory;
425425
var batchSize = persister.GetBatchSize();
426-
// TODO: check for subclass support
427-
if (batchSize > 1 && persister.Cache.IsBatchingGetSupported() && !persister.EntityMetamodel.HasSubclasses)
426+
if (batchSize > 1 && persister.Cache.IsBatchingGetSupported())
428427
{
429428
// The first item in the array is the item that we want to load
430429
var entityBatch =

0 commit comments

Comments
 (0)