From eaf97821757a66de24b406e581232d195dd6334f Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 12 Jun 2019 10:24:16 +0300 Subject: [PATCH 1/6] Unexpected query for Future with non-lazy association --- .../Async/Futures/QueryBatchFixture.cs | 39 +++++++++++++++++++ src/NHibernate.Test/Futures/Entities.cs | 7 ++++ .../Futures/QueryBatchFixture.cs | 39 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs index 2fe7614ce42..268988df5cc 100644 --- a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs @@ -498,6 +498,33 @@ public async Task CacheModeWorksWithFutureAsync() } } + //GH-2173 + [Test] + public async Task CanFetchNonLazyEntitiesInSubsequentQueryAsync() + { + Sfi.Statistics.IsStatisticsEnabled = true; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.SaveAsync( + new EntityEager + { + Name = "EagerManyToOneAssociation", + EagerEntity = new EntityEagerChild {Name = "association"} + })); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + Sfi.Statistics.Clear(); + //EntityEager.EagerEntity is lazy initialized instead of being loaded by the second query + s.QueryOver().Fetch(SelectMode.Skip, x => x.EagerEntity).Future(); + s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().ToList(); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + } + } + #region Test Setup protected override HbmMapping GetMappings() @@ -543,6 +570,10 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); + rc.ManyToOne(x => x.EagerEntity, m => + { + m.Cascade(Mapping.ByCode.Cascade.Persist); + }); rc.Bag(ep => ep.ChildrenListSubselect, m => { @@ -560,6 +591,14 @@ protected override HbmMapping GetMappings() }, a => a.OneToMany()); }); + mapper.Class( + rc => + { + rc.Lazy(false); + + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); mapper.Class( rc => { diff --git a/src/NHibernate.Test/Futures/Entities.cs b/src/NHibernate.Test/Futures/Entities.cs index 3c866f497fd..c68a0574850 100644 --- a/src/NHibernate.Test/Futures/Entities.cs +++ b/src/NHibernate.Test/Futures/Entities.cs @@ -36,11 +36,18 @@ public class EntitySubselectChild public virtual EntityEager Parent { get; set; } } + public class EntityEagerChild + { + public Guid Id { get; set; } + public string Name { get; set; } + } + public class EntityEager { public Guid Id { get; set; } public string Name { get; set; } + public EntityEagerChild EagerEntity { get; set; } public IList ChildrenListSubselect { get; set; } public IList ChildrenListEager { get; set; } //= new HashSet(); } diff --git a/src/NHibernate.Test/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Futures/QueryBatchFixture.cs index cb79faedae1..baa3ca2cdff 100644 --- a/src/NHibernate.Test/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Futures/QueryBatchFixture.cs @@ -486,6 +486,33 @@ public void CacheModeWorksWithFuture() } } + //GH-2173 + [Test] + public void CanFetchNonLazyEntitiesInSubsequentQuery() + { + Sfi.Statistics.IsStatisticsEnabled = true; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save( + new EntityEager + { + Name = "EagerManyToOneAssociation", + EagerEntity = new EntityEagerChild {Name = "association"} + }); + t.Commit(); + } + + using (var s = OpenSession()) + { + Sfi.Statistics.Clear(); + //EntityEager.EagerEntity is lazy initialized instead of being loaded by the second query + s.QueryOver().Fetch(SelectMode.Skip, x => x.EagerEntity).Future(); + s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().ToList(); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + } + } + #region Test Setup protected override HbmMapping GetMappings() @@ -531,6 +558,10 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); + rc.ManyToOne(x => x.EagerEntity, m => + { + m.Cascade(Mapping.ByCode.Cascade.Persist); + }); rc.Bag(ep => ep.ChildrenListSubselect, m => { @@ -548,6 +579,14 @@ protected override HbmMapping GetMappings() }, a => a.OneToMany()); }); + mapper.Class( + rc => + { + rc.Lazy(false); + + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); mapper.Class( rc => { From 3f1f025db11b13af3000032bf9ea7df658f84da1 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 12 Jun 2019 12:44:50 +0300 Subject: [PATCH 2/6] Move InitializeEntitiesAndCollections to ProcessResults --- src/AsyncGenerator.yml | 4 ++++ .../Async/Multi/QueryBatchItemBase.cs | 19 +++---------------- .../Engine/Loading/CollectionLoadContext.cs | 14 ++++++++++++++ .../Engine/Loading/LoadingCollectionEntry.cs | 2 ++ src/NHibernate/Loader/Loader.cs | 9 +++++++++ src/NHibernate/Multi/QueryBatchItemBase.cs | 19 +++++++++++++++++-- 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml index 382d07d44c9..b12437b419c 100644 --- a/src/AsyncGenerator.yml +++ b/src/AsyncGenerator.yml @@ -5,6 +5,10 @@ applyChanges: true analyzation: methodConversion: +#TODO 6.0: Remove ignore rule for IQueryBatchItem.ProcessResults + - conversion: Ignore + name: ProcessResults + containingTypeName: IQueryBatchItem - conversion: Ignore name: PostProcessInsert containingTypeName: HqlSqlWalker diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index 47e7bf0a952..3f907699353 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -108,8 +108,9 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT await (reader.NextResultAsync(cancellationToken)).ConfigureAwait(false); } - await (InitializeEntitiesAndCollectionsAsync(reader, hydratedObjects, cancellationToken)).ConfigureAwait(false); - + StopLoadingCollections(reader); + _reader = reader; + _hydratedObjects = hydratedObjects; return rowCount; } } @@ -123,19 +124,5 @@ public async Task ExecuteNonBatchedAsync(CancellationToken cancellationToken) } protected abstract Task> GetResultsNonBatchedAsync(CancellationToken cancellationToken); - - private async Task InitializeEntitiesAndCollectionsAsync(DbDataReader reader, List[] hydratedObjects, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - for (var i = 0; i < _queryInfos.Count; i++) - { - var queryInfo = _queryInfos[i]; - if (queryInfo.IsResultFromCache) - continue; - await (queryInfo.Loader.InitializeEntitiesAndCollectionsAsync( - hydratedObjects[i], reader, Session, queryInfo.Parameters.IsReadOnly(Session), - queryInfo.CacheBatcher, cancellationToken)).ConfigureAwait(false); - } - } } } diff --git a/src/NHibernate/Engine/Loading/CollectionLoadContext.cs b/src/NHibernate/Engine/Loading/CollectionLoadContext.cs index e41b0f9f404..4dded16d234 100644 --- a/src/NHibernate/Engine/Loading/CollectionLoadContext.cs +++ b/src/NHibernate/Engine/Loading/CollectionLoadContext.cs @@ -124,6 +124,8 @@ public IPersistentCollection GetLoadingCollection(ICollectionPersister persister } else { + if (loadingCollectionEntry.StopLoading) + return null; if (loadingCollectionEntry.ResultSet == resultSet) { log.Debug("found loading collection bound to current result set processing; reading row"); @@ -398,5 +400,17 @@ public override string ToString() { return base.ToString() + ""; } + + internal void StopLoadingCollections(ICollectionPersister[] collectionPersisters) + { + foreach (var collectionKey in localLoadingCollectionKeys) + { + var loadingCollectionEntry = LoadContext.LocateLoadingCollectionEntry(collectionKey); + if (loadingCollectionEntry != null && Array.IndexOf(collectionPersisters, loadingCollectionEntry.Persister) >= 0) + { + loadingCollectionEntry.StopLoading = true; + } + } + } } } diff --git a/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs b/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs index 992abf1ae5e..78b0260552a 100644 --- a/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs +++ b/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs @@ -44,6 +44,8 @@ public IPersistentCollection Collection get { return collection; } } + public bool StopLoading { get; set; } + public override string ToString() { return GetType().FullName + "@" + Convert.ToString(GetHashCode(), 16); diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index d50d0200dcc..7fc84bf3240 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -672,6 +672,15 @@ internal void InitializeEntitiesAndCollections( } } + internal void StopLoadingCollections(ISessionImplementor session, DbDataReader reader) + { + var collectionPersisters = CollectionPersisters; + if (collectionPersisters == null || collectionPersisters.Length == 0) + return; + + session.PersistenceContext.LoadContexts.GetCollectionLoadContext(reader).StopLoadingCollections(collectionPersisters); + } + private void EndCollectionLoad(DbDataReader reader, ISessionImplementor session, ICollectionPersister collectionPersister) { //this is a query and we are loading multiple instances of the same collection role diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index 273070d3f15..67a9c81d3d2 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -21,6 +21,8 @@ public abstract partial class QueryBatchItemBase : IQueryBatchItem _queryInfos; private CacheMode? _cacheMode; private IList _finalResults; + private DbDataReader _reader; + private List[] _hydratedObjects; protected class QueryInfo : ICachingInformation { @@ -247,8 +249,9 @@ public int ProcessResultsSet(DbDataReader reader) reader.NextResult(); } - InitializeEntitiesAndCollections(reader, hydratedObjects); - + StopLoadingCollections(reader); + _reader = reader; + _hydratedObjects = hydratedObjects; return rowCount; } } @@ -258,6 +261,7 @@ public void ProcessResults() { ThrowIfNotInitialized(); + InitializeEntitiesAndCollections(_reader, _hydratedObjects); for (var i = 0; i < _queryInfos.Count; i++) { var queryInfo = _queryInfos[i]; @@ -329,6 +333,17 @@ private void InitializeEntitiesAndCollections(DbDataReader reader, List[ } } + private void StopLoadingCollections(DbDataReader reader) + { + for (var i = 0; i < _queryInfos.Count; i++) + { + var queryInfo = _queryInfos[i]; + if (queryInfo.IsResultFromCache) + continue; + queryInfo.Loader.StopLoadingCollections(Session, reader); + } + } + private void ThrowIfNotInitialized() { if (_queryInfos == null) From e509920d6655f17f5a36b34388e9803bfb2264cc Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 12 Jun 2019 14:47:26 +0300 Subject: [PATCH 3/6] async QueryBatchItemBase.ProcessResults --- src/NHibernate/Async/Multi/IQueryBatchItem.cs | 5 +++ src/NHibernate/Async/Multi/QueryBatch.cs | 6 ++- .../Async/Multi/QueryBatchItemBase.cs | 42 ++++++++++++++++++- src/NHibernate/Multi/IQueryBatchItem.cs | 6 +++ src/NHibernate/Multi/QueryBatch.cs | 6 ++- src/NHibernate/Multi/QueryBatchItemBase.cs | 4 +- 6 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/NHibernate/Async/Multi/IQueryBatchItem.cs b/src/NHibernate/Async/Multi/IQueryBatchItem.cs index 257f6673964..c8e4fa5a2a1 100644 --- a/src/NHibernate/Async/Multi/IQueryBatchItem.cs +++ b/src/NHibernate/Async/Multi/IQueryBatchItem.cs @@ -36,4 +36,9 @@ public partial interface IQueryBatchItem /// A cancellation token that can be used to cancel the work Task ExecuteNonBatchedAsync(CancellationToken cancellationToken); } + + internal partial interface IQueryBatchItemWithAsyncProcessResults + { + Task ProcessResultsAsync(CancellationToken cancellationToken); + } } diff --git a/src/NHibernate/Async/Multi/QueryBatch.cs b/src/NHibernate/Async/Multi/QueryBatch.cs index e1faf9c5b47..ae8c329fa53 100644 --- a/src/NHibernate/Async/Multi/QueryBatch.cs +++ b/src/NHibernate/Async/Multi/QueryBatch.cs @@ -156,7 +156,11 @@ protected async Task ExecuteBatchedAsync(CancellationToken cancellationToken) foreach (var query in _queries) { - query.ProcessResults(); + //TODO 6.0: Replace with query.ProcessResults(); + if (query is IQueryBatchItemWithAsyncProcessResults q) + await (q.ProcessResultsAsync(cancellationToken)).ConfigureAwait(false); + else + query.ProcessResults(); } } catch (OperationCanceledException) { throw; } diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index 3f907699353..caedd2c88cf 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -23,7 +23,7 @@ namespace NHibernate.Multi { using System.Threading.Tasks; using System.Threading; - public abstract partial class QueryBatchItemBase : IQueryBatchItem + public abstract partial class QueryBatchItemBase : IQueryBatchItem, IQueryBatchItemWithAsyncProcessResults { /// @@ -115,6 +115,32 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT } } + /// + public async Task ProcessResultsAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfNotInitialized(); + + await (InitializeEntitiesAndCollectionsAsync(_reader, _hydratedObjects, cancellationToken)).ConfigureAwait(false); + for (var i = 0; i < _queryInfos.Count; i++) + { + var queryInfo = _queryInfos[i]; + if (_subselectResultKeys[i] != null) + { + queryInfo.Loader.CreateSubselects(_subselectResultKeys[i], queryInfo.Parameters, Session); + } + + if (queryInfo.IsCacheable) + { + // This transformation must not be applied to ResultToCache. + queryInfo.Result = + queryInfo.Loader.TransformCacheableResults( + queryInfo.Parameters, queryInfo.CacheKey.ResultTransformer, queryInfo.Result); + } + } + AfterLoadCallback?.Invoke(GetResults()); + } + /// public async Task ExecuteNonBatchedAsync(CancellationToken cancellationToken) { @@ -124,5 +150,19 @@ public async Task ExecuteNonBatchedAsync(CancellationToken cancellationToken) } protected abstract Task> GetResultsNonBatchedAsync(CancellationToken cancellationToken); + + private async Task InitializeEntitiesAndCollectionsAsync(DbDataReader reader, List[] hydratedObjects, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + for (var i = 0; i < _queryInfos.Count; i++) + { + var queryInfo = _queryInfos[i]; + if (queryInfo.IsResultFromCache) + continue; + await (queryInfo.Loader.InitializeEntitiesAndCollectionsAsync( + hydratedObjects[i], reader, Session, queryInfo.Parameters.IsReadOnly(Session), + queryInfo.CacheBatcher, cancellationToken)).ConfigureAwait(false); + } + } } } diff --git a/src/NHibernate/Multi/IQueryBatchItem.cs b/src/NHibernate/Multi/IQueryBatchItem.cs index 6e69f1d15f1..0eb11051ae0 100644 --- a/src/NHibernate/Multi/IQueryBatchItem.cs +++ b/src/NHibernate/Multi/IQueryBatchItem.cs @@ -80,4 +80,10 @@ public partial interface IQueryBatchItem /// void ExecuteNonBatched(); } + + //TODO 6.0: Remove along with ignore rule for IQueryBatchItem.ProcessResults in AsyncGenerator.yml + internal partial interface IQueryBatchItemWithAsyncProcessResults + { + void ProcessResults(); + } } diff --git a/src/NHibernate/Multi/QueryBatch.cs b/src/NHibernate/Multi/QueryBatch.cs index ff25cd5076e..101326464dd 100644 --- a/src/NHibernate/Multi/QueryBatch.cs +++ b/src/NHibernate/Multi/QueryBatch.cs @@ -182,7 +182,11 @@ protected void ExecuteBatched() foreach (var query in _queries) { - query.ProcessResults(); + //TODO 6.0: Replace with query.ProcessResults(); + if (query is IQueryBatchItemWithAsyncProcessResults q) + q.ProcessResults(); + else + query.ProcessResults(); } } catch (Exception sqle) diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index 67a9c81d3d2..12fda5f5813 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -14,7 +14,7 @@ namespace NHibernate.Multi /// /// Base class for both ICriteria and IQuery queries /// - public abstract partial class QueryBatchItemBase : IQueryBatchItem + public abstract partial class QueryBatchItemBase : IQueryBatchItem, IQueryBatchItemWithAsyncProcessResults { protected ISessionImplementor Session; private List[] _subselectResultKeys; @@ -256,7 +256,7 @@ public int ProcessResultsSet(DbDataReader reader) } } - /// + /// public void ProcessResults() { ThrowIfNotInitialized(); From 2a02786a5ef475227fa36c4f99d03b92b3633290 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 12 Jun 2019 17:39:29 +0300 Subject: [PATCH 4/6] Handle cacheable queries --- src/NHibernate/Async/Multi/QueryBatch.cs | 21 ++++++++++++------- .../Async/Multi/QueryBatchItemBase.cs | 4 +++- src/NHibernate/Multi/QueryBatch.cs | 13 ++++++------ src/NHibernate/Multi/QueryBatchItemBase.cs | 4 +++- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/NHibernate/Async/Multi/QueryBatch.cs b/src/NHibernate/Async/Multi/QueryBatch.cs index ae8c329fa53..b27f174665e 100644 --- a/src/NHibernate/Async/Multi/QueryBatch.cs +++ b/src/NHibernate/Async/Multi/QueryBatch.cs @@ -127,18 +127,19 @@ protected async Task ExecuteBatchedAsync(CancellationToken cancellationToken) } var rowCount = 0; + CacheBatcher cacheBatcher = null; try { if (resultSetsCommand.HasQueries) { + cacheBatcher = new CacheBatcher(Session); using (var reader = await (resultSetsCommand.GetReaderAsync(Timeout, cancellationToken)).ConfigureAwait(false)) { - var cacheBatcher = new CacheBatcher(Session); foreach (var query in _queries) { if (query.CachingInformation != null) { - foreach (var cachingInfo in query.CachingInformation.Where(ci => ci.IsCacheable)) + foreach (var cachingInfo in query.CachingInformation) { cachingInfo.SetCacheBatcher(cacheBatcher); } @@ -146,14 +147,9 @@ protected async Task ExecuteBatchedAsync(CancellationToken cancellationToken) rowCount += await (query.ProcessResultsSetAsync(reader, cancellationToken)).ConfigureAwait(false); } - await (cacheBatcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false); } } - // Query cacheable results must be cached untransformed: the put does not need to wait for - // the ProcessResults. - await (PutCacheableResultsAsync(cancellationToken)).ConfigureAwait(false); - foreach (var query in _queries) { //TODO 6.0: Replace with query.ProcessResults(); @@ -162,6 +158,17 @@ protected async Task ExecuteBatchedAsync(CancellationToken cancellationToken) else query.ProcessResults(); } + + var executeBatchTask = cacheBatcher?.ExecuteBatchAsync(cancellationToken); + + if (executeBatchTask != null) + + { + + await (executeBatchTask).ConfigureAwait(false); + + } + await (PutCacheableResultsAsync(cancellationToken)).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception sqle) diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index caedd2c88cf..ed37874dc1c 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -121,7 +121,9 @@ public async Task ProcessResultsAsync(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); ThrowIfNotInitialized(); - await (InitializeEntitiesAndCollectionsAsync(_reader, _hydratedObjects, cancellationToken)).ConfigureAwait(false); + using (Session.SwitchCacheMode(_cacheMode)) + await (InitializeEntitiesAndCollectionsAsync(_reader, _hydratedObjects, cancellationToken)).ConfigureAwait(false); + for (var i = 0; i < _queryInfos.Count; i++) { var queryInfo = _queryInfos[i]; diff --git a/src/NHibernate/Multi/QueryBatch.cs b/src/NHibernate/Multi/QueryBatch.cs index 101326464dd..915ac0d02da 100644 --- a/src/NHibernate/Multi/QueryBatch.cs +++ b/src/NHibernate/Multi/QueryBatch.cs @@ -153,18 +153,19 @@ protected void ExecuteBatched() } var rowCount = 0; + CacheBatcher cacheBatcher = null; try { if (resultSetsCommand.HasQueries) { + cacheBatcher = new CacheBatcher(Session); using (var reader = resultSetsCommand.GetReader(Timeout)) { - var cacheBatcher = new CacheBatcher(Session); foreach (var query in _queries) { if (query.CachingInformation != null) { - foreach (var cachingInfo in query.CachingInformation.Where(ci => ci.IsCacheable)) + foreach (var cachingInfo in query.CachingInformation) { cachingInfo.SetCacheBatcher(cacheBatcher); } @@ -172,14 +173,9 @@ protected void ExecuteBatched() rowCount += query.ProcessResultsSet(reader); } - cacheBatcher.ExecuteBatch(); } } - // Query cacheable results must be cached untransformed: the put does not need to wait for - // the ProcessResults. - PutCacheableResults(); - foreach (var query in _queries) { //TODO 6.0: Replace with query.ProcessResults(); @@ -188,6 +184,9 @@ protected void ExecuteBatched() else query.ProcessResults(); } + + cacheBatcher?.ExecuteBatch(); + PutCacheableResults(); } catch (Exception sqle) { diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index 12fda5f5813..f25e3ed7498 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -261,7 +261,9 @@ public void ProcessResults() { ThrowIfNotInitialized(); - InitializeEntitiesAndCollections(_reader, _hydratedObjects); + using (Session.SwitchCacheMode(_cacheMode)) + InitializeEntitiesAndCollections(_reader, _hydratedObjects); + for (var i = 0; i < _queryInfos.Count; i++) { var queryInfo = _queryInfos[i]; From 5e393406d7775bbbbb735a1de2e31a2eed5c3a12 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 12 Jun 2019 19:01:43 +0300 Subject: [PATCH 5/6] Tests adjustments and commented --- src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs | 6 ++++-- src/NHibernate.Test/Futures/QueryBatchFixture.cs | 6 ++++-- src/NHibernate/Loader/Loader.cs | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs index 268988df5cc..1c4a39aae97 100644 --- a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs @@ -520,8 +520,10 @@ public async Task CanFetchNonLazyEntitiesInSubsequentQueryAsync() Sfi.Statistics.Clear(); //EntityEager.EagerEntity is lazy initialized instead of being loaded by the second query s.QueryOver().Fetch(SelectMode.Skip, x => x.EagerEntity).Future(); - s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().ToList(); - Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + await (s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().GetEnumerableAsync()); + + if(SupportsMultipleQueries) + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); } } diff --git a/src/NHibernate.Test/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Futures/QueryBatchFixture.cs index baa3ca2cdff..0f959a54559 100644 --- a/src/NHibernate.Test/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Futures/QueryBatchFixture.cs @@ -508,8 +508,10 @@ public void CanFetchNonLazyEntitiesInSubsequentQuery() Sfi.Statistics.Clear(); //EntityEager.EagerEntity is lazy initialized instead of being loaded by the second query s.QueryOver().Fetch(SelectMode.Skip, x => x.EagerEntity).Future(); - s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().ToList(); - Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().GetEnumerable(); + + if(SupportsMultipleQueries) + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); } } diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 7fc84bf3240..7b3b79fc12a 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -672,6 +672,9 @@ internal void InitializeEntitiesAndCollections( } } + /// + /// Stops further collection population without actual collection initialization. + /// internal void StopLoadingCollections(ISessionImplementor session, DbDataReader reader) { var collectionPersisters = CollectionPersisters; From e96b9c56c4ed2b9e232ea5ca0226e21f4ced55a4 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 13 Jun 2019 15:03:40 +0300 Subject: [PATCH 6/6] 5.2 specific fix --- src/NHibernate/Async/Multi/QueryBatchItemBase.cs | 2 +- src/NHibernate/Multi/QueryBatchItemBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index ed37874dc1c..cd144051e0f 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -103,7 +103,7 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT queryInfo.Result = tmpResults; if (queryInfo.CanPutToCache) - queryInfo.ResultToCache = tmpResults; + queryInfo.ResultToCache = new List(tmpResults); await (reader.NextResultAsync(cancellationToken)).ConfigureAwait(false); } diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index f25e3ed7498..fa33182327e 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -244,7 +244,7 @@ public int ProcessResultsSet(DbDataReader reader) queryInfo.Result = tmpResults; if (queryInfo.CanPutToCache) - queryInfo.ResultToCache = tmpResults; + queryInfo.ResultToCache = new List(tmpResults); reader.NextResult(); }