diff --git a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs index 3fe00c5e385..94ebca5e031 100644 --- a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs @@ -1588,6 +1588,54 @@ public async Task CollectionLazyInitializationFromCacheIsBatchedAsync() Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); } + [Test] + public async Task CollectionLazyInitializationFromCacheIsBatched_FillCacheByQueryCacheAsync() + { + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + int id; + using (var s = OpenSession()) + { + id = await (s.Query().Select(x => x.Id).FirstAsync()); + var readOnly = (await (s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToListAsync())) + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + using (var s = OpenSession()) + { + var readOnly = (await (s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToListAsync())) + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + + + using (var s = OpenSession()) + { + var readOnly = await (s.GetAsync(id)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + // 6 items with batch-size = 4 so 2 GetMany calls are expected 1st call: 4 items + 2nd call: 2 items + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); + } + private async Task AssertMultipleCacheCallsAsync(IEnumerable loadIds, IReadOnlyList getIds, int idIndex, int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken)) where TEntity : CacheEntity diff --git a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs index a18e7b616ca..d3f06568cd6 100644 --- a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs @@ -1576,6 +1576,54 @@ public void CollectionLazyInitializationFromCacheIsBatched() Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); } + [Test] + public void CollectionLazyInitializationFromCacheIsBatched_FillCacheByQueryCache() + { + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + int id; + using (var s = OpenSession()) + { + id = s.Query().Select(x => x.Id).First(); + var readOnly = s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToList() + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + using (var s = OpenSession()) + { + var readOnly = s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToList() + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + + + using (var s = OpenSession()) + { + var readOnly = s.Get(id); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + // 6 items with batch-size = 4 so 2 GetMany calls are expected 1st call: 4 items + 2nd call: 2 items + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); + } + private void AssertMultipleCacheCalls(IEnumerable loadIds, IReadOnlyList getIds, int idIndex, int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null) where TEntity : CacheEntity diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs index 7ffa1b41fb9..687700d44fd 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs @@ -111,10 +111,7 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist BeforeInitialize(persister, size); var elementType = persister.ElementType; - for (int i = 0; i < size; i++) - { - await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); - } + await (BeforeAssembleAsync(elementType, array, cancellationToken)).ConfigureAwait(false); for (var i = 0; i < size; i++) { @@ -126,6 +123,18 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist } } + private async Task BeforeAssembleAsync(IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override Task NeedsInsertingAsync(object entry, int i, IType elemType, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs index 1b8a8f57502..cb5c7bcc41b 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -47,11 +47,7 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist var identifierType = persister.IdentifierType; var elementType = persister.ElementType; - for (int i = 0; i < size; i += 2) - { - await (identifierType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); - await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); - } + await (BeforeAssembleAsync(identifierType, elementType, array, cancellationToken)).ConfigureAwait(false); for (int i = 0; i < size; i += 2) { @@ -60,6 +56,19 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist } } + private async Task BeforeAssembleAsync(IType identifierType, IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + await (identifierType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs index 6ac827ecfa0..626fe414d3c 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs @@ -100,10 +100,7 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist BeforeInitialize(persister, size); var elementType = persister.ElementType; - for (int i = 0; i < size; i++) - { - await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); - } + await (BeforeAssembleAsync(elementType, array, cancellationToken)).ConfigureAwait(false); for (int i = 0; i < size; i++) { @@ -112,6 +109,18 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist } } + private async Task BeforeAssembleAsync(IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs index 3348087744a..18526b4d09d 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs @@ -97,11 +97,7 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist var indexType = persister.IndexType; var elementType = persister.ElementType; - for (int i = 0; i < size; i += 2) - { - await (indexType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); - await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); - } + await (BeforeAssembleAsync(indexType, elementType, array, cancellationToken)).ConfigureAwait(false); for (int i = 0; i < size; i += 2) { @@ -110,6 +106,19 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist } } + private async Task BeforeAssembleAsync(IType indexType, IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + await (indexType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs index c567ea44d86..848e918ef5e 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs @@ -86,10 +86,7 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist BeforeInitialize(persister, size); var elementType = persister.ElementType; - for (int i = 0; i < size; i++) - { - await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); - } + await (BeforeAssembleAsync(elementType, array, cancellationToken)).ConfigureAwait(false); for (int i = 0; i < size; i++) { @@ -102,6 +99,18 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist SetInitialized(); } + private async Task BeforeAssembleAsync(IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override async Task ReadFromAsync(DbDataReader rs, ICollectionPersister role, ICollectionAliases descriptor, object owner, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs index a08d2e6e96e..6fb58819c4d 100644 --- a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs @@ -95,14 +95,23 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist array = System.Array.CreateInstance(persister.ElementClass, cached.Length); var elementType = persister.ElementType; + await (BeforeAssembleAsync(elementType, cached, cancellationToken)).ConfigureAwait(false); + for (int i = 0; i < cached.Length; i++) { - await (elementType.BeforeAssembleAsync(cached[i], Session, cancellationToken)).ConfigureAwait(false); + array.SetValue(await (elementType.AssembleAsync(cached[i], Session, owner, cancellationToken)).ConfigureAwait(false), i); } + } + + private async Task BeforeAssembleAsync(IType elementType, object[] cached, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; for (int i = 0; i < cached.Length; i++) { - array.SetValue(await (elementType.AssembleAsync(cached[i], Session, owner, cancellationToken)).ConfigureAwait(false), i); + await (elementType.BeforeAssembleAsync(cached[i], Session, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs index 6530936f613..74740304845 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs @@ -402,10 +402,7 @@ public override void InitializeFromCache(ICollectionPersister persister, object BeforeInitialize(persister, size); var elementType = persister.ElementType; - for (int i = 0; i < size; i++) - { - elementType.BeforeAssemble(array[i], Session); - } + BeforeAssemble(elementType, array); for (var i = 0; i < size; i++) { @@ -417,6 +414,17 @@ public override void InitializeFromCache(ICollectionPersister persister, object } } + private void BeforeAssemble(IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + } + public override bool IsSnapshotEmpty(object snapshot) { return ((ICollection) snapshot).Count == 0; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs index 59769257121..f66980aa8b6 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -78,11 +78,7 @@ public override void InitializeFromCache(ICollectionPersister persister, object var identifierType = persister.IdentifierType; var elementType = persister.ElementType; - for (int i = 0; i < size; i += 2) - { - identifierType.BeforeAssemble(array[i], Session); - elementType.BeforeAssemble(array[i + 1], Session); - } + BeforeAssemble(identifierType, elementType, array); for (int i = 0; i < size; i += 2) { @@ -91,6 +87,18 @@ public override void InitializeFromCache(ICollectionPersister persister, object } } + private void BeforeAssemble(IType identifierType, IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + identifierType.BeforeAssemble(array[i], Session); + elementType.BeforeAssemble(array[i + 1], Session); + } + } + private object GetIdentifier(int index) { // NH specific : To emulate IDictionary behavior but using Dictionary (without boxing/unboxing for index) diff --git a/src/NHibernate/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Collection/Generic/PersistentGenericList.cs index 2c08510c311..c8756cf504b 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericList.cs @@ -162,10 +162,7 @@ public override void InitializeFromCache(ICollectionPersister persister, object BeforeInitialize(persister, size); var elementType = persister.ElementType; - for (int i = 0; i < size; i++) - { - elementType.BeforeAssemble(array[i], Session); - } + BeforeAssemble(elementType, array); for (int i = 0; i < size; i++) { @@ -174,6 +171,17 @@ public override void InitializeFromCache(ICollectionPersister persister, object } } + private void BeforeAssemble(IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + } + public override object Disassemble(ICollectionPersister persister) { int length = WrappedList.Count; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs index 24c4c30568e..da27836713b 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs @@ -166,11 +166,7 @@ public override void InitializeFromCache(ICollectionPersister persister, object var indexType = persister.IndexType; var elementType = persister.ElementType; - for (int i = 0; i < size; i += 2) - { - indexType.BeforeAssemble(array[i], Session); - elementType.BeforeAssemble(array[i + 1], Session); - } + BeforeAssemble(indexType, elementType, array); for (int i = 0; i < size; i += 2) { @@ -179,6 +175,18 @@ public override void InitializeFromCache(ICollectionPersister persister, object } } + private void BeforeAssemble(IType indexType, IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + indexType.BeforeAssemble(array[i], Session); + elementType.BeforeAssemble(array[i + 1], Session); + } + } + public override object Disassemble(ICollectionPersister persister) { object[] result = new object[WrappedMap.Count * 2]; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs index f2736a8d21c..9a686724e41 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs @@ -156,10 +156,7 @@ public override void InitializeFromCache(ICollectionPersister persister, object BeforeInitialize(persister, size); var elementType = persister.ElementType; - for (int i = 0; i < size; i++) - { - elementType.BeforeAssemble(array[i], Session); - } + BeforeAssemble(elementType, array); for (int i = 0; i < size; i++) { @@ -172,6 +169,17 @@ public override void InitializeFromCache(ICollectionPersister persister, object SetInitialized(); } + private void BeforeAssemble(IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + } + public override bool Empty { get { return WrappedSet.Count == 0; } diff --git a/src/NHibernate/Collection/PersistentArrayHolder.cs b/src/NHibernate/Collection/PersistentArrayHolder.cs index 1bd42dc42ce..aaa8ab2625d 100644 --- a/src/NHibernate/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Collection/PersistentArrayHolder.cs @@ -197,14 +197,22 @@ public override void InitializeFromCache(ICollectionPersister persister, object array = System.Array.CreateInstance(persister.ElementClass, cached.Length); var elementType = persister.ElementType; + BeforeAssemble(elementType, cached); + for (int i = 0; i < cached.Length; i++) { - elementType.BeforeAssemble(cached[i], Session); + array.SetValue(elementType.Assemble(cached[i], Session, owner), i); } + } + + private void BeforeAssemble(IType elementType, object[] cached) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; for (int i = 0; i < cached.Length; i++) { - array.SetValue(elementType.Assemble(cached[i], Session, owner), i); + elementType.BeforeAssemble(cached[i], Session); } }