diff --git a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs index c3c2195688a..3fe00c5e385 100644 --- a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs @@ -1565,8 +1565,31 @@ public async Task QueryFetchEntityBatchCacheTestAsync(bool clearEntityCacheAfter Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); } + [Test] + public async Task CollectionLazyInitializationFromCacheIsBatchedAsync() + { + using (var s = OpenSession()) + { + var readOnly = await (s.GetAsync(await (s.Query().Select(x => x.Id).FirstAsync()))); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + + using (var s = OpenSession()) + { + var readOnly = await (s.GetAsync(await (s.Query().Select(x => x.Id).FirstAsync()))); + 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)) + int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken)) where TEntity : CacheEntity { var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName); diff --git a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs index 150369306b2..a18e7b616ca 100644 --- a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs @@ -1553,8 +1553,31 @@ public void QueryFetchEntityBatchCacheTest(bool clearEntityCacheAfterQuery, bool Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); } + [Test] + public void CollectionLazyInitializationFromCacheIsBatched() + { + using (var s = OpenSession()) + { + var readOnly = s.Get(s.Query().Select(x => x.Id).First()); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + + using (var s = OpenSession()) + { + var readOnly = s.Get(s.Query().Select(x => x.Id).First()); + 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) + int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null) where TEntity : CacheEntity { var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName); diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs index 3b229ac54b8..7ffa1b41fb9 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs @@ -109,9 +109,16 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist var array = (object[]) disassembled; var size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + for (var i = 0; i < size; i++) { - var element = await (persister.ElementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + var element = await (elementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); if (element != null) { _gbag.Add((T) element); diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs index 6e3ea111d01..af37fb8a1e0 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -44,10 +44,19 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var identifierType = persister.IdentifierType; + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + await (identifierType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); + } + for (int i = 0; i < size; i += 2) { - _identifiers[i / 2] = await (persister.IdentifierType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); - _values.Add((T) await (persister.ElementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false)); + _identifiers[i / 2] = await (identifierType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + _values.Add((T) await (elementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false)); } } diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs index 1417c98e361..6ac827ecfa0 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs @@ -98,9 +98,16 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; for (int i = 0; i < size; i++) { - var element = await (persister.ElementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + + for (int i = 0; i < size; i++) + { + var element = await (elementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); WrappedList.Add((T) (element ?? DefaultForType)); } } diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs index e574577a111..fbe6e30c490 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs @@ -94,10 +94,19 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var indexType = persister.IndexType; + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + await (indexType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); + } + for (int i = 0; i < size; i += 2) { - WrappedMap[(TKey)await (persister.IndexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = - (TValue)await (persister.ElementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false); + WrappedMap[(TKey)await (indexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = + (TValue)await (elementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs index 91f2f86f626..c567ea44d86 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs @@ -84,9 +84,16 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist var array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + for (int i = 0; i < size; i++) { - var element = await (persister.ElementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + var element = await (elementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); if (element != null) { WrappedSet.Add((T) element); diff --git a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs index 448309f2f9e..a08d2e6e96e 100644 --- a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs @@ -94,9 +94,15 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist array = System.Array.CreateInstance(persister.ElementClass, cached.Length); + var elementType = persister.ElementType; for (int i = 0; i < cached.Length; i++) { - array.SetValue(await (persister.ElementType.AssembleAsync(cached[i], Session, owner, cancellationToken)).ConfigureAwait(false), i); + await (elementType.BeforeAssembleAsync(cached[i], Session, cancellationToken)).ConfigureAwait(false); + } + + for (int i = 0; i < cached.Length; i++) + { + array.SetValue(await (elementType.AssembleAsync(cached[i], Session, owner, cancellationToken)).ConfigureAwait(false), i); } } diff --git a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs index 98c916717c3..6530936f613 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs @@ -400,9 +400,16 @@ public override void InitializeFromCache(ICollectionPersister persister, object var array = (object[]) disassembled; var size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + for (var i = 0; i < size; i++) { - var element = persister.ElementType.Assemble(array[i], Session, owner); + var element = elementType.Assemble(array[i], Session, owner); if (element != null) { _gbag.Add((T) element); diff --git a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs index 884d454b76e..71005049ad8 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -75,10 +75,19 @@ public override void InitializeFromCache(ICollectionPersister persister, object object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var identifierType = persister.IdentifierType; + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + identifierType.BeforeAssemble(array[i], Session); + elementType.BeforeAssemble(array[i + 1], Session); + } + for (int i = 0; i < size; i += 2) { - _identifiers[i / 2] = persister.IdentifierType.Assemble(array[i], Session, owner); - _values.Add((T) persister.ElementType.Assemble(array[i + 1], Session, owner)); + _identifiers[i / 2] = identifierType.Assemble(array[i], Session, owner); + _values.Add((T) elementType.Assemble(array[i + 1], Session, owner)); } } diff --git a/src/NHibernate/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Collection/Generic/PersistentGenericList.cs index 6255616dd0e..2c08510c311 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericList.cs @@ -160,9 +160,16 @@ public override void InitializeFromCache(ICollectionPersister persister, object object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; for (int i = 0; i < size; i++) { - var element = persister.ElementType.Assemble(array[i], Session, owner); + elementType.BeforeAssemble(array[i], Session); + } + + for (int i = 0; i < size; i++) + { + var element = elementType.Assemble(array[i], Session, owner); WrappedList.Add((T) (element ?? DefaultForType)); } } diff --git a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs index 5fab797d793..e81aee07578 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs @@ -163,10 +163,19 @@ public override void InitializeFromCache(ICollectionPersister persister, object object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var indexType = persister.IndexType; + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + indexType.BeforeAssemble(array[i], Session); + elementType.BeforeAssemble(array[i + 1], Session); + } + for (int i = 0; i < size; i += 2) { - WrappedMap[(TKey)persister.IndexType.Assemble(array[i], Session, owner)] = - (TValue)persister.ElementType.Assemble(array[i + 1], Session, owner); + WrappedMap[(TKey)indexType.Assemble(array[i], Session, owner)] = + (TValue)elementType.Assemble(array[i + 1], Session, owner); } } @@ -246,8 +255,9 @@ public void Add(TKey key, TValue value) { if (key == null) { - throw new ArgumentNullException("key"); + throw new ArgumentNullException(nameof(key)); } + if (PutQueueEnabled) { var found = TryReadElementByKey(key, out _, out _); diff --git a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs index 4e138bb22fa..f2736a8d21c 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs @@ -154,9 +154,16 @@ public override void InitializeFromCache(ICollectionPersister persister, object var array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + for (int i = 0; i < size; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + for (int i = 0; i < size; i++) { - var element = persister.ElementType.Assemble(array[i], Session, owner); + var element = elementType.Assemble(array[i], Session, owner); if (element != null) { WrappedSet.Add((T) element); diff --git a/src/NHibernate/Collection/PersistentArrayHolder.cs b/src/NHibernate/Collection/PersistentArrayHolder.cs index 242d7dac51a..1bd42dc42ce 100644 --- a/src/NHibernate/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Collection/PersistentArrayHolder.cs @@ -196,9 +196,15 @@ public override void InitializeFromCache(ICollectionPersister persister, object array = System.Array.CreateInstance(persister.ElementClass, cached.Length); + var elementType = persister.ElementType; for (int i = 0; i < cached.Length; i++) { - array.SetValue(persister.ElementType.Assemble(cached[i], Session, owner), i); + elementType.BeforeAssemble(cached[i], Session); + } + + for (int i = 0; i < cached.Length; i++) + { + array.SetValue(elementType.Assemble(cached[i], Session, owner), i); } }