From 5905e7d3dc7661065daacff95982bbd9cd541713 Mon Sep 17 00:00:00 2001 From: maca88 Date: Wed, 2 Jan 2019 05:08:18 +0100 Subject: [PATCH 1/6] Add support for caching fetched relations with linq query provider --- src/AsyncGenerator.yml | 3 + .../Async/Linq/QueryCacheableTests.cs | 136 +++++++++++++++++ .../Linq/QueryCacheableTests.cs | 136 +++++++++++++++++ .../Async/Cache/StandardQueryCache.cs | 24 +-- .../Async/Loader/Hql/QueryLoader.cs | 1 + src/NHibernate/Async/Loader/Loader.cs | 76 +++++++--- src/NHibernate/Async/Multi/QueryBatch.cs | 18 ++- .../Async/Multi/QueryBatchItemBase.cs | 4 +- .../Cache/QueryCacheResultBuilder.cs | 139 ++++++++++++++++++ src/NHibernate/Cache/StandardQueryCache.cs | 24 +-- src/NHibernate/Loader/Hql/QueryLoader.cs | 20 +++ src/NHibernate/Loader/Loader.cs | 83 ++++++++--- src/NHibernate/Multi/ICachingInformation.cs | 23 +++ src/NHibernate/Multi/QueryBatch.cs | 18 ++- src/NHibernate/Multi/QueryBatchItemBase.cs | 10 +- 15 files changed, 626 insertions(+), 89 deletions(-) create mode 100644 src/NHibernate/Cache/QueryCacheResultBuilder.cs diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml index 382d07d44c9..dc5c250544c 100644 --- a/src/AsyncGenerator.yml +++ b/src/AsyncGenerator.yml @@ -104,6 +104,9 @@ - conversion: Ignore name: GetEnumerator containingTypeName: IFutureEnumerable + # 6.0 TODO: Remove QueryCacheResultBuilder from ignore + - conversion: Ignore + containingTypeName: QueryCacheResultBuilder - conversion: ToAsync name: ExecuteReader containingTypeName: IBatcher diff --git a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index 884f2b9d0c4..8a91aea24ff 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -10,6 +10,7 @@ using System.Linq; using NHibernate.Cfg; +using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Linq; using NUnit.Framework; @@ -281,5 +282,140 @@ public async Task CanBeCombinedWithFetchAsync() Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "Unexpected cache put count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); } + + [Test] + public async Task FetchIsCachableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + Order order; + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + order = (await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToListAsync())) + .First(); + + await (t.CommitAsync()); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + order = (await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToListAsync())) + .First(); + await (t.CommitAsync()); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + + } + + [Test] + public async Task FutureFetchIsCachableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; + + Order order; + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .Where(x => x.OrderId == 10248) + .ToFuture(); + + order = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToFuture() + .ToList() + .First(); + + await (t.CommitAsync()); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(multiQueries ? 1 : 2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(2), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .Where(x => x.OrderId == 10248) + .ToFuture(); + + order = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToFuture() + .ToList() + .First(); + + await (t.CommitAsync()); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); + } + + private static void AssertFetchedOrder(Order order) + { + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items"); + var orderLine = order.OrderLines.First(); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + } } } diff --git a/src/NHibernate.Test/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Linq/QueryCacheableTests.cs index b4c9babf20e..93f41608fee 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -1,5 +1,6 @@ using System.Linq; using NHibernate.Cfg; +using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Linq; using NUnit.Framework; @@ -270,5 +271,140 @@ public void CanBeCombinedWithFetch() Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "Unexpected cache put count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); } + + [Test] + public void FetchIsCachable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + Order order; + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + order = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToList() + .First(); + + t.Commit(); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + order = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToList() + .First(); + t.Commit(); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + + } + + [Test] + public void FutureFetchIsCachable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; + + Order order; + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .Where(x => x.OrderId == 10248) + .ToFuture(); + + order = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToFuture() + .ToList() + .First(); + + t.Commit(); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(multiQueries ? 1 : 2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(2), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(x => x.Customer) + .Where(x => x.OrderId == 10248) + .ToFuture(); + + order = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(x => x.OrderLines) + .ThenFetch(x => x.Product) + .ThenFetchMany(x => x.OrderLines) + .Where(x => x.OrderId == 10248) + .ToFuture() + .ToList() + .First(); + + t.Commit(); + } + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); + } + + private static void AssertFetchedOrder(Order order) + { + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items"); + var orderLine = order.OrderLines.First(); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + } } } diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index c23a7a25526..48699c3f6ed 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -296,39 +296,27 @@ private async Task GetResultFromCacheableAsync( var returnType = returnTypes[0]; // Skip first element, it is the timestamp - var rows = new List(cacheable.Count - 1); for (var i = 1; i < cacheable.Count; i++) { - rows.Add(cacheable[i]); + await (returnType.BeforeAssembleAsync(cacheable[i], session, cancellationToken)).ConfigureAwait(false); } - foreach (var row in rows) - { - await (returnType.BeforeAssembleAsync(row, session, cancellationToken)).ConfigureAwait(false); - } - - foreach (var row in rows) + for (var i = 1; i < cacheable.Count; i++) { - result.Add(await (returnType.AssembleAsync(row, session, null, cancellationToken)).ConfigureAwait(false)); + result.Add(await (returnType.AssembleAsync(cacheable[i], session, null, cancellationToken)).ConfigureAwait(false)); } } else { // Skip first element, it is the timestamp - var rows = new List(cacheable.Count - 1); for (var i = 1; i < cacheable.Count; i++) { - rows.Add((object[]) cacheable[i]); + await (TypeHelper.BeforeAssembleAsync((object[]) cacheable[i], returnTypes, session, cancellationToken)).ConfigureAwait(false); } - foreach (var row in rows) - { - await (TypeHelper.BeforeAssembleAsync(row, returnTypes, session, cancellationToken)).ConfigureAwait(false); - } - - foreach (var row in rows) + for (var i = 1; i < cacheable.Count; i++) { - result.Add(await (TypeHelper.AssembleAsync(row, returnTypes, session, null, cancellationToken)).ConfigureAwait(false)); + result.Add(await (TypeHelper.AssembleAsync((object[]) cacheable[i], returnTypes, session, null, cancellationToken)).ConfigureAwait(false)); } } diff --git a/src/NHibernate/Async/Loader/Hql/QueryLoader.cs b/src/NHibernate/Async/Loader/Hql/QueryLoader.cs index a25c9b8c52a..4285de1c77e 100644 --- a/src/NHibernate/Async/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Async/Loader/Hql/QueryLoader.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; using NHibernate.Engine; using NHibernate.Event; using NHibernate.Hql.Ast.ANTLR; diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index bf7d06b1046..35391114979 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -56,11 +56,13 @@ private Task DoQueryAndInitializeNonLazyCollectionsAsync(ISessionImplemen { return Task.FromCanceled(cancellationToken); } - return DoQueryAndInitializeNonLazyCollectionsAsync(session, queryParameters, returnProxies, null, cancellationToken); + return DoQueryAndInitializeNonLazyCollectionsAsync(session, queryParameters, returnProxies, null, null, cancellationToken); } - private async Task DoQueryAndInitializeNonLazyCollectionsAsync(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, IResultTransformer forcedResultTransformer, CancellationToken cancellationToken) + private async Task DoQueryAndInitializeNonLazyCollectionsAsync(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, + IResultTransformer forcedResultTransformer, + QueryCacheResultBuilder queryCacheResultBuilder, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); IPersistenceContext persistenceContext = session.PersistenceContext; @@ -77,7 +79,7 @@ private async Task DoQueryAndInitializeNonLazyCollectionsAsync(ISessionIm { try { - result = await (DoQueryAsync(session, queryParameters, returnProxies, forcedResultTransformer, cancellationToken)).ConfigureAwait(false); + result = await (DoQueryAsync(session, queryParameters, returnProxies, forcedResultTransformer, queryCacheResultBuilder, cancellationToken)).ConfigureAwait(false); } finally { @@ -139,6 +141,8 @@ protected async Task LoadSingleRowAsync(DbDataReader resultSet, ISession return result; } + // Since 5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] internal Task GetRowFromResultSetAsync(DbDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, @@ -149,14 +153,15 @@ internal Task GetRowFromResultSetAsync(DbDataReader resultSet, ISessionI return Task.FromCanceled(cancellationToken); } return GetRowFromResultSetAsync(resultSet, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects, - keys, returnProxies, null, cacheBatchingHandler, cancellationToken); + keys, returnProxies, null, null, cacheBatchingHandler, cancellationToken); } internal async Task GetRowFromResultSetAsync(DbDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, bool returnProxies, IResultTransformer forcedResultTransformer, - Action cacheBatchingHandler, CancellationToken cancellationToken) + QueryCacheResultBuilder queryCacheResultBuilder, + Action cacheBatchingHandler, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ILoadable[] persisters = EntityPersisters; @@ -176,7 +181,7 @@ internal async Task GetRowFromResultSetAsync(DbDataReader resultSet, ISe await (GetRowAsync(resultSet, persisters, keys, queryParameters.OptionalObject, optionalObjectKey, lockModeArray, hydratedObjects, session, !returnProxies, cacheBatchingHandler, cancellationToken)).ConfigureAwait(false); - await (ReadCollectionElementsAsync(row, resultSet, session, cancellationToken)).ConfigureAwait(false); + var collectionKeys = await (ReadCollectionElementsAsync(row, resultSet, session, cancellationToken)).ConfigureAwait(false); if (returnProxies) { @@ -205,16 +210,20 @@ internal async Task GetRowFromResultSetAsync(DbDataReader resultSet, ISe } } - return forcedResultTransformer == null + var result = forcedResultTransformer == null ? await (GetResultColumnOrRowAsync(row, queryParameters.ResultTransformer, resultSet, session, cancellationToken)).ConfigureAwait(false) : forcedResultTransformer.TransformTuple(await (GetResultRowAsync(row, resultSet, session, cancellationToken)).ConfigureAwait(false), ResultRowAliases); + + queryCacheResultBuilder?.AddRow(result, row, collectionKeys); + + return result; } /// /// Read any collection elements contained in a single row of the result set /// - private async Task ReadCollectionElementsAsync(object[] row, DbDataReader resultSet, ISessionImplementor session, CancellationToken cancellationToken) + private async Task ReadCollectionElementsAsync(object[] row, DbDataReader resultSet, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); //TODO: make this handle multiple collection roles! @@ -223,6 +232,7 @@ private async Task ReadCollectionElementsAsync(object[] row, DbDataReader result if (collectionPersisters != null) { + var result = new object[collectionPersisters.Length]; ICollectionAliases[] descriptors = CollectionAliases; int[] collectionOwners = CollectionOwners; @@ -249,12 +259,17 @@ private async Task ReadCollectionElementsAsync(object[] row, DbDataReader result //keys[collectionOwner].getIdentifier() } - await (ReadCollectionElementAsync(owner, key, collectionPersister, descriptors[i], resultSet, session, cancellationToken)).ConfigureAwait(false); + result[i] = await (ReadCollectionElementAsync(owner, key, collectionPersister, descriptors[i], resultSet, session, cancellationToken)).ConfigureAwait(false); } + + return result; } + + return null; } - private async Task DoQueryAsync(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, IResultTransformer forcedResultTransformer, CancellationToken cancellationToken) + private async Task DoQueryAsync(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, + IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (session.BeginProcess()) @@ -302,8 +317,8 @@ private async Task DoQueryAsync(ISessionImplementor session, QueryParamet object result = await (GetRowFromResultSetAsync(rs, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects, - keys, returnProxies, forcedResultTransformer, - (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); + keys, returnProxies, forcedResultTransformer, queryCacheResultBuilder, + (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); results.Add(result); if (createSubselects) @@ -476,7 +491,7 @@ protected virtual Task GetResultRowAsync(Object[] row, DbDataReader rs /// /// Read one collection element from the current row of the ADO.NET result set /// - private static async Task ReadCollectionElementAsync(object optionalOwner, object optionalKey, ICollectionPersister persister, + private static async Task ReadCollectionElementAsync(object optionalOwner, object optionalKey, ICollectionPersister persister, ICollectionAliases descriptor, DbDataReader rs, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -513,6 +528,8 @@ private static async Task ReadCollectionElementAsync(object optionalOwner, objec { await (rowCollection.ReadFromAsync(rs, persister, descriptor, owner, cancellationToken)).ConfigureAwait(false); } + + return collectionRowKey; } else if (optionalKey != null) { @@ -526,9 +543,11 @@ private static async Task ReadCollectionElementAsync(object optionalOwner, objec } persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, optionalKey); // handle empty collection + return optionalKey; } // else no collection element, but also no owner + return null; } /// @@ -1361,13 +1380,18 @@ private async Task ListUsingQueryCacheAsync(ISessionImplementor session, IQueryCache queryCache = _factory.GetQueryCache(queryParameters.CacheRegion); QueryKey key = GenerateQueryKey(session, queryParameters); + var queryCacheBuilder = new QueryCacheResultBuilder(this, session); IList result = await (GetResultFromQueryCacheAsync(session, queryParameters, querySpaces, queryCache, key, cancellationToken)).ConfigureAwait(false); if (result == null) { - result = await (DoListAsync(session, queryParameters, key.ResultTransformer, cancellationToken)).ConfigureAwait(false); - await (PutResultInQueryCacheAsync(session, queryParameters, queryCache, key, result, cancellationToken)).ConfigureAwait(false); + result = await (DoListAsync(session, queryParameters, key.ResultTransformer, queryCacheBuilder, cancellationToken)).ConfigureAwait(false); + await (PutResultInQueryCacheAsync(session, queryParameters, queryCache, key, queryCacheBuilder.Result, cancellationToken)).ConfigureAwait(false); + } + else + { + result = queryCacheBuilder.GetResultList(result); } result = TransformCacheableResults(queryParameters, key.ResultTransformer, result); @@ -1387,7 +1411,7 @@ private async Task GetResultFromQueryCacheAsync( key, queryParameters, queryParameters.HasAutoDiscoverScalarTypes ? null - : key.ResultTransformer.GetCachedResultTypes(ResultTypes), + : key.ResultTransformer.GetCachedResultTypes(CacheTypes), querySpaces, session, cancellationToken)).ConfigureAwait(false); if (_factory.Statistics.IsStatisticsEnabled) @@ -1414,7 +1438,7 @@ private async Task PutResultInQueryCacheAsync(ISessionImplementor session, Query var put = await (queryCache.PutAsync( key, queryParameters, - key.ResultTransformer.GetCachedResultTypes(ResultTypes), + key.ResultTransformer.GetCachedResultTypes(CacheTypes), result, session, cancellationToken)).ConfigureAwait(false); if (put && _factory.Statistics.IsStatisticsEnabled) @@ -1436,10 +1460,22 @@ protected Task DoListAsync(ISessionImplementor session, QueryParameters q { return Task.FromCanceled(cancellationToken); } - return DoListAsync(session, queryParameters, null, cancellationToken); + return DoListAsync(session, queryParameters, null, null, cancellationToken); + } + + // Since 5.3 + [Obsolete("Use the overload with queryCacheResultBuilder parameter")] + protected Task DoListAsync(ISessionImplementor session, QueryParameters queryParameters, IResultTransformer forcedResultTransformer, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return DoListAsync(session, queryParameters, forcedResultTransformer, null, cancellationToken); } - protected async Task DoListAsync(ISessionImplementor session, QueryParameters queryParameters, IResultTransformer forcedResultTransformer, CancellationToken cancellationToken) + protected async Task DoListAsync(ISessionImplementor session, QueryParameters queryParameters, IResultTransformer forcedResultTransformer, + QueryCacheResultBuilder queryCacheResultBuilder, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); bool statsEnabled = Factory.Statistics.IsStatisticsEnabled; @@ -1452,7 +1488,7 @@ protected async Task DoListAsync(ISessionImplementor session, QueryParame IList result; try { - result = await (DoQueryAndInitializeNonLazyCollectionsAsync(session, queryParameters, true, forcedResultTransformer, cancellationToken)).ConfigureAwait(false); + result = await (DoQueryAndInitializeNonLazyCollectionsAsync(session, queryParameters, true, forcedResultTransformer, queryCacheResultBuilder, cancellationToken)).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (HibernateException) diff --git a/src/NHibernate/Async/Multi/QueryBatch.cs b/src/NHibernate/Async/Multi/QueryBatch.cs index 0e8f03daadb..91903833cd9 100644 --- a/src/NHibernate/Async/Multi/QueryBatch.cs +++ b/src/NHibernate/Async/Multi/QueryBatch.cs @@ -184,9 +184,14 @@ protected async Task ExecuteBatchedAsync(CancellationToken cancellationToken) resultSetsCommand.Sql); } - if (statsEnabled) + if (!statsEnabled) + { + return; + } + + stopWatch.Stop(); + if (resultSetsCommand.HasQueries) { - stopWatch.Stop(); Session.Factory.StatisticsImplementor.QueryExecuted( resultSetsCommand.Sql.ToString(), rowCount, @@ -214,7 +219,7 @@ private async Task GetCachedResultsAsync(CancellationToken cancellationToken) parameters[i] = queryInfo.Parameters; returnTypes[i] = queryInfo.Parameters.HasAutoDiscoverScalarTypes ? null - : queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.ResultTypes); + : queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.GetCacheTypes()); spaces[i] = queryInfo.QuerySpaces; } @@ -222,11 +227,12 @@ private async Task GetCachedResultsAsync(CancellationToken cancellationToken) for (var i = 0; i < queryInfos.Length; i++) { - queryInfos[i].SetCachedResult(results[i]); + var queryInfo = queryInfos[i]; + queryInfo.SetCachedResult(results[i]); if (statisticsEnabled) { - var queryIdentifier = queryInfos[i].QueryIdentifier; + var queryIdentifier = queryInfo.QueryIdentifier; if (results[i] == null) { Session.Factory.StatisticsImplementor.QueryCacheMiss(queryIdentifier, cache.RegionName); @@ -258,7 +264,7 @@ private async Task PutCacheableResultsAsync(CancellationToken cancellationToken) var queryInfo = queryInfos[i]; keys[i] = queryInfo.CacheKey; parameters[i] = queryInfo.Parameters; - returnTypes[i] = queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.ResultTypes); + returnTypes[i] = queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.GetCacheTypes()); results[i] = queryInfo.ResultToCache; } diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index f262161b342..77a414081b7 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -75,6 +75,7 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT var lockModeArray = loader.GetLockModes(queryParameters.LockModes); var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); var tmpResults = new List(); + var queryCacheBuilder = new QueryCacheResultBuilder(loader, Session); var cacheBatcher = queryInfo.CacheBatcher; var ownCacheBatcher = cacheBatcher == null; if (ownCacheBatcher) @@ -95,6 +96,7 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT keys, true, forcedResultTransformer, + queryCacheBuilder, (persister, data) => cacheBatcher.AddToBatch(persister, data) , cancellationToken )).ConfigureAwait(false); if (loader.IsSubselectLoadingEnabled) @@ -108,7 +110,7 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT queryInfo.Result = tmpResults; if (queryInfo.CanPutToCache) - queryInfo.ResultToCache = tmpResults; + queryInfo.ResultToCache = queryCacheBuilder.Result; if (ownCacheBatcher) await (cacheBatcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false); diff --git a/src/NHibernate/Cache/QueryCacheResultBuilder.cs b/src/NHibernate/Cache/QueryCacheResultBuilder.cs new file mode 100644 index 00000000000..05ef0fefadc --- /dev/null +++ b/src/NHibernate/Cache/QueryCacheResultBuilder.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Engine; +using NHibernate.Persister.Collection; +using NHibernate.Type; + +namespace NHibernate.Cache +{ + /// + /// A builder that builds a list from a query that can be passed to . + /// + public sealed class QueryCacheResultBuilder + { + private readonly IType[] _resultTypes; + private readonly IType[] _cacheTypes; + private readonly ICollectionPersister[] _collectionPersisters; + private readonly List _entityFetchIndexes = new List(); + private readonly List _collectionFetchIndexes = new List(); + private readonly bool _hasFetches; + private readonly bool _hasCollectionFetches; + private readonly ISessionImplementor _session; + + internal QueryCacheResultBuilder(Loader.Loader loader, ISessionImplementor session) + { + _resultTypes = loader.ResultTypes; + _cacheTypes = loader.CacheTypes; + _collectionPersisters = loader.GetCollectionPersisters(); + _session = session; + + if (loader.EntityFetches == null) + { + return; + } + + for (var i = 0; i < loader.EntityFetches.Length; i++) + { + if (loader.EntityFetches[i]) + { + _entityFetchIndexes.Add(i); + } + } + + _hasFetches = _entityFetchIndexes.Count > 0; + if (loader.CollectionFetches == null) + { + return; + } + + for (var i = 0; i < loader.CollectionFetches.Length; i++) + { + if (loader.CollectionFetches[i]) + { + _collectionFetchIndexes.Add(i); + } + } + + _hasCollectionFetches = _collectionFetchIndexes.Count > 0; + } + + internal IList Result { get; } = new List(); + + internal void AddRow(object result, object[] entities, object[] collectionKeys) + { + if (!_hasFetches) + { + Result.Add(result); + return; + } + + var row = new object[_cacheTypes.Length]; + if (_resultTypes.Length == 1) + { + row[0] = result; + } + else + { + Array.Copy((object[]) result, 0, row, 0, _resultTypes.Length); + } + + var i = _resultTypes.Length; + foreach (var index in _entityFetchIndexes) + { + row[i++] = entities[index]; + } + + foreach (var index in _collectionFetchIndexes) + { + row[i++] = collectionKeys[index]; + } + + Result.Add(row); + } + + internal IList GetResultList(IList cacheList) + { + if (!_hasFetches) + { + return cacheList; + } + + var result = new List(); + + foreach (object[] cacheRow in cacheList) + { + if (_resultTypes.Length == 1) + { + result.Add(cacheRow[0]); + } + else + { + var row = new object[_resultTypes.Length]; + Array.Copy(cacheRow, 0, row, 0, _resultTypes.Length); + result.Add(row); + } + + if (!_hasCollectionFetches) + { + continue; + } + + var i = _cacheTypes.Length - _collectionFetchIndexes.Count; + foreach (var index in _collectionFetchIndexes) + { + var persister = _collectionPersisters[index]; + var key = cacheRow[i]; + var collection = _session.PersistenceContext.GetCollection(new CollectionKey(persister, key)); + collection.ForceInitialization(); + i++; + } + } + + return result; + } + } +} diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index ee361c1541c..20f7c7316ce 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -330,39 +330,27 @@ private IList GetResultFromCacheable( var returnType = returnTypes[0]; // Skip first element, it is the timestamp - var rows = new List(cacheable.Count - 1); for (var i = 1; i < cacheable.Count; i++) { - rows.Add(cacheable[i]); + returnType.BeforeAssemble(cacheable[i], session); } - foreach (var row in rows) - { - returnType.BeforeAssemble(row, session); - } - - foreach (var row in rows) + for (var i = 1; i < cacheable.Count; i++) { - result.Add(returnType.Assemble(row, session, null)); + result.Add(returnType.Assemble(cacheable[i], session, null)); } } else { // Skip first element, it is the timestamp - var rows = new List(cacheable.Count - 1); for (var i = 1; i < cacheable.Count; i++) { - rows.Add((object[]) cacheable[i]); + TypeHelper.BeforeAssemble((object[]) cacheable[i], returnTypes, session); } - foreach (var row in rows) - { - TypeHelper.BeforeAssemble(row, returnTypes, session); - } - - foreach (var row in rows) + for (var i = 1; i < cacheable.Count; i++) { - result.Add(TypeHelper.Assemble(row, returnTypes, session, null)); + result.Add(TypeHelper.Assemble((object[]) cacheable[i], returnTypes, session, null)); } } diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index 9936a60968e..3f339b50158 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; using NHibernate.Engine; using NHibernate.Event; using NHibernate.Hql.Ast.ANTLR; @@ -43,6 +44,7 @@ public partial class QueryLoader : BasicLoader private readonly NullableDictionary _sqlAliasByEntityAlias = new NullableDictionary(); private int _selectLength; private LockMode[] _defaultLockModes; + private IType[] _cacheTypes; private ISet _uncacheableCollectionPersisters; public QueryLoader(QueryTranslatorImpl queryTranslator, ISessionFactoryImplementor factory, SelectClause selectClause) @@ -201,6 +203,8 @@ protected override ICollectionPersister[] CollectionPersisters get { return _collectionPersisters; } } + public override IType[] CacheTypes => _cacheTypes; + private void Initialize(SelectClause selectClause) { IList fromElementList = selectClause.FromElementsForLoad; @@ -220,6 +224,7 @@ private void Initialize(SelectClause selectClause) _collectionPersisters = new IQueryableCollection[length]; _collectionOwners = new int[length]; _collectionSuffixes = new string[length]; + CollectionFetches = new bool[length]; for (int i = 0; i < length; i++) { @@ -229,6 +234,7 @@ private void Initialize(SelectClause selectClause) // collectionSuffixes[i] = collectionFromElement.getColumnAliasSuffix(); // collectionSuffixes[i] = Integer.toString( i ) + "_"; _collectionSuffixes[i] = collectionFromElement.CollectionSuffix; + CollectionFetches[i] = collectionFromElement.IsFetch; } } @@ -242,6 +248,8 @@ private void Initialize(SelectClause selectClause) _includeInSelect = new bool[size]; _owners = new int[size]; _ownerAssociationTypes = new EntityType[size]; + EntityFetches = new bool[size]; + var cacheTypes = new List(ResultTypes); for (int i = 0; i < size; i++) { @@ -264,6 +272,11 @@ private void Initialize(SelectClause selectClause) _sqlAliasSuffixes[i] = (size == 1) ? "" : i + "_"; // sqlAliasSuffixes[i] = element.getColumnAliasSuffix(); _includeInSelect[i] = !element.IsFetch; + EntityFetches[i] = element.IsFetch; + if (element.IsFetch) + { + cacheTypes.Add(_entityPersisters[i].Type); + } if (_includeInSelect[i]) { _selectLength++; @@ -288,6 +301,13 @@ private void Initialize(SelectClause selectClause) } } + if (_collectionPersisters != null) + { + cacheTypes.AddRange(_collectionPersisters.Where((t, i) => CollectionFetches[i]).Select(t => t.KeyType)); + } + + _cacheTypes = cacheTypes.ToArray(); + //NONE, because its the requested lock mode, not the actual! _defaultLockModes = ArrayHelper.Fill(LockMode.None, size); _uncacheableCollectionPersisters = _queryTranslator.UncacheableCollectionPersisters; diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index a89c9b9b007..7959cda3460 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -156,6 +156,12 @@ public virtual bool IsSubselectLoadingEnabled /// public IType[] ResultTypes { get; protected set; } + public bool[] EntityFetches { get; protected set; } + + public bool[] CollectionFetches { get; protected set; } + + public virtual IType[] CacheTypes => ResultTypes; + public ISessionFactoryImplementor Factory { get { return _factory; } @@ -180,6 +186,7 @@ public ISessionFactoryImplementor Factory /// An (optional) persister for a collection to be initialized; only collection loaders /// return a non-null value /// + // 6.0 TODO: Make it public protected virtual ICollectionPersister[] CollectionPersisters { get { return null; } @@ -233,6 +240,12 @@ protected virtual SqlString PreprocessSQL(SqlString sql, QueryParameters paramet return Factory.Settings.IsCommentsEnabled ? PrependComment(sql, parameters) : sql; } + // 6.0 TODO: Remove and replace its usages with CollectionPersisters property + internal ICollectionPersister[] GetCollectionPersisters() + { + return CollectionPersisters; + } + private static SqlString PrependComment(SqlString sql, QueryParameters parameters) { string comment = parameters.Comment; @@ -254,11 +267,13 @@ private static SqlString PrependComment(SqlString sql, QueryParameters parameter private IList DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies) { - return DoQueryAndInitializeNonLazyCollections(session, queryParameters, returnProxies, null); + return DoQueryAndInitializeNonLazyCollections(session, queryParameters, returnProxies, null, null); } - private IList DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, IResultTransformer forcedResultTransformer) + private IList DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, + IResultTransformer forcedResultTransformer, + QueryCacheResultBuilder queryCacheResultBuilder) { IPersistenceContext persistenceContext = session.PersistenceContext; bool defaultReadOnlyOrig = persistenceContext.DefaultReadOnly; @@ -274,7 +289,7 @@ private IList DoQueryAndInitializeNonLazyCollections(ISessionImplementor session { try { - result = DoQuery(session, queryParameters, returnProxies, forcedResultTransformer); + result = DoQuery(session, queryParameters, returnProxies, forcedResultTransformer, queryCacheResultBuilder); } finally { @@ -351,20 +366,23 @@ internal static EntityKey GetOptionalObjectKey(QueryParameters queryParameters, } } + // Since 5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, bool returnProxies, Action cacheBatchingHandler) { return GetRowFromResultSet(resultSet, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects, - keys, returnProxies, null, cacheBatchingHandler); + keys, returnProxies, null, null, cacheBatchingHandler); } internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, bool returnProxies, IResultTransformer forcedResultTransformer, - Action cacheBatchingHandler) + QueryCacheResultBuilder queryCacheResultBuilder, + Action cacheBatchingHandler) { ILoadable[] persisters = EntityPersisters; int entitySpan = persisters.Length; @@ -383,7 +401,7 @@ internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor GetRow(resultSet, persisters, keys, queryParameters.OptionalObject, optionalObjectKey, lockModeArray, hydratedObjects, session, !returnProxies, cacheBatchingHandler); - ReadCollectionElements(row, resultSet, session); + var collectionKeys = ReadCollectionElements(row, resultSet, session); if (returnProxies) { @@ -412,16 +430,20 @@ internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor } } - return forcedResultTransformer == null + var result = forcedResultTransformer == null ? GetResultColumnOrRow(row, queryParameters.ResultTransformer, resultSet, session) : forcedResultTransformer.TransformTuple(GetResultRow(row, resultSet, session), ResultRowAliases); + + queryCacheResultBuilder?.AddRow(result, row, collectionKeys); + + return result; } /// /// Read any collection elements contained in a single row of the result set /// - private void ReadCollectionElements(object[] row, DbDataReader resultSet, ISessionImplementor session) + private object[] ReadCollectionElements(object[] row, DbDataReader resultSet, ISessionImplementor session) { //TODO: make this handle multiple collection roles! @@ -429,6 +451,7 @@ private void ReadCollectionElements(object[] row, DbDataReader resultSet, ISessi if (collectionPersisters != null) { + var result = new object[collectionPersisters.Length]; ICollectionAliases[] descriptors = CollectionAliases; int[] collectionOwners = CollectionOwners; @@ -455,12 +478,17 @@ private void ReadCollectionElements(object[] row, DbDataReader resultSet, ISessi //keys[collectionOwner].getIdentifier() } - ReadCollectionElement(owner, key, collectionPersister, descriptors[i], resultSet, session); + result[i] = ReadCollectionElement(owner, key, collectionPersister, descriptors[i], resultSet, session); } + + return result; } + + return null; } - private IList DoQuery(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, IResultTransformer forcedResultTransformer) + private IList DoQuery(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, + IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder) { using (session.BeginProcess()) { @@ -507,8 +535,8 @@ private IList DoQuery(ISessionImplementor session, QueryParameters queryParamete object result = GetRowFromResultSet(rs, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects, - keys, returnProxies, forcedResultTransformer, - (persister, data) => cacheBatcher.AddToBatch(persister, data)); + keys, returnProxies, forcedResultTransformer, queryCacheResultBuilder, + (persister, data) => cacheBatcher.AddToBatch(persister, data)); results.Add(result); if (createSubselects) @@ -794,7 +822,7 @@ private void RegisterNonExists(EntityKey[] keys, ISessionImplementor session) /// /// Read one collection element from the current row of the ADO.NET result set /// - private static void ReadCollectionElement(object optionalOwner, object optionalKey, ICollectionPersister persister, + private static object ReadCollectionElement(object optionalOwner, object optionalKey, ICollectionPersister persister, ICollectionAliases descriptor, DbDataReader rs, ISessionImplementor session) { IPersistenceContext persistenceContext = session.PersistenceContext; @@ -830,6 +858,8 @@ private static void ReadCollectionElement(object optionalOwner, object optionalK { rowCollection.ReadFrom(rs, persister, descriptor, owner); } + + return collectionRowKey; } else if (optionalKey != null) { @@ -843,9 +873,11 @@ private static void ReadCollectionElement(object optionalOwner, object optionalK } persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, optionalKey); // handle empty collection + return optionalKey; } // else no collection element, but also no owner + return null; } /// @@ -1811,13 +1843,18 @@ private IList ListUsingQueryCache(ISessionImplementor session, QueryParameters q IQueryCache queryCache = _factory.GetQueryCache(queryParameters.CacheRegion); QueryKey key = GenerateQueryKey(session, queryParameters); + var queryCacheBuilder = new QueryCacheResultBuilder(this, session); IList result = GetResultFromQueryCache(session, queryParameters, querySpaces, queryCache, key); if (result == null) { - result = DoList(session, queryParameters, key.ResultTransformer); - PutResultInQueryCache(session, queryParameters, queryCache, key, result); + result = DoList(session, queryParameters, key.ResultTransformer, queryCacheBuilder); + PutResultInQueryCache(session, queryParameters, queryCache, key, queryCacheBuilder.Result); + } + else + { + result = queryCacheBuilder.GetResultList(result); } result = TransformCacheableResults(queryParameters, key.ResultTransformer, result); @@ -1866,7 +1903,7 @@ private IList GetResultFromQueryCache( key, queryParameters, queryParameters.HasAutoDiscoverScalarTypes ? null - : key.ResultTransformer.GetCachedResultTypes(ResultTypes), + : key.ResultTransformer.GetCachedResultTypes(CacheTypes), querySpaces, session); if (_factory.Statistics.IsStatisticsEnabled) @@ -1892,7 +1929,7 @@ private void PutResultInQueryCache(ISessionImplementor session, QueryParameters var put = queryCache.Put( key, queryParameters, - key.ResultTransformer.GetCachedResultTypes(ResultTypes), + key.ResultTransformer.GetCachedResultTypes(CacheTypes), result, session); if (put && _factory.Statistics.IsStatisticsEnabled) @@ -1909,10 +1946,18 @@ private void PutResultInQueryCache(ISessionImplementor session, QueryParameters /// protected IList DoList(ISessionImplementor session, QueryParameters queryParameters) { - return DoList(session, queryParameters, null); + return DoList(session, queryParameters, null, null); } + // Since 5.3 + [Obsolete("Use the overload with queryCacheResultBuilder parameter")] protected IList DoList(ISessionImplementor session, QueryParameters queryParameters, IResultTransformer forcedResultTransformer) + { + return DoList(session, queryParameters, forcedResultTransformer, null); + } + + protected IList DoList(ISessionImplementor session, QueryParameters queryParameters, IResultTransformer forcedResultTransformer, + QueryCacheResultBuilder queryCacheResultBuilder) { bool statsEnabled = Factory.Statistics.IsStatisticsEnabled; var stopWatch = new Stopwatch(); @@ -1924,7 +1969,7 @@ protected IList DoList(ISessionImplementor session, QueryParameters queryParamet IList result; try { - result = DoQueryAndInitializeNonLazyCollections(session, queryParameters, true, forcedResultTransformer); + result = DoQueryAndInitializeNonLazyCollections(session, queryParameters, true, forcedResultTransformer, queryCacheResultBuilder); } catch (HibernateException) { diff --git a/src/NHibernate/Multi/ICachingInformation.cs b/src/NHibernate/Multi/ICachingInformation.cs index 906d79ac5ee..a695febe67a 100644 --- a/src/NHibernate/Multi/ICachingInformation.cs +++ b/src/NHibernate/Multi/ICachingInformation.cs @@ -1,8 +1,12 @@ +using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using NHibernate.Cache; using NHibernate.Engine; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Multi { @@ -44,6 +48,8 @@ public interface ICachingInformation /// /// The query result types. /// + // Since 5.3 + [Obsolete("This property is not used and will be removed in a future version.")] IType[] ResultTypes { get; } /// @@ -68,4 +74,21 @@ public interface ICachingInformation /// A cache batcher. void SetCacheBatcher(CacheBatcher cacheBatcher); } + + internal static class CachingInformationExtensions + { + // 6.0 TODO: Move to ICachingInformation as a Property + public static IType[] GetCacheTypes(this ICachingInformation cachingInformation) + { + var loaderProperty = cachingInformation.GetType().GetProperty("Loader"); + if (loaderProperty?.GetValue(cachingInformation) is Loader.Loader loader) + { + return loader.CacheTypes; + } + +#pragma warning disable 618 + return cachingInformation.ResultTypes; +#pragma warning restore 618 + } + } } diff --git a/src/NHibernate/Multi/QueryBatch.cs b/src/NHibernate/Multi/QueryBatch.cs index ff25cd5076e..0543f3fa684 100644 --- a/src/NHibernate/Multi/QueryBatch.cs +++ b/src/NHibernate/Multi/QueryBatch.cs @@ -195,9 +195,14 @@ protected void ExecuteBatched() resultSetsCommand.Sql); } - if (statsEnabled) + if (!statsEnabled) + { + return; + } + + stopWatch.Stop(); + if (resultSetsCommand.HasQueries) { - stopWatch.Stop(); Session.Factory.StatisticsImplementor.QueryExecuted( resultSetsCommand.Sql.ToString(), rowCount, @@ -224,7 +229,7 @@ private void GetCachedResults() parameters[i] = queryInfo.Parameters; returnTypes[i] = queryInfo.Parameters.HasAutoDiscoverScalarTypes ? null - : queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.ResultTypes); + : queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.GetCacheTypes()); spaces[i] = queryInfo.QuerySpaces; } @@ -232,11 +237,12 @@ private void GetCachedResults() for (var i = 0; i < queryInfos.Length; i++) { - queryInfos[i].SetCachedResult(results[i]); + var queryInfo = queryInfos[i]; + queryInfo.SetCachedResult(results[i]); if (statisticsEnabled) { - var queryIdentifier = queryInfos[i].QueryIdentifier; + var queryIdentifier = queryInfo.QueryIdentifier; if (results[i] == null) { Session.Factory.StatisticsImplementor.QueryCacheMiss(queryIdentifier, cache.RegionName); @@ -276,7 +282,7 @@ private void PutCacheableResults() var queryInfo = queryInfos[i]; keys[i] = queryInfo.CacheKey; parameters[i] = queryInfo.Parameters; - returnTypes[i] = queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.ResultTypes); + returnTypes[i] = queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.GetCacheTypes()); results[i] = queryInfo.ResultToCache; } diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index 73d9cafd18e..d3120293f14 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -214,6 +214,7 @@ public int ProcessResultsSet(DbDataReader reader) var lockModeArray = loader.GetLockModes(queryParameters.LockModes); var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); var tmpResults = new List(); + var queryCacheBuilder = new QueryCacheResultBuilder(loader, Session); var cacheBatcher = queryInfo.CacheBatcher; var ownCacheBatcher = cacheBatcher == null; if (ownCacheBatcher) @@ -234,6 +235,7 @@ public int ProcessResultsSet(DbDataReader reader) keys, true, forcedResultTransformer, + queryCacheBuilder, (persister, data) => cacheBatcher.AddToBatch(persister, data) ); if (loader.IsSubselectLoadingEnabled) @@ -247,7 +249,7 @@ public int ProcessResultsSet(DbDataReader reader) queryInfo.Result = tmpResults; if (queryInfo.CanPutToCache) - queryInfo.ResultToCache = tmpResults; + queryInfo.ResultToCache = queryCacheBuilder.Result; if (ownCacheBatcher) cacheBatcher.ExecuteBatch(); @@ -276,6 +278,12 @@ public void ProcessResults() if (queryInfo.IsCacheable) { + if (queryInfo.IsResultFromCache) + { + var queryCacheBuilder = new QueryCacheResultBuilder(queryInfo.Loader, Session); + queryInfo.Result = queryCacheBuilder.GetResultList(queryInfo.Result); + } + // This transformation must not be applied to ResultToCache. queryInfo.Result = queryInfo.Loader.TransformCacheableResults( From d5d11c3c9ce8ee8bc1fce43796173c2d560cf17f Mon Sep 17 00:00:00 2001 From: maca88 Date: Fri, 4 Jan 2019 00:22:24 +0100 Subject: [PATCH 2/6] Moved collection initialization to StandardQueryCache --- src/AsyncGenerator.yml | 3 - .../Async/Cache/StandardQueryCache.cs | 24 ++++++- src/NHibernate/Async/Loader/Loader.cs | 18 +++--- .../Async/Multi/QueryBatchItemBase.cs | 2 +- src/NHibernate/Async/Type/TypeHelper.cs | 64 ++++++++++++++++++- .../Cache/QueryCacheResultBuilder.cs | 29 ++------- src/NHibernate/Cache/StandardQueryCache.cs | 24 ++++++- src/NHibernate/Loader/Hql/QueryLoader.cs | 2 +- src/NHibernate/Loader/Loader.cs | 25 +++----- src/NHibernate/Multi/QueryBatchItemBase.cs | 4 +- src/NHibernate/Type/TypeHelper.cs | 60 ++++++++++++++++- 11 files changed, 194 insertions(+), 61 deletions(-) diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml index dc5c250544c..382d07d44c9 100644 --- a/src/AsyncGenerator.yml +++ b/src/AsyncGenerator.yml @@ -104,9 +104,6 @@ - conversion: Ignore name: GetEnumerator containingTypeName: IFutureEnumerable - # 6.0 TODO: Remove QueryCacheResultBuilder from ignore - - conversion: Ignore - containingTypeName: QueryCacheResultBuilder - conversion: ToAsync name: ExecuteReader containingTypeName: IBatcher diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 48699c3f6ed..acfd9731c94 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -308,6 +308,20 @@ private async Task GetResultFromCacheableAsync( } else { + var collectionTypes = new Dictionary(); + var nonCollectionTypeIndexes = new List(); + for (var i = 0; i < returnTypes.Length; i++) + { + if (returnTypes[i] is CollectionType collectionType) + { + collectionTypes.Add(i, collectionType); + } + else + { + nonCollectionTypeIndexes.Add(i); + } + } + // Skip first element, it is the timestamp for (var i = 1; i < cacheable.Count; i++) { @@ -316,7 +330,15 @@ private async Task GetResultFromCacheableAsync( for (var i = 1; i < cacheable.Count; i++) { - result.Add(await (TypeHelper.AssembleAsync((object[]) cacheable[i], returnTypes, session, null, cancellationToken)).ConfigureAwait(false)); + result.Add(await (TypeHelper.AssembleAsync((object[]) cacheable[i], returnTypes, nonCollectionTypeIndexes, session, cancellationToken)).ConfigureAwait(false)); + } + + // Initialization of the fetched collection must be done at the end in order to be able to batch fetch them + // from the cache or database. The collections were already created in the previous for statement so we only + // have to initialize them. + for (var i = 1; i < cacheable.Count; i++) + { + await (TypeHelper.InitializeCollectionsAsync((object[]) cacheable[i], collectionTypes, session, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index 35391114979..eba8fd8cab2 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -181,7 +181,7 @@ internal async Task GetRowFromResultSetAsync(DbDataReader resultSet, ISe await (GetRowAsync(resultSet, persisters, keys, queryParameters.OptionalObject, optionalObjectKey, lockModeArray, hydratedObjects, session, !returnProxies, cacheBatchingHandler, cancellationToken)).ConfigureAwait(false); - var collectionKeys = await (ReadCollectionElementsAsync(row, resultSet, session, cancellationToken)).ConfigureAwait(false); + var collections = await (ReadCollectionElementsAsync(row, resultSet, session, cancellationToken)).ConfigureAwait(false); if (returnProxies) { @@ -215,7 +215,7 @@ internal async Task GetRowFromResultSetAsync(DbDataReader resultSet, ISe : forcedResultTransformer.TransformTuple(await (GetResultRowAsync(row, resultSet, session, cancellationToken)).ConfigureAwait(false), ResultRowAliases); - queryCacheResultBuilder?.AddRow(result, row, collectionKeys); + queryCacheResultBuilder?.AddRow(result, row, collections); return result; } @@ -223,7 +223,7 @@ internal async Task GetRowFromResultSetAsync(DbDataReader resultSet, ISe /// /// Read any collection elements contained in a single row of the result set /// - private async Task ReadCollectionElementsAsync(object[] row, DbDataReader resultSet, ISessionImplementor session, CancellationToken cancellationToken) + private async Task ReadCollectionElementsAsync(object[] row, DbDataReader resultSet, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); //TODO: make this handle multiple collection roles! @@ -232,7 +232,7 @@ private async Task ReadCollectionElementsAsync(object[] row, DbDataRea if (collectionPersisters != null) { - var result = new object[collectionPersisters.Length]; + var result = new IPersistentCollection[collectionPersisters.Length]; ICollectionAliases[] descriptors = CollectionAliases; int[] collectionOwners = CollectionOwners; @@ -491,7 +491,7 @@ protected virtual Task GetResultRowAsync(Object[] row, DbDataReader rs /// /// Read one collection element from the current row of the ADO.NET result set /// - private static async Task ReadCollectionElementAsync(object optionalOwner, object optionalKey, ICollectionPersister persister, + private static async Task ReadCollectionElementAsync(object optionalOwner, object optionalKey, ICollectionPersister persister, ICollectionAliases descriptor, DbDataReader rs, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -529,7 +529,7 @@ private static async Task ReadCollectionElementAsync(object optionalOwne await (rowCollection.ReadFromAsync(rs, persister, descriptor, owner, cancellationToken)).ConfigureAwait(false); } - return collectionRowKey; + return rowCollection; } else if (optionalKey != null) { @@ -541,9 +541,9 @@ private static async Task ReadCollectionElementAsync(object optionalOwne { Log.Debug("result set contains (possibly empty) collection: {0}", MessageHelper.CollectionInfoString(persister, optionalKey)); } - persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, optionalKey); + // handle empty collection - return optionalKey; + return persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, optionalKey); } // else no collection element, but also no owner @@ -1380,7 +1380,7 @@ private async Task ListUsingQueryCacheAsync(ISessionImplementor session, IQueryCache queryCache = _factory.GetQueryCache(queryParameters.CacheRegion); QueryKey key = GenerateQueryKey(session, queryParameters); - var queryCacheBuilder = new QueryCacheResultBuilder(this, session); + var queryCacheBuilder = new QueryCacheResultBuilder(this); IList result = await (GetResultFromQueryCacheAsync(session, queryParameters, querySpaces, queryCache, key, cancellationToken)).ConfigureAwait(false); diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index 77a414081b7..37fbaff3558 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -75,7 +75,7 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT var lockModeArray = loader.GetLockModes(queryParameters.LockModes); var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); var tmpResults = new List(); - var queryCacheBuilder = new QueryCacheResultBuilder(loader, Session); + var queryCacheBuilder = new QueryCacheResultBuilder(loader); var cacheBatcher = queryInfo.CacheBatcher; var ownCacheBatcher = cacheBatcher == null; if (ownCacheBatcher) diff --git a/src/NHibernate/Async/Type/TypeHelper.cs b/src/NHibernate/Async/Type/TypeHelper.cs index 6208289b5b9..135d0133eef 100644 --- a/src/NHibernate/Async/Type/TypeHelper.cs +++ b/src/NHibernate/Async/Type/TypeHelper.cs @@ -10,6 +10,8 @@ using System; using System.Collections; +using System.Collections.Generic; +using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Intercept; using NHibernate.Properties; @@ -66,6 +68,59 @@ public static async Task AssembleAsync(object[] row, ICacheAssembler[] return assembled; } + /// + /// Apply the operation across a series of values. + /// + /// The values + /// The value types + /// The indexes of types to assemble. + /// The originating session + /// A cancellation token that can be used to cancel the work + /// + internal static async Task AssembleAsync(object[] row, ICacheAssembler[] types, IList typeIndexes, ISessionImplementor session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var assembled = new object[row.Length]; + foreach (var i in typeIndexes) + { + var value = row[i]; + if (Equals(LazyPropertyInitializer.UnfetchedProperty, value) || Equals(BackrefPropertyAccessor.Unknown, value)) + { + assembled[i] = value; + } + else + { + assembled[i] = await (types[i].AssembleAsync(row[i], session, null, cancellationToken)).ConfigureAwait(false); + } + } + return assembled; + } + + /// + /// Initialize collections from the query cache row values. + /// + /// The values + /// The dictionary containing collection types and their indexes in the parameter as key + /// The originating session + /// A cancellation token that can be used to cancel the work + /// + internal static async Task InitializeCollectionsAsync(object[] row, IDictionary types, ISessionImplementor session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + foreach (var pair in types) + { + var value = row[pair.Key]; + if (value == null) + { + continue; + } + + var persister = session.Factory.GetCollectionPersister(pair.Value.Role); + var collection = session.PersistenceContext.GetCollection(new CollectionKey(persister, value)); + await (collection.ForceInitializationAsync(cancellationToken)).ConfigureAwait(false); + } + } + /// Apply the operation across a series of values. /// The values /// The value types @@ -90,7 +145,14 @@ public static async Task DisassembleAsync(object[] row, ICacheAssemble } else { - disassembled[i] = await (types[i].DisassembleAsync(row[i], session, owner, cancellationToken)).ConfigureAwait(false); + if (owner == null && row[i] is IPersistentCollection collection) + { + disassembled[i] = await (types[i].DisassembleAsync(row[i], session, collection.Owner, cancellationToken)).ConfigureAwait(false); + } + else + { + disassembled[i] = await (types[i].DisassembleAsync(row[i], session, owner, cancellationToken)).ConfigureAwait(false); + } } } return disassembled; diff --git a/src/NHibernate/Cache/QueryCacheResultBuilder.cs b/src/NHibernate/Cache/QueryCacheResultBuilder.cs index 05ef0fefadc..6c85bbf2f39 100644 --- a/src/NHibernate/Cache/QueryCacheResultBuilder.cs +++ b/src/NHibernate/Cache/QueryCacheResultBuilder.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -17,19 +18,14 @@ public sealed class QueryCacheResultBuilder { private readonly IType[] _resultTypes; private readonly IType[] _cacheTypes; - private readonly ICollectionPersister[] _collectionPersisters; private readonly List _entityFetchIndexes = new List(); private readonly List _collectionFetchIndexes = new List(); private readonly bool _hasFetches; - private readonly bool _hasCollectionFetches; - private readonly ISessionImplementor _session; - internal QueryCacheResultBuilder(Loader.Loader loader, ISessionImplementor session) + internal QueryCacheResultBuilder(Loader.Loader loader) { _resultTypes = loader.ResultTypes; _cacheTypes = loader.CacheTypes; - _collectionPersisters = loader.GetCollectionPersisters(); - _session = session; if (loader.EntityFetches == null) { @@ -57,13 +53,11 @@ internal QueryCacheResultBuilder(Loader.Loader loader, ISessionImplementor sessi _collectionFetchIndexes.Add(i); } } - - _hasCollectionFetches = _collectionFetchIndexes.Count > 0; } internal IList Result { get; } = new List(); - internal void AddRow(object result, object[] entities, object[] collectionKeys) + internal void AddRow(object result, object[] entities, IPersistentCollection[] collections) { if (!_hasFetches) { @@ -89,7 +83,7 @@ internal void AddRow(object result, object[] entities, object[] collectionKeys) foreach (var index in _collectionFetchIndexes) { - row[i++] = collectionKeys[index]; + row[i++] = collections[index]; } Result.Add(row); @@ -116,21 +110,6 @@ internal IList GetResultList(IList cacheList) Array.Copy(cacheRow, 0, row, 0, _resultTypes.Length); result.Add(row); } - - if (!_hasCollectionFetches) - { - continue; - } - - var i = _cacheTypes.Length - _collectionFetchIndexes.Count; - foreach (var index in _collectionFetchIndexes) - { - var persister = _collectionPersisters[index]; - var key = cacheRow[i]; - var collection = _session.PersistenceContext.GetCollection(new CollectionKey(persister, key)); - collection.ForceInitialization(); - i++; - } } return result; diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 20f7c7316ce..1a34ba1c34e 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -342,6 +342,20 @@ private IList GetResultFromCacheable( } else { + var collectionTypes = new Dictionary(); + var nonCollectionTypeIndexes = new List(); + for (var i = 0; i < returnTypes.Length; i++) + { + if (returnTypes[i] is CollectionType collectionType) + { + collectionTypes.Add(i, collectionType); + } + else + { + nonCollectionTypeIndexes.Add(i); + } + } + // Skip first element, it is the timestamp for (var i = 1; i < cacheable.Count; i++) { @@ -350,7 +364,15 @@ private IList GetResultFromCacheable( for (var i = 1; i < cacheable.Count; i++) { - result.Add(TypeHelper.Assemble((object[]) cacheable[i], returnTypes, session, null)); + result.Add(TypeHelper.Assemble((object[]) cacheable[i], returnTypes, nonCollectionTypeIndexes, session)); + } + + // Initialization of the fetched collection must be done at the end in order to be able to batch fetch them + // from the cache or database. The collections were already created in the previous for statement so we only + // have to initialize them. + for (var i = 1; i < cacheable.Count; i++) + { + TypeHelper.InitializeCollections((object[]) cacheable[i], collectionTypes, session); } } diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index 3f339b50158..6708091d01d 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -303,7 +303,7 @@ private void Initialize(SelectClause selectClause) if (_collectionPersisters != null) { - cacheTypes.AddRange(_collectionPersisters.Where((t, i) => CollectionFetches[i]).Select(t => t.KeyType)); + cacheTypes.AddRange(_collectionPersisters.Where((t, i) => CollectionFetches[i]).Select(t => t.CollectionType)); } _cacheTypes = cacheTypes.ToArray(); diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 7959cda3460..b48e575927d 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -186,7 +186,6 @@ public ISessionFactoryImplementor Factory /// An (optional) persister for a collection to be initialized; only collection loaders /// return a non-null value /// - // 6.0 TODO: Make it public protected virtual ICollectionPersister[] CollectionPersisters { get { return null; } @@ -240,12 +239,6 @@ protected virtual SqlString PreprocessSQL(SqlString sql, QueryParameters paramet return Factory.Settings.IsCommentsEnabled ? PrependComment(sql, parameters) : sql; } - // 6.0 TODO: Remove and replace its usages with CollectionPersisters property - internal ICollectionPersister[] GetCollectionPersisters() - { - return CollectionPersisters; - } - private static SqlString PrependComment(SqlString sql, QueryParameters parameters) { string comment = parameters.Comment; @@ -401,7 +394,7 @@ internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor GetRow(resultSet, persisters, keys, queryParameters.OptionalObject, optionalObjectKey, lockModeArray, hydratedObjects, session, !returnProxies, cacheBatchingHandler); - var collectionKeys = ReadCollectionElements(row, resultSet, session); + var collections = ReadCollectionElements(row, resultSet, session); if (returnProxies) { @@ -435,7 +428,7 @@ internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor : forcedResultTransformer.TransformTuple(GetResultRow(row, resultSet, session), ResultRowAliases); - queryCacheResultBuilder?.AddRow(result, row, collectionKeys); + queryCacheResultBuilder?.AddRow(result, row, collections); return result; } @@ -443,7 +436,7 @@ internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor /// /// Read any collection elements contained in a single row of the result set /// - private object[] ReadCollectionElements(object[] row, DbDataReader resultSet, ISessionImplementor session) + private IPersistentCollection[] ReadCollectionElements(object[] row, DbDataReader resultSet, ISessionImplementor session) { //TODO: make this handle multiple collection roles! @@ -451,7 +444,7 @@ private object[] ReadCollectionElements(object[] row, DbDataReader resultSet, IS if (collectionPersisters != null) { - var result = new object[collectionPersisters.Length]; + var result = new IPersistentCollection[collectionPersisters.Length]; ICollectionAliases[] descriptors = CollectionAliases; int[] collectionOwners = CollectionOwners; @@ -822,7 +815,7 @@ private void RegisterNonExists(EntityKey[] keys, ISessionImplementor session) /// /// Read one collection element from the current row of the ADO.NET result set /// - private static object ReadCollectionElement(object optionalOwner, object optionalKey, ICollectionPersister persister, + private static IPersistentCollection ReadCollectionElement(object optionalOwner, object optionalKey, ICollectionPersister persister, ICollectionAliases descriptor, DbDataReader rs, ISessionImplementor session) { IPersistenceContext persistenceContext = session.PersistenceContext; @@ -859,7 +852,7 @@ private static object ReadCollectionElement(object optionalOwner, object optiona rowCollection.ReadFrom(rs, persister, descriptor, owner); } - return collectionRowKey; + return rowCollection; } else if (optionalKey != null) { @@ -871,9 +864,9 @@ private static object ReadCollectionElement(object optionalOwner, object optiona { Log.Debug("result set contains (possibly empty) collection: {0}", MessageHelper.CollectionInfoString(persister, optionalKey)); } - persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, optionalKey); + // handle empty collection - return optionalKey; + return persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, optionalKey); } // else no collection element, but also no owner @@ -1843,7 +1836,7 @@ private IList ListUsingQueryCache(ISessionImplementor session, QueryParameters q IQueryCache queryCache = _factory.GetQueryCache(queryParameters.CacheRegion); QueryKey key = GenerateQueryKey(session, queryParameters); - var queryCacheBuilder = new QueryCacheResultBuilder(this, session); + var queryCacheBuilder = new QueryCacheResultBuilder(this); IList result = GetResultFromQueryCache(session, queryParameters, querySpaces, queryCache, key); diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index d3120293f14..43ba89b325d 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -214,7 +214,7 @@ public int ProcessResultsSet(DbDataReader reader) var lockModeArray = loader.GetLockModes(queryParameters.LockModes); var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); var tmpResults = new List(); - var queryCacheBuilder = new QueryCacheResultBuilder(loader, Session); + var queryCacheBuilder = new QueryCacheResultBuilder(loader); var cacheBatcher = queryInfo.CacheBatcher; var ownCacheBatcher = cacheBatcher == null; if (ownCacheBatcher) @@ -280,7 +280,7 @@ public void ProcessResults() { if (queryInfo.IsResultFromCache) { - var queryCacheBuilder = new QueryCacheResultBuilder(queryInfo.Loader, Session); + var queryCacheBuilder = new QueryCacheResultBuilder(queryInfo.Loader); queryInfo.Result = queryCacheBuilder.GetResultList(queryInfo.Result); } diff --git a/src/NHibernate/Type/TypeHelper.cs b/src/NHibernate/Type/TypeHelper.cs index 56a676cc87a..03b617ed028 100644 --- a/src/NHibernate/Type/TypeHelper.cs +++ b/src/NHibernate/Type/TypeHelper.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Intercept; using NHibernate.Properties; @@ -78,6 +80,55 @@ public static object[] Assemble(object[] row, ICacheAssembler[] types, ISessionI return assembled; } + /// + /// Apply the operation across a series of values. + /// + /// The values + /// The value types + /// The indexes of types to assemble. + /// The originating session + /// + internal static object[] Assemble(object[] row, ICacheAssembler[] types, IList typeIndexes, ISessionImplementor session) + { + var assembled = new object[row.Length]; + foreach (var i in typeIndexes) + { + var value = row[i]; + if (Equals(LazyPropertyInitializer.UnfetchedProperty, value) || Equals(BackrefPropertyAccessor.Unknown, value)) + { + assembled[i] = value; + } + else + { + assembled[i] = types[i].Assemble(row[i], session, null); + } + } + return assembled; + } + + /// + /// Initialize collections from the query cache row values. + /// + /// The values + /// The dictionary containing collection types and their indexes in the parameter as key + /// The originating session + /// + internal static void InitializeCollections(object[] row, IDictionary types, ISessionImplementor session) + { + foreach (var pair in types) + { + var value = row[pair.Key]; + if (value == null) + { + continue; + } + + var persister = session.Factory.GetCollectionPersister(pair.Value.Role); + var collection = session.PersistenceContext.GetCollection(new CollectionKey(persister, value)); + collection.ForceInitialization(); + } + } + /// Apply the operation across a series of values. /// The values /// The value types @@ -100,7 +151,14 @@ public static object[] Disassemble(object[] row, ICacheAssembler[] types, bool[] } else { - disassembled[i] = types[i].Disassemble(row[i], session, owner); + if (owner == null && row[i] is IPersistentCollection collection) + { + disassembled[i] = types[i].Disassemble(row[i], session, collection.Owner); + } + else + { + disassembled[i] = types[i].Disassemble(row[i], session, owner); + } } } return disassembled; From 2f0ae6b7e6d4e7ee98f58045a18ccfca27d298a3 Mon Sep 17 00:00:00 2001 From: maca88 Date: Fri, 4 Jan 2019 18:36:36 +0100 Subject: [PATCH 3/6] Update cache result with fetched collections for consistency --- .../Async/Cache/StandardQueryCache.cs | 2 +- src/NHibernate/Async/Type/TypeHelper.cs | 34 ++++++++++++------- src/NHibernate/Cache/StandardQueryCache.cs | 2 +- src/NHibernate/Type/TypeHelper.cs | 34 ++++++++++++------- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index acfd9731c94..0193cd93ec7 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -338,7 +338,7 @@ private async Task GetResultFromCacheableAsync( // have to initialize them. for (var i = 1; i < cacheable.Count; i++) { - await (TypeHelper.InitializeCollectionsAsync((object[]) cacheable[i], collectionTypes, session, cancellationToken)).ConfigureAwait(false); + await (TypeHelper.InitializeCollectionsAsync((object[]) cacheable[i], (object[]) result[i - 1], collectionTypes, session, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Type/TypeHelper.cs b/src/NHibernate/Async/Type/TypeHelper.cs index 135d0133eef..08a01f66479 100644 --- a/src/NHibernate/Async/Type/TypeHelper.cs +++ b/src/NHibernate/Async/Type/TypeHelper.cs @@ -71,13 +71,17 @@ public static async Task AssembleAsync(object[] row, ICacheAssembler[] /// /// Apply the operation across a series of values. /// - /// The values - /// The value types + /// The cached values. + /// The value types. /// The indexes of types to assemble. - /// The originating session + /// The originating session. /// A cancellation token that can be used to cancel the work - /// - internal static async Task AssembleAsync(object[] row, ICacheAssembler[] types, IList typeIndexes, ISessionImplementor session, CancellationToken cancellationToken) + /// A new array of assembled values. + internal static async Task AssembleAsync( + object[] row, + ICacheAssembler[] types, + IList typeIndexes, + ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var assembled = new object[row.Length]; @@ -93,23 +97,28 @@ internal static async Task AssembleAsync(object[] row, ICacheAssembler assembled[i] = await (types[i].AssembleAsync(row[i], session, null, cancellationToken)).ConfigureAwait(false); } } + return assembled; } /// - /// Initialize collections from the query cache row values. + /// Initialize collections from the query cached row and update the assembled row. /// - /// The values - /// The dictionary containing collection types and their indexes in the parameter as key - /// The originating session + /// The cached values. + /// The assembled values to update. + /// The dictionary containing collection types and their indexes in the parameter as key. + /// The originating session. /// A cancellation token that can be used to cancel the work - /// - internal static async Task InitializeCollectionsAsync(object[] row, IDictionary types, ISessionImplementor session, CancellationToken cancellationToken) + internal static async Task InitializeCollectionsAsync( + object[] cacheRow, + object[] assembleRow, + IDictionary types, + ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); foreach (var pair in types) { - var value = row[pair.Key]; + var value = cacheRow[pair.Key]; if (value == null) { continue; @@ -118,6 +127,7 @@ internal static async Task InitializeCollectionsAsync(object[] row, IDictionary< var persister = session.Factory.GetCollectionPersister(pair.Value.Role); var collection = session.PersistenceContext.GetCollection(new CollectionKey(persister, value)); await (collection.ForceInitializationAsync(cancellationToken)).ConfigureAwait(false); + assembleRow[pair.Key] = collection; } } diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 1a34ba1c34e..69439f0a93b 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -372,7 +372,7 @@ private IList GetResultFromCacheable( // have to initialize them. for (var i = 1; i < cacheable.Count; i++) { - TypeHelper.InitializeCollections((object[]) cacheable[i], collectionTypes, session); + TypeHelper.InitializeCollections((object[]) cacheable[i], (object[]) result[i - 1], collectionTypes, session); } } diff --git a/src/NHibernate/Type/TypeHelper.cs b/src/NHibernate/Type/TypeHelper.cs index 03b617ed028..77bd0d211bb 100644 --- a/src/NHibernate/Type/TypeHelper.cs +++ b/src/NHibernate/Type/TypeHelper.cs @@ -83,12 +83,16 @@ public static object[] Assemble(object[] row, ICacheAssembler[] types, ISessionI /// /// Apply the operation across a series of values. /// - /// The values - /// The value types + /// The cached values. + /// The value types. /// The indexes of types to assemble. - /// The originating session - /// - internal static object[] Assemble(object[] row, ICacheAssembler[] types, IList typeIndexes, ISessionImplementor session) + /// The originating session. + /// A new array of assembled values. + internal static object[] Assemble( + object[] row, + ICacheAssembler[] types, + IList typeIndexes, + ISessionImplementor session) { var assembled = new object[row.Length]; foreach (var i in typeIndexes) @@ -103,21 +107,26 @@ internal static object[] Assemble(object[] row, ICacheAssembler[] types, IList - /// Initialize collections from the query cache row values. + /// Initialize collections from the query cached row and update the assembled row. /// - /// The values - /// The dictionary containing collection types and their indexes in the parameter as key - /// The originating session - /// - internal static void InitializeCollections(object[] row, IDictionary types, ISessionImplementor session) + /// The cached values. + /// The assembled values to update. + /// The dictionary containing collection types and their indexes in the parameter as key. + /// The originating session. + internal static void InitializeCollections( + object[] cacheRow, + object[] assembleRow, + IDictionary types, + ISessionImplementor session) { foreach (var pair in types) { - var value = row[pair.Key]; + var value = cacheRow[pair.Key]; if (value == null) { continue; @@ -126,6 +135,7 @@ internal static void InitializeCollections(object[] row, IDictionary Date: Mon, 21 Jan 2019 21:24:41 +0100 Subject: [PATCH 4/6] Removed Loader.GetRowFromResultSet --- src/NHibernate/Async/Impl/MultiCriteriaImpl.cs | 4 ++-- src/NHibernate/Async/Impl/MultiQueryImpl.cs | 2 +- src/NHibernate/Async/Loader/Loader.cs | 18 ++---------------- src/NHibernate/Impl/MultiCriteriaImpl.cs | 4 ++-- src/NHibernate/Impl/MultiQueryImpl.cs | 2 +- src/NHibernate/Loader/Loader.cs | 14 ++------------ 6 files changed, 10 insertions(+), 34 deletions(-) diff --git a/src/NHibernate/Async/Impl/MultiCriteriaImpl.cs b/src/NHibernate/Async/Impl/MultiCriteriaImpl.cs index 3f063cf00e6..d7f4ccc38fc 100644 --- a/src/NHibernate/Async/Impl/MultiCriteriaImpl.cs +++ b/src/NHibernate/Async/Impl/MultiCriteriaImpl.cs @@ -185,8 +185,8 @@ private async Task GetResultsFromDatabaseAsync(IList results, CancellationToken object o = await (loader.GetRowFromResultSetAsync(reader, session, queryParameters, loader.GetLockModes(queryParameters.LockModes), - null, hydratedObjects[i], keys, true, - (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); + null, hydratedObjects[i], keys, true, null, null, + (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); if (createSubselects[i]) { subselectResultKeys[i].Add(keys); diff --git a/src/NHibernate/Async/Impl/MultiQueryImpl.cs b/src/NHibernate/Async/Impl/MultiQueryImpl.cs index b65ead8cf26..4e2f6051deb 100644 --- a/src/NHibernate/Async/Impl/MultiQueryImpl.cs +++ b/src/NHibernate/Async/Impl/MultiQueryImpl.cs @@ -143,7 +143,7 @@ protected async Task> DoListAsync(CancellationToken cancellationTok rowCount++; object result = await (translator.Loader.GetRowFromResultSetAsync( - reader, session, parameter, lockModeArray, optionalObjectKey, hydratedObjects[i], keys, true, + reader, session, parameter, lockModeArray, optionalObjectKey, hydratedObjects[i], keys, true, null, null, (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); tempResults.Add(result); diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index eba8fd8cab2..a87d8845ce3 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -121,7 +121,8 @@ protected async Task LoadSingleRowAsync(DbDataReader resultSet, ISession { result = await (GetRowFromResultSetAsync(resultSet, session, queryParameters, GetLockModes(queryParameters.LockModes), null, - hydratedObjects, new EntityKey[entitySpan], returnProxies, (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); + hydratedObjects, new EntityKey[entitySpan], returnProxies, null, null, + (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (HibernateException) @@ -141,21 +142,6 @@ protected async Task LoadSingleRowAsync(DbDataReader resultSet, ISession return result; } - // Since 5.3 - [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] - internal Task GetRowFromResultSetAsync(DbDataReader resultSet, ISessionImplementor session, - QueryParameters queryParameters, LockMode[] lockModeArray, - EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, - bool returnProxies, Action cacheBatchingHandler, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return GetRowFromResultSetAsync(resultSet, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects, - keys, returnProxies, null, null, cacheBatchingHandler, cancellationToken); - } - internal async Task GetRowFromResultSetAsync(DbDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, diff --git a/src/NHibernate/Impl/MultiCriteriaImpl.cs b/src/NHibernate/Impl/MultiCriteriaImpl.cs index 4920acc5306..d3d6be2fd84 100644 --- a/src/NHibernate/Impl/MultiCriteriaImpl.cs +++ b/src/NHibernate/Impl/MultiCriteriaImpl.cs @@ -258,8 +258,8 @@ private void GetResultsFromDatabase(IList results) object o = loader.GetRowFromResultSet(reader, session, queryParameters, loader.GetLockModes(queryParameters.LockModes), - null, hydratedObjects[i], keys, true, - (persister, data) => cacheBatcher.AddToBatch(persister, data)); + null, hydratedObjects[i], keys, true, null, null, + (persister, data) => cacheBatcher.AddToBatch(persister, data)); if (createSubselects[i]) { subselectResultKeys[i].Add(keys); diff --git a/src/NHibernate/Impl/MultiQueryImpl.cs b/src/NHibernate/Impl/MultiQueryImpl.cs index 310e45125b4..481ce1feb94 100644 --- a/src/NHibernate/Impl/MultiQueryImpl.cs +++ b/src/NHibernate/Impl/MultiQueryImpl.cs @@ -587,7 +587,7 @@ protected List DoList() rowCount++; object result = translator.Loader.GetRowFromResultSet( - reader, session, parameter, lockModeArray, optionalObjectKey, hydratedObjects[i], keys, true, + reader, session, parameter, lockModeArray, optionalObjectKey, hydratedObjects[i], keys, true, null, null, (persister, data) => cacheBatcher.AddToBatch(persister, data)); tempResults.Add(result); diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index b48e575927d..97adc046a84 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -322,7 +322,8 @@ protected object LoadSingleRow(DbDataReader resultSet, ISessionImplementor sessi { result = GetRowFromResultSet(resultSet, session, queryParameters, GetLockModes(queryParameters.LockModes), null, - hydratedObjects, new EntityKey[entitySpan], returnProxies, (persister, data) => cacheBatcher.AddToBatch(persister, data)); + hydratedObjects, new EntityKey[entitySpan], returnProxies, null, null, + (persister, data) => cacheBatcher.AddToBatch(persister, data)); } catch (HibernateException) { @@ -359,17 +360,6 @@ internal static EntityKey GetOptionalObjectKey(QueryParameters queryParameters, } } - // Since 5.3 - [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] - internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor session, - QueryParameters queryParameters, LockMode[] lockModeArray, - EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, - bool returnProxies, Action cacheBatchingHandler) - { - return GetRowFromResultSet(resultSet, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects, - keys, returnProxies, null, null, cacheBatchingHandler); - } - internal object GetRowFromResultSet(DbDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, From df0d9df6765a13b026ba28a081d130d802514c0a Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 22 Jan 2019 20:57:38 +0100 Subject: [PATCH 5/6] Add some optimizations --- src/NHibernate/Async/Cache/StandardQueryCache.cs | 12 ++++++++---- src/NHibernate/Async/Type/TypeHelper.cs | 10 +++++----- src/NHibernate/Cache/QueryCacheResultBuilder.cs | 2 ++ src/NHibernate/Cache/StandardQueryCache.cs | 12 ++++++++---- src/NHibernate/Multi/ICachingInformation.cs | 7 +++---- .../Multi/ICachingInformationWithFetches.cs | 13 +++++++++++++ src/NHibernate/Multi/QueryBatchItemBase.cs | 5 ++++- src/NHibernate/Type/TypeHelper.cs | 10 +++++----- 8 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 src/NHibernate/Multi/ICachingInformationWithFetches.cs diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 0193cd93ec7..bea449d1a6c 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -14,6 +14,7 @@ using System.Linq; using NHibernate.Cfg; using NHibernate.Engine; +using NHibernate.Persister.Collection; using NHibernate.Type; using NHibernate.Util; @@ -308,13 +309,13 @@ private async Task GetResultFromCacheableAsync( } else { - var collectionTypes = new Dictionary(); + var collectionIndexes = new Dictionary(); var nonCollectionTypeIndexes = new List(); for (var i = 0; i < returnTypes.Length; i++) { if (returnTypes[i] is CollectionType collectionType) { - collectionTypes.Add(i, collectionType); + collectionIndexes.Add(i, session.Factory.GetCollectionPersister(collectionType.Role)); } else { @@ -336,9 +337,12 @@ private async Task GetResultFromCacheableAsync( // Initialization of the fetched collection must be done at the end in order to be able to batch fetch them // from the cache or database. The collections were already created in the previous for statement so we only // have to initialize them. - for (var i = 1; i < cacheable.Count; i++) + if (collectionIndexes.Count > 0) { - await (TypeHelper.InitializeCollectionsAsync((object[]) cacheable[i], (object[]) result[i - 1], collectionTypes, session, cancellationToken)).ConfigureAwait(false); + for (var i = 1; i < cacheable.Count; i++) + { + await (TypeHelper.InitializeCollectionsAsync((object[]) cacheable[i], (object[]) result[i - 1], collectionIndexes, session, cancellationToken)).ConfigureAwait(false); + } } } diff --git a/src/NHibernate/Async/Type/TypeHelper.cs b/src/NHibernate/Async/Type/TypeHelper.cs index 08a01f66479..3446d115249 100644 --- a/src/NHibernate/Async/Type/TypeHelper.cs +++ b/src/NHibernate/Async/Type/TypeHelper.cs @@ -14,6 +14,7 @@ using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Intercept; +using NHibernate.Persister.Collection; using NHibernate.Properties; using NHibernate.Tuple; @@ -106,17 +107,17 @@ internal static async Task AssembleAsync( /// /// The cached values. /// The assembled values to update. - /// The dictionary containing collection types and their indexes in the parameter as key. + /// The dictionary containing collection persisters and their indexes in the parameter as key. /// The originating session. /// A cancellation token that can be used to cancel the work internal static async Task InitializeCollectionsAsync( object[] cacheRow, object[] assembleRow, - IDictionary types, + IDictionary collectionIndexes, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - foreach (var pair in types) + foreach (var pair in collectionIndexes) { var value = cacheRow[pair.Key]; if (value == null) @@ -124,8 +125,7 @@ internal static async Task InitializeCollectionsAsync( continue; } - var persister = session.Factory.GetCollectionPersister(pair.Value.Role); - var collection = session.PersistenceContext.GetCollection(new CollectionKey(persister, value)); + var collection = session.PersistenceContext.GetCollection(new CollectionKey(pair.Value, value)); await (collection.ForceInitializationAsync(cancellationToken)).ConfigureAwait(false); assembleRow[pair.Key] = collection; } diff --git a/src/NHibernate/Cache/QueryCacheResultBuilder.cs b/src/NHibernate/Cache/QueryCacheResultBuilder.cs index 6c85bbf2f39..fa8ac989d10 100644 --- a/src/NHibernate/Cache/QueryCacheResultBuilder.cs +++ b/src/NHibernate/Cache/QueryCacheResultBuilder.cs @@ -53,6 +53,8 @@ internal QueryCacheResultBuilder(Loader.Loader loader) _collectionFetchIndexes.Add(i); } } + + _hasFetches = _hasFetches || _collectionFetchIndexes.Count > 0; } internal IList Result { get; } = new List(); diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 69439f0a93b..24f99968ac8 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -4,6 +4,7 @@ using System.Linq; using NHibernate.Cfg; using NHibernate.Engine; +using NHibernate.Persister.Collection; using NHibernate.Type; using NHibernate.Util; @@ -342,13 +343,13 @@ private IList GetResultFromCacheable( } else { - var collectionTypes = new Dictionary(); + var collectionIndexes = new Dictionary(); var nonCollectionTypeIndexes = new List(); for (var i = 0; i < returnTypes.Length; i++) { if (returnTypes[i] is CollectionType collectionType) { - collectionTypes.Add(i, collectionType); + collectionIndexes.Add(i, session.Factory.GetCollectionPersister(collectionType.Role)); } else { @@ -370,9 +371,12 @@ private IList GetResultFromCacheable( // Initialization of the fetched collection must be done at the end in order to be able to batch fetch them // from the cache or database. The collections were already created in the previous for statement so we only // have to initialize them. - for (var i = 1; i < cacheable.Count; i++) + if (collectionIndexes.Count > 0) { - TypeHelper.InitializeCollections((object[]) cacheable[i], (object[]) result[i - 1], collectionTypes, session); + for (var i = 1; i < cacheable.Count; i++) + { + TypeHelper.InitializeCollections((object[]) cacheable[i], (object[]) result[i - 1], collectionIndexes, session); + } } } diff --git a/src/NHibernate/Multi/ICachingInformation.cs b/src/NHibernate/Multi/ICachingInformation.cs index a695febe67a..5355c6498a8 100644 --- a/src/NHibernate/Multi/ICachingInformation.cs +++ b/src/NHibernate/Multi/ICachingInformation.cs @@ -77,13 +77,12 @@ public interface ICachingInformation internal static class CachingInformationExtensions { - // 6.0 TODO: Move to ICachingInformation as a Property + // 6.0 TODO: Remove and use CacheTypes instead. public static IType[] GetCacheTypes(this ICachingInformation cachingInformation) { - var loaderProperty = cachingInformation.GetType().GetProperty("Loader"); - if (loaderProperty?.GetValue(cachingInformation) is Loader.Loader loader) + if (cachingInformation is ICachingInformationWithFetches cachingInformationWithFetches) { - return loader.CacheTypes; + return cachingInformationWithFetches.CacheTypes; } #pragma warning disable 618 diff --git a/src/NHibernate/Multi/ICachingInformationWithFetches.cs b/src/NHibernate/Multi/ICachingInformationWithFetches.cs new file mode 100644 index 00000000000..5ef73d37627 --- /dev/null +++ b/src/NHibernate/Multi/ICachingInformationWithFetches.cs @@ -0,0 +1,13 @@ +using NHibernate.Type; + +namespace NHibernate.Multi +{ + // 6.0 TODO: merge into 'ICachingInformation'. + internal interface ICachingInformationWithFetches + { + /// + /// The query cache types. + /// + IType[] CacheTypes { get; } + } +} diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index 43ba89b325d..6655a88503d 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -22,7 +22,7 @@ public abstract partial class QueryBatchItemBase : IQueryBatchItem _finalResults; - protected class QueryInfo : ICachingInformation + protected class QueryInfo : ICachingInformation, ICachingInformationWithFetches { /// /// The query loader. @@ -56,6 +56,9 @@ protected class QueryInfo : ICachingInformation /// public IType[] ResultTypes => Loader.ResultTypes; + /// + public IType[] CacheTypes => Loader.CacheTypes; + /// public string QueryIdentifier => Loader.QueryIdentifier; diff --git a/src/NHibernate/Type/TypeHelper.cs b/src/NHibernate/Type/TypeHelper.cs index 77bd0d211bb..1e28e73c8a5 100644 --- a/src/NHibernate/Type/TypeHelper.cs +++ b/src/NHibernate/Type/TypeHelper.cs @@ -4,6 +4,7 @@ using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Intercept; +using NHibernate.Persister.Collection; using NHibernate.Properties; using NHibernate.Tuple; @@ -116,15 +117,15 @@ internal static object[] Assemble( /// /// The cached values. /// The assembled values to update. - /// The dictionary containing collection types and their indexes in the parameter as key. + /// The dictionary containing collection persisters and their indexes in the parameter as key. /// The originating session. internal static void InitializeCollections( object[] cacheRow, object[] assembleRow, - IDictionary types, + IDictionary collectionIndexes, ISessionImplementor session) { - foreach (var pair in types) + foreach (var pair in collectionIndexes) { var value = cacheRow[pair.Key]; if (value == null) @@ -132,8 +133,7 @@ internal static void InitializeCollections( continue; } - var persister = session.Factory.GetCollectionPersister(pair.Value.Role); - var collection = session.PersistenceContext.GetCollection(new CollectionKey(persister, value)); + var collection = session.PersistenceContext.GetCollection(new CollectionKey(pair.Value, value)); collection.ForceInitialization(); assembleRow[pair.Key] = collection; } From 52441016abb056853fe563235fd46d14c3f88019 Mon Sep 17 00:00:00 2001 From: maca88 Date: Wed, 30 Jan 2019 21:23:16 +0100 Subject: [PATCH 6/6] Removed shortcut --- src/NHibernate/Cache/QueryCacheResultBuilder.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/NHibernate/Cache/QueryCacheResultBuilder.cs b/src/NHibernate/Cache/QueryCacheResultBuilder.cs index fa8ac989d10..f3f145ddad3 100644 --- a/src/NHibernate/Cache/QueryCacheResultBuilder.cs +++ b/src/NHibernate/Cache/QueryCacheResultBuilder.cs @@ -27,20 +27,19 @@ internal QueryCacheResultBuilder(Loader.Loader loader) _resultTypes = loader.ResultTypes; _cacheTypes = loader.CacheTypes; - if (loader.EntityFetches == null) + if (loader.EntityFetches != null) { - return; - } - - for (var i = 0; i < loader.EntityFetches.Length; i++) - { - if (loader.EntityFetches[i]) + for (var i = 0; i < loader.EntityFetches.Length; i++) { - _entityFetchIndexes.Add(i); + if (loader.EntityFetches[i]) + { + _entityFetchIndexes.Add(i); + } } + + _hasFetches = _entityFetchIndexes.Count > 0; } - _hasFetches = _entityFetchIndexes.Count > 0; if (loader.CollectionFetches == null) { return;