diff --git a/Tools/packages.config b/Tools/packages.config
index 367755e5532..438a75071ab 100644
--- a/Tools/packages.config
+++ b/Tools/packages.config
@@ -7,7 +7,7 @@
-
+
diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml
index 47966a224ff..f973b07fe72 100644
--- a/src/AsyncGenerator.yml
+++ b/src/AsyncGenerator.yml
@@ -98,6 +98,12 @@
- conversion: Ignore
name: QuoteTableAndColumns
containingTypeName: SchemaMetadataUpdater
+ - conversion: Ignore
+ name: GetEnumerator
+ containingTypeName: DelayedEnumerator
+ - conversion: Ignore
+ name: GetEnumerator
+ containingTypeName: IFutureEnumerable
- conversion: ToAsync
name: ExecuteReader
containingTypeName: IBatcher
diff --git a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs
index dbfbb9d6c70..c2baef09b8d 100644
--- a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs
+++ b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs
@@ -9,22 +9,20 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
using NHibernate.Cache;
using NHibernate.Cfg;
-using NHibernate.DomainModel;
+using NHibernate.Linq;
+using NHibernate.Multi;
using NHibernate.Test.CacheTest.Caches;
using NUnit.Framework;
using Environment = NHibernate.Cfg.Environment;
-using NHibernate.Linq;
namespace NHibernate.Test.CacheTest
{
+ using System.Threading.Tasks;
using System.Threading;
[TestFixture]
public class BatchableCacheFixtureAsync : TestCase
@@ -46,12 +44,6 @@ protected override void Configure(Configuration configuration)
configuration.SetProperty(Environment.CacheProvider, typeof(BatchableCacheProvider).AssemblyQualifiedName);
}
- protected override bool CheckDatabaseWasCleaned()
- {
- base.CheckDatabaseWasCleaned();
- return true; // We are unable to delete read-only items.
- }
-
protected override void OnSetUp()
{
using (var s = Sfi.OpenSession())
@@ -99,9 +91,16 @@ protected override void OnTearDown()
using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
- s.Delete("from ReadWrite");
+ s.CreateQuery("delete from ReadOnlyItem").ExecuteUpdate();
+ s.CreateQuery("delete from ReadWriteItem").ExecuteUpdate();
+ s.CreateQuery("delete from ReadOnly").ExecuteUpdate();
+ s.CreateQuery("delete from ReadWrite").ExecuteUpdate();
tx.Commit();
}
+ // Must manually evict "readonly" entities since their caches are readonly
+ Sfi.Evict(typeof(ReadOnly));
+ Sfi.Evict(typeof(ReadOnlyItem));
+ Sfi.EvictQueries();
}
[Test]
@@ -110,7 +109,6 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
var persister = Sfi.GetCollectionPersister($"{typeof(ReadOnly).FullName}.Items");
Assert.That(persister.Cache.Cache, Is.Not.Null);
Assert.That(persister.Cache.Cache, Is.TypeOf());
- var cache = (BatchableCache) persister.Cache.Cache;
var ids = new List();
using (var s = Sfi.OpenSession())
@@ -128,7 +126,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
// DefaultInitializeCollectionEventListener and the other time in BatchingCollectionInitializer.
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}, // triggered by InitializeCollectionFromCache method of DefaultInitializeCollectionEventListener type
new[] {1, 2, 3, 4, 5}, // triggered by Initialize method of BatchingCollectionInitializer type
@@ -140,7 +138,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
// the nearest before the demanded collection are added.
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2, 1},
new[] {5, 3, 2, 1, 0},
@@ -150,7 +148,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2, 1},
new[] {4, 3, 2, 1, 0},
@@ -160,7 +158,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4} // 0 get assembled and no further processing is done
},
@@ -169,7 +167,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
),
new Tuple>(
1,
- new int[][]
+ new[]
{
new[] {1, 2, 3, 4, 5}, // 2 and 4 get assembled inside InitializeCollectionFromCache
new[] {3, 5, 0}
@@ -179,7 +177,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2, 1}, // 4 and 2 get assembled inside InitializeCollectionFromCache
new[] {3, 1, 0}
@@ -189,7 +187,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}, // 1 and 3 get assembled inside InitializeCollectionFromCache
new[] {2, 4, 5}
@@ -199,7 +197,7 @@ public async Task MultipleGetReadOnlyCollectionTestAsync()
),
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2, 1}, // 5, 3 and 1 get assembled inside InitializeCollectionFromCache
new[] {2, 0}
@@ -221,7 +219,6 @@ public async Task MultipleGetReadOnlyTestAsync()
var persister = Sfi.GetEntityPersister(typeof(ReadOnly).FullName);
Assert.That(persister.Cache.Cache, Is.Not.Null);
Assert.That(persister.Cache.Cache, Is.TypeOf());
- var cache = (BatchableCache) persister.Cache.Cache;
var ids = new List();
using (var s = Sfi.OpenSession())
@@ -238,7 +235,7 @@ public async Task MultipleGetReadOnlyTestAsync()
// DefaultLoadEventListener and the other time in BatchingEntityLoader.
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type
new[] {1, 2, 3}, // triggered by Load method of BatchingEntityLoader type
@@ -250,7 +247,7 @@ public async Task MultipleGetReadOnlyTestAsync()
// the nearest before the demanded entity are added.
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3},
new[] {5, 3, 2},
@@ -260,7 +257,7 @@ public async Task MultipleGetReadOnlyTestAsync()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3},
new[] {4, 3, 2},
@@ -270,7 +267,7 @@ public async Task MultipleGetReadOnlyTestAsync()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2} // 0 get assembled and no further processing is done
},
@@ -279,7 +276,7 @@ public async Task MultipleGetReadOnlyTestAsync()
),
new Tuple>(
1,
- new int[][]
+ new[]
{
new[] {1, 2, 3}, // 2 gets assembled inside LoadFromSecondLevelCache
new[] {3, 4, 5}
@@ -289,7 +286,7 @@ public async Task MultipleGetReadOnlyTestAsync()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3}, // 4 gets assembled inside LoadFromSecondLevelCache
new[] {3, 2, 1}
@@ -299,7 +296,7 @@ public async Task MultipleGetReadOnlyTestAsync()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2}, // 1 gets assembled inside LoadFromSecondLevelCache
new[] {2, 3, 4}
@@ -309,7 +306,7 @@ public async Task MultipleGetReadOnlyTestAsync()
),
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3}, // 5 and 3 get assembled inside LoadFromSecondLevelCache
new[] {2, 1, 0}
@@ -331,7 +328,6 @@ public async Task MultipleGetReadOnlyItemTestAsync()
var persister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName);
Assert.That(persister.Cache.Cache, Is.Not.Null);
Assert.That(persister.Cache.Cache, Is.TypeOf());
- var cache = (BatchableCache) persister.Cache.Cache;
var ids = new List();
using (var s = Sfi.OpenSession())
@@ -348,7 +344,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
// DefaultLoadEventListener and the other time in BatchingEntityLoader.
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type
new[] {1, 2, 3, 4}, // triggered by Load method of BatchingEntityLoader type
@@ -360,7 +356,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
// the nearest before the demanded entity are added.
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2},
new[] {5, 3, 2, 1},
@@ -370,7 +366,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2},
new[] {4, 3, 2, 1},
@@ -380,7 +376,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3} // 0 get assembled and no further processing is done
},
@@ -389,7 +385,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
),
new Tuple>(
1,
- new int[][]
+ new[]
{
new[] {1, 2, 3, 4}, // 2 and 4 get assembled inside LoadFromSecondLevelCache
new[] {3, 5, 0}
@@ -399,7 +395,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2}, // 4 and 2 get assembled inside LoadFromSecondLevelCache
new[] {3, 1, 0}
@@ -409,7 +405,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3}, // 1 and 3 get assembled inside LoadFromSecondLevelCache
new[] {2, 4, 5}
@@ -419,7 +415,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
),
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2}, // 5 and 3 get assembled inside LoadFromSecondLevelCache
new[] {2, 1, 0}
@@ -459,7 +455,7 @@ public async Task MultiplePutReadWriteTestAsync()
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2},
new[] {3, 4, 5}
@@ -468,7 +464,7 @@ public async Task MultiplePutReadWriteTestAsync()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2},
new[] {3, 4, 5}
@@ -477,7 +473,7 @@ public async Task MultiplePutReadWriteTestAsync()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2},
new[] {3, 4, 5}
@@ -514,7 +510,7 @@ public async Task MultiplePutReadWriteItemTestAsync()
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}
},
@@ -522,7 +518,7 @@ public async Task MultiplePutReadWriteItemTestAsync()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}
},
@@ -530,7 +526,7 @@ public async Task MultiplePutReadWriteItemTestAsync()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}
},
@@ -542,33 +538,240 @@ public async Task MultiplePutReadWriteItemTestAsync()
public async Task UpdateTimestampsCacheTestAsync()
{
var timestamp = Sfi.UpdateTimestampsCache;
+ var fieldReadonly = typeof(UpdateTimestampsCache).GetField(
+ "_batchReadOnlyUpdateTimestamps",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(fieldReadonly, Is.Not.Null, "Unable to find _batchReadOnlyUpdateTimestamps field");
+ Assert.That(fieldReadonly.GetValue(timestamp), Is.Not.Null, "_batchReadOnlyUpdateTimestamps is null");
var field = typeof(UpdateTimestampsCache).GetField(
"_batchUpdateTimestamps",
BindingFlags.NonPublic | BindingFlags.Instance);
- Assert.That(field, Is.Not.Null);
+ Assert.That(field, Is.Not.Null, "Unable to find _batchUpdateTimestamps field");
var cache = (BatchableCache) field.GetValue(timestamp);
- Assert.That(cache, Is.Not.Null);
+ Assert.That(cache, Is.Not.Null, "_batchUpdateTimestamps is null");
+ await (cache.ClearAsync(CancellationToken.None));
+ cache.ClearStatistics();
+
+ const string query = "from ReadOnly e where e.Name = :name";
+ const string name = "Name1";
using (var s = OpenSession())
+ using (var t = s.BeginTransaction())
{
- const string query = "from ReadOnly e where e.Name = :name";
- const string name = "Name1";
await (s
.CreateQuery(query)
.SetString("name", name)
.SetCacheable(true)
.UniqueResultAsync());
+ await (t.CommitAsync());
+ }
- // Run a second time, just to test the query cache
- var result = await (s
- .CreateQuery(query)
- .SetString("name", name)
- .SetCacheable(true)
- .UniqueResultAsync());
+ // Run a second time, to test the query cache
+ using (var s = OpenSession())
+ using (var t = s.BeginTransaction())
+ {
+ var result =
+ await (s
+ .CreateQuery(query)
+ .SetString("name", name)
+ .SetCacheable(true)
+ .UniqueResultAsync());
Assert.That(result, Is.Not.Null);
- Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1));
- Assert.That(cache.GetCalls, Has.Count.EqualTo(0));
+ await (t.CommitAsync());
+ }
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1), "GetMany");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "Get");
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(0), "PutMany");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "Put");
+
+ // Update entities to put some update ts
+ using (var s = OpenSession())
+ using (var t = s.BeginTransaction())
+ {
+ var readwrite1 = await (s.Query().FirstAsync());
+ readwrite1.Name = "NewName";
+ await (t.CommitAsync());
+ }
+ // PreInvalidate + Invalidate => 2 calls
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(2), "PutMany after update");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "Put after update");
+ }
+
+ [Test]
+ public async Task QueryCacheTestAsync()
+ {
+ // QueryCache batching is used by QueryBatch.
+ if (!Sfi.ConnectionProvider.Driver.SupportsMultipleQueries)
+ Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries");
+
+ var queryCache = Sfi.GetQueryCache(null);
+ var readonlyField = typeof(StandardQueryCache).GetField(
+ "_batchableReadOnlyCache",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(readonlyField, Is.Not.Null, "Unable to find _batchableReadOnlyCache field");
+ Assert.That(readonlyField.GetValue(queryCache), Is.Not.Null, "_batchableReadOnlyCache is null");
+ var field = typeof(StandardQueryCache).GetField(
+ "_batchableCache",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(field, Is.Not.Null, "Unable to find _batchableCache field");
+ var cache = (BatchableCache) field.GetValue(queryCache);
+ Assert.That(cache, Is.Not.Null, "_batchableCache is null");
+
+ var timestamp = Sfi.UpdateTimestampsCache;
+ var tsField = typeof(UpdateTimestampsCache).GetField(
+ "_batchUpdateTimestamps",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(tsField, Is.Not.Null, "Unable to find _batchUpdateTimestamps field");
+ var tsCache = (BatchableCache) tsField.GetValue(timestamp);
+ Assert.That(tsCache, Is.Not.Null, "_batchUpdateTimestamps is null");
+
+ await (cache.ClearAsync(CancellationToken.None));
+ cache.ClearStatistics();
+ await (tsCache.ClearAsync(CancellationToken.None));
+ tsCache.ClearStatistics();
+
+ using (var s = OpenSession())
+ {
+ const string query = "from ReadOnly e where e.Name = :name";
+ const string name1 = "Name1";
+ const string name2 = "Name2";
+ const string name3 = "Name3";
+ const string name4 = "Name4";
+ const string name5 = "Name5";
+ var q1 =
+ s
+ .CreateQuery(query)
+ .SetString("name", name1)
+ .SetCacheable(true);
+ var q2 =
+ s
+ .CreateQuery(query)
+ .SetString("name", name2)
+ .SetCacheable(true);
+ var q3 =
+ s
+ .Query()
+ .Where(r => r.Name == name3)
+ .WithOptions(o => o.SetCacheable(true));
+ var q4 =
+ s
+ .QueryOver()
+ .Where(r => r.Name == name4)
+ .Cacheable();
+ var q5 =
+ s
+ .CreateSQLQuery("select * " + query)
+ .AddEntity(typeof(ReadOnly))
+ .SetString("name", name5)
+ .SetCacheable(true);
+
+ var queries =
+ s
+ .CreateQueryBatch()
+ .Add(q1)
+ .Add(q2)
+ .Add(q3)
+ .Add(q4)
+ .Add(q5);
+
+ using (var t = s.BeginTransaction())
+ {
+ await (queries.ExecuteAsync(CancellationToken.None));
+ await (t.CommitAsync());
+ }
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1), "cache GetMany first execution");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "cache Get first execution");
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1), "cache PutMany first execution");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "cache Put first execution");
+
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(0), "tsCache GetMany first execution");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get first execution");
+
+ // Run a second time, to test the query cache
+ using (var t = s.BeginTransaction())
+ {
+ await (queries.ExecuteAsync(CancellationToken.None));
+ await (t.CommitAsync());
+ }
+
+ Assert.That(
+ await (queries.GetResultAsync(0, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1");
+ Assert.That(
+ await (queries.GetResultAsync(1, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2");
+ Assert.That(
+ await (queries.GetResultAsync(2, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name3), "q3");
+ Assert.That(
+ await (queries.GetResultAsync(3, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4");
+ Assert.That(
+ await (queries.GetResultAsync(4, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5");
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(2), "cache GetMany secondExecution");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "cache Get secondExecution");
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1), "cache PutMany secondExecution");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "cache Put secondExecution");
+
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(1), "tsCache GetMany secondExecution");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get secondExecution");
+ Assert.That(tsCache.PutMultipleCalls, Has.Count.EqualTo(0), "tsCache PutMany secondExecution");
+ Assert.That(tsCache.PutCalls, Has.Count.EqualTo(0), "tsCache Put secondExecution");
+
+ // Update an entity to invalidate them
+ using (var t = s.BeginTransaction())
+ {
+ var readwrite1 = await (s.Query().SingleAsync(e => e.Name == name3));
+ readwrite1.Name = "NewName";
+ await (t.CommitAsync());
+ }
+
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(1), "tsCache GetMany after update");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get after update");
+ // Pre-invalidate + invalidate => 2 calls
+ Assert.That(tsCache.PutMultipleCalls, Has.Count.EqualTo(2), "tsCache PutMany after update");
+ Assert.That(tsCache.PutCalls, Has.Count.EqualTo(0), "tsCache Put after update");
+
+ // Run a third time, to re-test the query cache
+ using (var t = s.BeginTransaction())
+ {
+ await (queries.ExecuteAsync(CancellationToken.None));
+ await (t.CommitAsync());
+ }
+
+ Assert.That(
+ await (queries.GetResultAsync(0, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1 after update");
+ Assert.That(
+ await (queries.GetResultAsync(1, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2 after update");
+ Assert.That(
+ await (queries.GetResultAsync(2, CancellationToken.None)),
+ Has.Count.EqualTo(0), "q3 after update");
+ Assert.That(
+ await (queries.GetResultAsync(3, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4 after update");
+ Assert.That(
+ await (queries.GetResultAsync(4, CancellationToken.None)),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5 after update");
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(3), "cache GetMany thirdExecution");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "cache Get thirdExecution");
+ // ReadWrite queries should have been re-put, so count should have been incremented
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(2), "cache PutMany thirdExecution");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "cache Put thirdExecution");
+
+ // Readonly entities should have been still cached, so their queries timestamp should have been
+ // rechecked and the get count incremented
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(2), "tsCache GetMany thirdExecution");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get thirdExecution");
+ Assert.That(tsCache.PutMultipleCalls, Has.Count.EqualTo(2), "tsCache PutMany thirdExecution");
+ Assert.That(tsCache.PutCalls, Has.Count.EqualTo(0), "tsCache Put thirdExecution");
}
}
diff --git a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs
index 2cc850b8e97..fbe948fb8c1 100644
--- a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs
+++ b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs
@@ -304,7 +304,7 @@ public async Task SerializationAsync()
var transformer = Transformers.AliasToBean();
var bytes = SerializationHelper.Serialize(transformer);
transformer = (IResultTransformer)SerializationHelper.Deserialize(bytes);
- return AssertCardinalityNameAndIdAsync(transformer: transformer, cancellationToken:cancellationToken);
+ return AssertCardinalityNameAndIdAsync(transformer: transformer, cancellationToken: cancellationToken);
}
catch (System.Exception ex)
{
diff --git a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs
index 97afbebcb74..9e166ef6509 100644
--- a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs
+++ b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs
@@ -1,13 +1,11 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
using NHibernate.Cache;
using NHibernate.Cfg;
-using NHibernate.DomainModel;
+using NHibernate.Linq;
+using NHibernate.Multi;
using NHibernate.Test.CacheTest.Caches;
using NUnit.Framework;
using Environment = NHibernate.Cfg.Environment;
@@ -34,12 +32,6 @@ protected override void Configure(Configuration configuration)
configuration.SetProperty(Environment.CacheProvider, typeof(BatchableCacheProvider).AssemblyQualifiedName);
}
- protected override bool CheckDatabaseWasCleaned()
- {
- base.CheckDatabaseWasCleaned();
- return true; // We are unable to delete read-only items.
- }
-
protected override void OnSetUp()
{
using (var s = Sfi.OpenSession())
@@ -87,9 +79,16 @@ protected override void OnTearDown()
using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
- s.Delete("from ReadWrite");
+ s.CreateQuery("delete from ReadOnlyItem").ExecuteUpdate();
+ s.CreateQuery("delete from ReadWriteItem").ExecuteUpdate();
+ s.CreateQuery("delete from ReadOnly").ExecuteUpdate();
+ s.CreateQuery("delete from ReadWrite").ExecuteUpdate();
tx.Commit();
}
+ // Must manually evict "readonly" entities since their caches are readonly
+ Sfi.Evict(typeof(ReadOnly));
+ Sfi.Evict(typeof(ReadOnlyItem));
+ Sfi.EvictQueries();
}
[Test]
@@ -98,7 +97,6 @@ public void MultipleGetReadOnlyCollectionTest()
var persister = Sfi.GetCollectionPersister($"{typeof(ReadOnly).FullName}.Items");
Assert.That(persister.Cache.Cache, Is.Not.Null);
Assert.That(persister.Cache.Cache, Is.TypeOf());
- var cache = (BatchableCache) persister.Cache.Cache;
var ids = new List();
using (var s = Sfi.OpenSession())
@@ -116,7 +114,7 @@ public void MultipleGetReadOnlyCollectionTest()
// DefaultInitializeCollectionEventListener and the other time in BatchingCollectionInitializer.
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}, // triggered by InitializeCollectionFromCache method of DefaultInitializeCollectionEventListener type
new[] {1, 2, 3, 4, 5}, // triggered by Initialize method of BatchingCollectionInitializer type
@@ -128,7 +126,7 @@ public void MultipleGetReadOnlyCollectionTest()
// the nearest before the demanded collection are added.
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2, 1},
new[] {5, 3, 2, 1, 0},
@@ -138,7 +136,7 @@ public void MultipleGetReadOnlyCollectionTest()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2, 1},
new[] {4, 3, 2, 1, 0},
@@ -148,7 +146,7 @@ public void MultipleGetReadOnlyCollectionTest()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4} // 0 get assembled and no further processing is done
},
@@ -157,7 +155,7 @@ public void MultipleGetReadOnlyCollectionTest()
),
new Tuple>(
1,
- new int[][]
+ new[]
{
new[] {1, 2, 3, 4, 5}, // 2 and 4 get assembled inside InitializeCollectionFromCache
new[] {3, 5, 0}
@@ -167,7 +165,7 @@ public void MultipleGetReadOnlyCollectionTest()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2, 1}, // 4 and 2 get assembled inside InitializeCollectionFromCache
new[] {3, 1, 0}
@@ -177,7 +175,7 @@ public void MultipleGetReadOnlyCollectionTest()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}, // 1 and 3 get assembled inside InitializeCollectionFromCache
new[] {2, 4, 5}
@@ -187,7 +185,7 @@ public void MultipleGetReadOnlyCollectionTest()
),
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2, 1}, // 5, 3 and 1 get assembled inside InitializeCollectionFromCache
new[] {2, 0}
@@ -209,7 +207,6 @@ public void MultipleGetReadOnlyTest()
var persister = Sfi.GetEntityPersister(typeof(ReadOnly).FullName);
Assert.That(persister.Cache.Cache, Is.Not.Null);
Assert.That(persister.Cache.Cache, Is.TypeOf());
- var cache = (BatchableCache) persister.Cache.Cache;
var ids = new List();
using (var s = Sfi.OpenSession())
@@ -226,7 +223,7 @@ public void MultipleGetReadOnlyTest()
// DefaultLoadEventListener and the other time in BatchingEntityLoader.
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type
new[] {1, 2, 3}, // triggered by Load method of BatchingEntityLoader type
@@ -238,7 +235,7 @@ public void MultipleGetReadOnlyTest()
// the nearest before the demanded entity are added.
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3},
new[] {5, 3, 2},
@@ -248,7 +245,7 @@ public void MultipleGetReadOnlyTest()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3},
new[] {4, 3, 2},
@@ -258,7 +255,7 @@ public void MultipleGetReadOnlyTest()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2} // 0 get assembled and no further processing is done
},
@@ -267,7 +264,7 @@ public void MultipleGetReadOnlyTest()
),
new Tuple>(
1,
- new int[][]
+ new[]
{
new[] {1, 2, 3}, // 2 gets assembled inside LoadFromSecondLevelCache
new[] {3, 4, 5}
@@ -277,7 +274,7 @@ public void MultipleGetReadOnlyTest()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3}, // 4 gets assembled inside LoadFromSecondLevelCache
new[] {3, 2, 1}
@@ -287,7 +284,7 @@ public void MultipleGetReadOnlyTest()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2}, // 1 gets assembled inside LoadFromSecondLevelCache
new[] {2, 3, 4}
@@ -297,7 +294,7 @@ public void MultipleGetReadOnlyTest()
),
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3}, // 5 and 3 get assembled inside LoadFromSecondLevelCache
new[] {2, 1, 0}
@@ -319,7 +316,6 @@ public void MultipleGetReadOnlyItemTest()
var persister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName);
Assert.That(persister.Cache.Cache, Is.Not.Null);
Assert.That(persister.Cache.Cache, Is.TypeOf());
- var cache = (BatchableCache) persister.Cache.Cache;
var ids = new List();
using (var s = Sfi.OpenSession())
@@ -336,7 +332,7 @@ public void MultipleGetReadOnlyItemTest()
// DefaultLoadEventListener and the other time in BatchingEntityLoader.
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type
new[] {1, 2, 3, 4}, // triggered by Load method of BatchingEntityLoader type
@@ -348,7 +344,7 @@ public void MultipleGetReadOnlyItemTest()
// the nearest before the demanded entity are added.
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2},
new[] {5, 3, 2, 1},
@@ -358,7 +354,7 @@ public void MultipleGetReadOnlyItemTest()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2},
new[] {4, 3, 2, 1},
@@ -368,7 +364,7 @@ public void MultipleGetReadOnlyItemTest()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3} // 0 get assembled and no further processing is done
},
@@ -377,7 +373,7 @@ public void MultipleGetReadOnlyItemTest()
),
new Tuple>(
1,
- new int[][]
+ new[]
{
new[] {1, 2, 3, 4}, // 2 and 4 get assembled inside LoadFromSecondLevelCache
new[] {3, 5, 0}
@@ -387,7 +383,7 @@ public void MultipleGetReadOnlyItemTest()
),
new Tuple>(
5,
- new int[][]
+ new[]
{
new[] {5, 4, 3, 2}, // 4 and 2 get assembled inside LoadFromSecondLevelCache
new[] {3, 1, 0}
@@ -397,7 +393,7 @@ public void MultipleGetReadOnlyItemTest()
),
new Tuple>(
0,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3}, // 1 and 3 get assembled inside LoadFromSecondLevelCache
new[] {2, 4, 5}
@@ -407,7 +403,7 @@ public void MultipleGetReadOnlyItemTest()
),
new Tuple>(
4,
- new int[][]
+ new[]
{
new[] {4, 5, 3, 2}, // 5 and 3 get assembled inside LoadFromSecondLevelCache
new[] {2, 1, 0}
@@ -447,7 +443,7 @@ public void MultiplePutReadWriteTest()
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2},
new[] {3, 4, 5}
@@ -456,7 +452,7 @@ public void MultiplePutReadWriteTest()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2},
new[] {3, 4, 5}
@@ -465,7 +461,7 @@ public void MultiplePutReadWriteTest()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2},
new[] {3, 4, 5}
@@ -502,7 +498,7 @@ public void MultiplePutReadWriteItemTest()
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}
},
@@ -510,7 +506,7 @@ public void MultiplePutReadWriteItemTest()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}
},
@@ -518,7 +514,7 @@ public void MultiplePutReadWriteItemTest()
);
AssertEquivalent(
ids,
- new int[][]
+ new[]
{
new[] {0, 1, 2, 3, 4}
},
@@ -530,33 +526,240 @@ public void MultiplePutReadWriteItemTest()
public void UpdateTimestampsCacheTest()
{
var timestamp = Sfi.UpdateTimestampsCache;
+ var fieldReadonly = typeof(UpdateTimestampsCache).GetField(
+ "_batchReadOnlyUpdateTimestamps",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(fieldReadonly, Is.Not.Null, "Unable to find _batchReadOnlyUpdateTimestamps field");
+ Assert.That(fieldReadonly.GetValue(timestamp), Is.Not.Null, "_batchReadOnlyUpdateTimestamps is null");
var field = typeof(UpdateTimestampsCache).GetField(
"_batchUpdateTimestamps",
BindingFlags.NonPublic | BindingFlags.Instance);
- Assert.That(field, Is.Not.Null);
+ Assert.That(field, Is.Not.Null, "Unable to find _batchUpdateTimestamps field");
var cache = (BatchableCache) field.GetValue(timestamp);
- Assert.That(cache, Is.Not.Null);
+ Assert.That(cache, Is.Not.Null, "_batchUpdateTimestamps is null");
+ cache.Clear();
+ cache.ClearStatistics();
+
+ const string query = "from ReadOnly e where e.Name = :name";
+ const string name = "Name1";
using (var s = OpenSession())
+ using (var t = s.BeginTransaction())
{
- const string query = "from ReadOnly e where e.Name = :name";
- const string name = "Name1";
s
.CreateQuery(query)
.SetString("name", name)
.SetCacheable(true)
.UniqueResult();
+ t.Commit();
+ }
- // Run a second time, just to test the query cache
- var result = s
- .CreateQuery(query)
- .SetString("name", name)
- .SetCacheable(true)
- .UniqueResult();
+ // Run a second time, to test the query cache
+ using (var s = OpenSession())
+ using (var t = s.BeginTransaction())
+ {
+ var result =
+ s
+ .CreateQuery(query)
+ .SetString("name", name)
+ .SetCacheable(true)
+ .UniqueResult();
Assert.That(result, Is.Not.Null);
- Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1));
- Assert.That(cache.GetCalls, Has.Count.EqualTo(0));
+ t.Commit();
+ }
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1), "GetMany");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "Get");
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(0), "PutMany");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "Put");
+
+ // Update entities to put some update ts
+ using (var s = OpenSession())
+ using (var t = s.BeginTransaction())
+ {
+ var readwrite1 = s.Query().First();
+ readwrite1.Name = "NewName";
+ t.Commit();
+ }
+ // PreInvalidate + Invalidate => 2 calls
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(2), "PutMany after update");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "Put after update");
+ }
+
+ [Test]
+ public void QueryCacheTest()
+ {
+ // QueryCache batching is used by QueryBatch.
+ if (!Sfi.ConnectionProvider.Driver.SupportsMultipleQueries)
+ Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries");
+
+ var queryCache = Sfi.GetQueryCache(null);
+ var readonlyField = typeof(StandardQueryCache).GetField(
+ "_batchableReadOnlyCache",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(readonlyField, Is.Not.Null, "Unable to find _batchableReadOnlyCache field");
+ Assert.That(readonlyField.GetValue(queryCache), Is.Not.Null, "_batchableReadOnlyCache is null");
+ var field = typeof(StandardQueryCache).GetField(
+ "_batchableCache",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(field, Is.Not.Null, "Unable to find _batchableCache field");
+ var cache = (BatchableCache) field.GetValue(queryCache);
+ Assert.That(cache, Is.Not.Null, "_batchableCache is null");
+
+ var timestamp = Sfi.UpdateTimestampsCache;
+ var tsField = typeof(UpdateTimestampsCache).GetField(
+ "_batchUpdateTimestamps",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(tsField, Is.Not.Null, "Unable to find _batchUpdateTimestamps field");
+ var tsCache = (BatchableCache) tsField.GetValue(timestamp);
+ Assert.That(tsCache, Is.Not.Null, "_batchUpdateTimestamps is null");
+
+ cache.Clear();
+ cache.ClearStatistics();
+ tsCache.Clear();
+ tsCache.ClearStatistics();
+
+ using (var s = OpenSession())
+ {
+ const string query = "from ReadOnly e where e.Name = :name";
+ const string name1 = "Name1";
+ const string name2 = "Name2";
+ const string name3 = "Name3";
+ const string name4 = "Name4";
+ const string name5 = "Name5";
+ var q1 =
+ s
+ .CreateQuery(query)
+ .SetString("name", name1)
+ .SetCacheable(true);
+ var q2 =
+ s
+ .CreateQuery(query)
+ .SetString("name", name2)
+ .SetCacheable(true);
+ var q3 =
+ s
+ .Query()
+ .Where(r => r.Name == name3)
+ .WithOptions(o => o.SetCacheable(true));
+ var q4 =
+ s
+ .QueryOver()
+ .Where(r => r.Name == name4)
+ .Cacheable();
+ var q5 =
+ s
+ .CreateSQLQuery("select * " + query)
+ .AddEntity(typeof(ReadOnly))
+ .SetString("name", name5)
+ .SetCacheable(true);
+
+ var queries =
+ s
+ .CreateQueryBatch()
+ .Add(q1)
+ .Add(q2)
+ .Add(q3)
+ .Add(q4)
+ .Add(q5);
+
+ using (var t = s.BeginTransaction())
+ {
+ queries.Execute();
+ t.Commit();
+ }
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1), "cache GetMany first execution");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "cache Get first execution");
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1), "cache PutMany first execution");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "cache Put first execution");
+
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(0), "tsCache GetMany first execution");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get first execution");
+
+ // Run a second time, to test the query cache
+ using (var t = s.BeginTransaction())
+ {
+ queries.Execute();
+ t.Commit();
+ }
+
+ Assert.That(
+ queries.GetResult(0),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1");
+ Assert.That(
+ queries.GetResult(1),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2");
+ Assert.That(
+ queries.GetResult(2),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name3), "q3");
+ Assert.That(
+ queries.GetResult(3),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4");
+ Assert.That(
+ queries.GetResult(4),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5");
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(2), "cache GetMany secondExecution");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "cache Get secondExecution");
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1), "cache PutMany secondExecution");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "cache Put secondExecution");
+
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(1), "tsCache GetMany secondExecution");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get secondExecution");
+ Assert.That(tsCache.PutMultipleCalls, Has.Count.EqualTo(0), "tsCache PutMany secondExecution");
+ Assert.That(tsCache.PutCalls, Has.Count.EqualTo(0), "tsCache Put secondExecution");
+
+ // Update an entity to invalidate them
+ using (var t = s.BeginTransaction())
+ {
+ var readwrite1 = s.Query().Single(e => e.Name == name3);
+ readwrite1.Name = "NewName";
+ t.Commit();
+ }
+
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(1), "tsCache GetMany after update");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get after update");
+ // Pre-invalidate + invalidate => 2 calls
+ Assert.That(tsCache.PutMultipleCalls, Has.Count.EqualTo(2), "tsCache PutMany after update");
+ Assert.That(tsCache.PutCalls, Has.Count.EqualTo(0), "tsCache Put after update");
+
+ // Run a third time, to re-test the query cache
+ using (var t = s.BeginTransaction())
+ {
+ queries.Execute();
+ t.Commit();
+ }
+
+ Assert.That(
+ queries.GetResult(0),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1 after update");
+ Assert.That(
+ queries.GetResult(1),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2 after update");
+ Assert.That(
+ queries.GetResult(2),
+ Has.Count.EqualTo(0), "q3 after update");
+ Assert.That(
+ queries.GetResult(3),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4 after update");
+ Assert.That(
+ queries.GetResult(4),
+ Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5 after update");
+
+ Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(3), "cache GetMany thirdExecution");
+ Assert.That(cache.GetCalls, Has.Count.EqualTo(0), "cache Get thirdExecution");
+ // ReadWrite queries should have been re-put, so count should have been incremented
+ Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(2), "cache PutMany thirdExecution");
+ Assert.That(cache.PutCalls, Has.Count.EqualTo(0), "cache Put thirdExecution");
+
+ // Readonly entities should have been still cached, so their queries timestamp should have been
+ // rechecked and the get count incremented
+ Assert.That(tsCache.GetMultipleCalls, Has.Count.EqualTo(2), "tsCache GetMany thirdExecution");
+ Assert.That(tsCache.GetCalls, Has.Count.EqualTo(0), "tsCache Get thirdExecution");
+ Assert.That(tsCache.PutMultipleCalls, Has.Count.EqualTo(2), "tsCache PutMany thirdExecution");
+ Assert.That(tsCache.PutCalls, Has.Count.EqualTo(0), "tsCache Put thirdExecution");
}
}
diff --git a/src/NHibernate/Async/Cache/CacheBatcher.cs b/src/NHibernate/Async/Cache/CacheBatcher.cs
index bce22267614..de7fa2228b9 100644
--- a/src/NHibernate/Async/Cache/CacheBatcher.cs
+++ b/src/NHibernate/Async/Cache/CacheBatcher.cs
@@ -8,11 +8,7 @@
//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Text;
-using NHibernate.Cache.Access;
using NHibernate.Engine;
using NHibernate.Persister.Collection;
using NHibernate.Persister.Entity;
@@ -21,7 +17,7 @@ namespace NHibernate.Cache
{
using System.Threading.Tasks;
using System.Threading;
- internal partial class CacheBatcher
+ public sealed partial class CacheBatcher
{
///
@@ -31,7 +27,7 @@ internal partial class CacheBatcher
/// The entity persister.
/// The data to put in the cache.
/// A cancellation token that can be used to cancel the work
- public async Task AddToBatchAsync(IEntityPersister persister, CachePutData data, CancellationToken cancellationToken)
+ internal async Task AddToBatchAsync(IEntityPersister persister, CachePutData data, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (ShouldExecuteBatch(persister, _putBatch))
@@ -54,7 +50,7 @@ public async Task AddToBatchAsync(IEntityPersister persister, CachePutData data,
/// The collection persister.
/// The data to put in the cache.
/// A cancellation token that can be used to cancel the work
- public async Task AddToBatchAsync(ICollectionPersister persister, CachePutData data, CancellationToken cancellationToken)
+ internal async Task AddToBatchAsync(ICollectionPersister persister, CachePutData data, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (ShouldExecuteBatch(persister, _putBatch))
@@ -74,7 +70,7 @@ public async Task AddToBatchAsync(ICollectionPersister persister, CachePutData d
/// Executes the current batch.
///
/// A cancellation token that can be used to cancel the work
- public async Task ExecuteBatchAsync(CancellationToken cancellationToken)
+ internal async Task ExecuteBatchAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_currentBatch == null || _currentBatch.BatchSize == 0)
diff --git a/src/NHibernate/Async/Cache/IQueryCache.cs b/src/NHibernate/Async/Cache/IQueryCache.cs
index 8ac8d1bfb24..5cb2aded412 100644
--- a/src/NHibernate/Async/Cache/IQueryCache.cs
+++ b/src/NHibernate/Async/Cache/IQueryCache.cs
@@ -8,6 +8,7 @@
//------------------------------------------------------------------------------
+using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Engine;
@@ -20,8 +21,275 @@ namespace NHibernate.Cache
public partial interface IQueryCache
{
+ ///
+ /// Clear the cache.
+ ///
+ /// A cancellation token that can be used to cancel the work
Task ClearAsync(CancellationToken cancellationToken);
+
+ // Since 5.2
+ [Obsolete("Have the query cache implement IBatchableQueryCache, and use IBatchableQueryCache.Put")]
Task PutAsync(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool isNaturalKeyLookup, ISessionImplementor session, CancellationToken cancellationToken);
+
+ // Since 5.2
+ [Obsolete("Have the query cache implement IBatchableQueryCache, and use IBatchableQueryCache.Get")]
Task GetAsync(QueryKey key, ICacheAssembler[] returnTypes, bool isNaturalKeyLookup, ISet spaces, ISessionImplementor session, CancellationToken cancellationToken);
}
+
+ public partial interface IBatchableQueryCache : IQueryCache
+ {
+ ///
+ /// Get query results from the cache.
+ ///
+ /// The query key.
+ /// The query parameters.
+ /// The query result row types.
+ /// The query spaces.
+ /// The session for which the query is executed.
+ /// A cancellation token that can be used to cancel the work
+ /// The query results, if cached.
+ Task GetAsync(
+ QueryKey key, QueryParameters queryParameters, ICacheAssembler[] returnTypes, ISet spaces,
+ ISessionImplementor session, CancellationToken cancellationToken);
+
+ ///
+ /// Put query results in the cache.
+ ///
+ /// The query key.
+ /// The query parameters.
+ /// The query result row types.
+ /// The query result.
+ /// The session for which the query was executed.
+ /// A cancellation token that can be used to cancel the work
+ /// if the result has been cached,
+ /// otherwise.
+ Task PutAsync(
+ QueryKey key, QueryParameters queryParameters, ICacheAssembler[] returnTypes, IList result,
+ ISessionImplementor session, CancellationToken cancellationToken);
+
+ ///
+ /// Retrieve multiple query results from the cache.
+ ///
+ /// The query keys.
+ /// The array of query parameters matching .
+ /// The array of query result row types matching .
+ /// The array of query spaces matching .
+ /// The session for which the queries are executed.
+ /// A cancellation token that can be used to cancel the work
+ /// The cached query results, matching each key of respectively. For each
+ /// missed key, it will contain a .
+ Task GetManyAsync(
+ QueryKey[] keys, QueryParameters[] queryParameters, ICacheAssembler[][] returnTypes,
+ ISet[] spaces, ISessionImplementor session, CancellationToken cancellationToken);
+
+ ///
+ /// Attempt to cache objects, after loading them from the database.
+ ///
+ /// The query keys.
+ /// The array of query parameters matching .
+ /// The array of query result row types matching .
+ /// The array of query results matching .
+ /// The session for which the queries were executed.
+ /// A cancellation token that can be used to cancel the work
+ /// An array of boolean indicating if each query was successfully cached.
+ ///
+ Task PutManyAsync(
+ QueryKey[] keys, QueryParameters[] queryParameters, ICacheAssembler[][] returnTypes, IList[] results,
+ ISessionImplementor session, CancellationToken cancellationToken);
+ }
+
+ internal static partial class QueryCacheExtensions
+ {
+
+ ///
+ /// Get query results from the cache.
+ ///
+ /// The cache.
+ /// The query key.
+ /// The query parameters.
+ /// The query result row types.
+ /// The query spaces.
+ /// The session for which the query is executed.
+ /// A cancellation token that can be used to cancel the work
+ /// The query results, if cached.
+ public static async Task GetAsync(
+ this IQueryCache queryCache,
+ QueryKey key,
+ QueryParameters queryParameters,
+ ICacheAssembler[] returnTypes,
+ ISet spaces,
+ ISessionImplementor session, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ if (queryCache is IBatchableQueryCache batchableQueryCache)
+ {
+ return await (batchableQueryCache.GetAsync(
+ key,
+ queryParameters,
+ returnTypes,
+ spaces,
+ session, cancellationToken)).ConfigureAwait(false);
+ }
+
+ if (!_hasWarnForObsoleteQueryCache)
+ {
+ _hasWarnForObsoleteQueryCache = true;
+ Log.Warn("{0} is obsolete, it should implement {1}", queryCache, nameof(IBatchableQueryCache));
+ }
+
+ var persistenceContext = session.PersistenceContext;
+
+ var defaultReadOnlyOrig = persistenceContext.DefaultReadOnly;
+
+ if (queryParameters.IsReadOnlyInitialized)
+ persistenceContext.DefaultReadOnly = queryParameters.ReadOnly;
+ else
+ queryParameters.ReadOnly = persistenceContext.DefaultReadOnly;
+
+ try
+ {
+#pragma warning disable 618
+ return await (queryCache.GetAsync(
+#pragma warning restore 618
+ key,
+ returnTypes,
+ queryParameters.NaturalKeyLookup,
+ spaces,
+ session, cancellationToken)).ConfigureAwait(false);
+ }
+ finally
+ {
+ persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
+ }
+ }
+
+ ///
+ /// Put query results in the cache.
+ ///
+ /// The cache.
+ /// The query key.
+ /// The query parameters.
+ /// The query result row types.
+ /// The query result.
+ /// The session for which the query was executed.
+ /// A cancellation token that can be used to cancel the work
+ /// if the result has been cached,
+ /// otherwise.
+ public static Task PutAsync(
+ this IQueryCache queryCache,
+ QueryKey key,
+ QueryParameters queryParameters,
+ ICacheAssembler[] returnTypes,
+ IList result,
+ ISessionImplementor session, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ if (queryCache is IBatchableQueryCache batchableQueryCache)
+ {
+ return batchableQueryCache.PutAsync(
+ key, queryParameters,
+ returnTypes,
+ result, session, cancellationToken);
+ }
+
+#pragma warning disable 618
+ return queryCache.PutAsync(
+#pragma warning restore 618
+ key,
+ returnTypes,
+ result,
+ queryParameters.NaturalKeyLookup,
+ session, cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
+ ///
+ /// Retrieve multiple query results from the cache.
+ ///
+ /// The cache.
+ /// The query keys.
+ /// The array of query parameters matching .
+ /// The array of query result row types matching .
+ /// The array of query spaces matching .
+ /// The session for which the queries are executed.
+ /// A cancellation token that can be used to cancel the work
+ /// The cached query results, matching each key of respectively. For each
+ /// missed key, it will contain a .
+ public static async Task GetManyAsync(
+ this IQueryCache queryCache,
+ QueryKey[] keys,
+ QueryParameters[] queryParameters,
+ ICacheAssembler[][] returnTypes,
+ ISet[] spaces,
+ ISessionImplementor session, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ if (queryCache is IBatchableQueryCache batchableQueryCache)
+ {
+ return await (batchableQueryCache.GetManyAsync(
+ keys,
+ queryParameters,
+ returnTypes,
+ spaces,
+ session, cancellationToken)).ConfigureAwait(false);
+ }
+
+ var results = new IList[keys.Length];
+ for (var i = 0; i < keys.Length; i++)
+ {
+ results[i] = await (queryCache.GetAsync(keys[i], queryParameters[i], returnTypes[i], spaces[i], session, cancellationToken)).ConfigureAwait(false);
+ }
+
+ return results;
+ }
+
+ ///
+ /// Attempt to cache objects, after loading them from the database.
+ ///
+ /// The cache.
+ /// The query keys.
+ /// The array of query parameters matching .
+ /// The array of query result row types matching .
+ /// The array of query results matching .
+ /// The session for which the queries were executed.
+ /// A cancellation token that can be used to cancel the work
+ /// An array of boolean indicating if each query was successfully cached.
+ ///
+ public static async Task PutManyAsync(
+ this IQueryCache queryCache,
+ QueryKey[] keys,
+ QueryParameters[] queryParameters,
+ ICacheAssembler[][] returnTypes,
+ IList[] results,
+ ISessionImplementor session, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ if (queryCache is IBatchableQueryCache batchableQueryCache)
+ {
+ return await (batchableQueryCache.PutManyAsync(
+ keys,
+ queryParameters,
+ returnTypes,
+ results,
+ session, cancellationToken)).ConfigureAwait(false);
+ }
+
+ var puts = new bool[keys.Length];
+ for (var i = 0; i < keys.Length; i++)
+ {
+ puts[i] = await (queryCache.PutAsync(keys[i], queryParameters[i], returnTypes[i], results[i], session, cancellationToken)).ConfigureAwait(false);
+ }
+
+ return puts;
+ }
+ }
}
diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs
index a85a0916311..81a3dfabe92 100644
--- a/src/NHibernate/Async/Cache/StandardQueryCache.cs
+++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs
@@ -11,7 +11,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
-
+using System.Linq;
using NHibernate.Cfg;
using NHibernate.Engine;
using NHibernate.Type;
@@ -21,7 +21,7 @@ namespace NHibernate.Cache
{
using System.Threading.Tasks;
using System.Threading;
- public partial class StandardQueryCache : IQueryCache
+ public partial class StandardQueryCache : IQueryCache, IBatchableQueryCache
{
#region IQueryCache Members
@@ -35,35 +35,73 @@ public Task ClearAsync(CancellationToken cancellationToken)
return _queryCache.ClearAsync(cancellationToken);
}
+ ///
+ public Task PutAsync(
+ QueryKey key,
+ QueryParameters queryParameters,
+ ICacheAssembler[] returnTypes,
+ IList result,
+ ISessionImplementor session, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ // 6.0 TODO: inline the call.
+#pragma warning disable 612
+ return PutAsync(key, returnTypes, result, queryParameters.NaturalKeyLookup, session, cancellationToken);
+#pragma warning restore 612
+ }
+
+ // Since 5.2
+ [Obsolete]
public async Task PutAsync(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool isNaturalKeyLookup, ISessionImplementor session, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (isNaturalKeyLookup && result.Count == 0)
return false;
- long ts = session.Factory.Settings.CacheProvider.NextTimestamp();
+ var ts = session.Factory.Settings.CacheProvider.NextTimestamp();
- if (Log.IsDebugEnabled())
- Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key);
-
- IList cacheable = new List