Skip to content

Commit 49d3ba7

Browse files
fixup! Implement multiple get and put for query cache and query batch
Refactor that nasty QueryInformation thing
1 parent 4454999 commit 49d3ba7

13 files changed

+199
-143
lines changed

src/NHibernate/Async/Cache/StandardQueryCache.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,9 @@ private async Task<IList> GetResultFromCacheableAsync(
351351
// the UnresolvableObjectException could occur while resolving
352352
// associations, leaving the PC in an inconsistent state
353353
Log.Debug(ex, "could not reassemble cached result set");
354-
// TODO: add and handle a RemoveMany?
354+
// Handling a RemoveMany here does not look worth it, as this case short-circuits
355+
// the result-set. So a Many could only benefit batched queries, and only if many
356+
// of them are natural key lookup with an unresolvable object case.
355357
await (_queryCache.RemoveAsync(key, cancellationToken)).ConfigureAwait(false);
356358
return null;
357359
}

src/NHibernate/Async/Multi/CriteriaBatchItem.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
using System;
1212
using System.Collections.Generic;
13+
using NHibernate.Engine;
1314
using NHibernate.Impl;
1415
using NHibernate.Loader.Criteria;
1516
using NHibernate.Persister.Entity;

src/NHibernate/Async/Multi/IQueryBatchItem.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ public partial interface IQueryBatchItem
2525
{
2626

2727
/// <summary>
28-
/// Immediate query execution in case the dialect does not support batches
28+
/// Execute immediately the query as a single standalone query. Used in case the data-provider
29+
/// does not support batches.
2930
/// </summary>
3031
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
3132
Task ExecuteNonBatchedAsync(CancellationToken cancellationToken);
3233

3334
/// <summary>
34-
/// Initialize loaded entities and collections.
35+
/// Initialize entities and collections loaded from database.
3536
/// </summary>
3637
/// <param name="reader">The reader having loaded the query.</param>
3738
/// <param name="cacheBatcher">A cache batcher for batching cache puts of entities and collections.</param>

src/NHibernate/Async/Multi/QueryBatch.cs

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ private async Task ExecuteBatchedAsync(CancellationToken cancellationToken)
106106
stopWatch.Start();
107107
}
108108

109-
var querySpaces = new HashSet<string>(
110-
_queries.SelectMany(q => q.QueryInformation).SelectMany(qi => qi.QuerySpaces));
109+
var querySpaces = new HashSet<string>(_queries.SelectMany(q => q.GetQuerySpaces()));
111110
if (querySpaces.Count > 0)
112111
{
113112
// The auto-flush must be handled before obtaining the commands, because an auto-flush
@@ -169,22 +168,16 @@ private async Task ExecuteBatchedAsync(CancellationToken cancellationToken)
169168
}
170169
}
171170

172-
private async Task<IDictionary<IQueryInformation, IList>> GetCachedResultsAsync(CancellationToken cancellationToken)
171+
private async Task<IDictionary<ICachingInformation, IList>> GetCachedResultsAsync(CancellationToken cancellationToken)
173172
{
174173
cancellationToken.ThrowIfCancellationRequested();
175-
var resultByQueryInfo = new Dictionary<IQueryInformation, IList>();
174+
var resultByQueryInfo = new Dictionary<ICachingInformation, IList>();
176175
var statisticsEnabled = Session.Factory.Statistics.IsStatisticsEnabled;
177-
var queriesByCaches =
178-
_queries
179-
.SelectMany(q => q.QueryInformation)
180-
.Where(q => q.Loader.IsCacheable(q.Parameters) && q.Loader.CanGetFromCache(Session, q.Parameters))
181-
.GroupBy(
182-
q => Session.Factory.GetQueryCache(q.Parameters.CacheRegion),
183-
(cache, q) => new { cache, queryInfos = q });
184-
176+
var queriesByCaches = GetQueriesByCaches(ci => ci.CanGetFromCache);
185177
foreach (var queriesByCache in queriesByCaches)
186178
{
187-
var queryInfos = queriesByCache.queryInfos.ToArray();
179+
var queryInfos = queriesByCache.ToArray();
180+
var cache = queriesByCache.Key;
188181
var keys = new QueryKey[queryInfos.Length];
189182
var parameters = new QueryParameters[queryInfos.Length];
190183
var returnTypes = new ICacheAssembler[queryInfos.Length][];
@@ -196,26 +189,26 @@ private async Task<IDictionary<IQueryInformation, IList>> GetCachedResultsAsync(
196189
parameters[i] = queryInfo.Parameters;
197190
returnTypes[i] = queryInfo.Parameters.HasAutoDiscoverScalarTypes
198191
? null
199-
: queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.Loader.ResultTypes);
192+
: queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.ResultTypes);
200193
spaces[i] = queryInfo.QuerySpaces;
201194
}
202195

203-
var results = await (queriesByCache.cache.GetManyAsync(keys, parameters, returnTypes, spaces, Session, cancellationToken)).ConfigureAwait(false);
196+
var results = await (cache.GetManyAsync(keys, parameters, returnTypes, spaces, Session, cancellationToken)).ConfigureAwait(false);
204197

205198
for (var i = 0; i < queryInfos.Length; i++)
206199
{
207200
resultByQueryInfo.Add(queryInfos[i], results[i]);
208201

209202
if (statisticsEnabled)
210203
{
211-
var queryIdentifier = queryInfos[i].Loader.QueryIdentifier;
204+
var queryIdentifier = queryInfos[i].QueryIdentifier;
212205
if (results[i] == null)
213206
{
214-
Session.Factory.StatisticsImplementor.QueryCacheMiss(queryIdentifier, queriesByCache.cache.RegionName);
207+
Session.Factory.StatisticsImplementor.QueryCacheMiss(queryIdentifier, cache.RegionName);
215208
}
216209
else
217210
{
218-
Session.Factory.StatisticsImplementor.QueryCacheHit(queryIdentifier, queriesByCache.cache.RegionName);
211+
Session.Factory.StatisticsImplementor.QueryCacheHit(queryIdentifier, cache.RegionName);
219212
}
220213
}
221214
}
@@ -231,17 +224,11 @@ private async Task PutCacheableResultsAsync(CancellationToken cancellationToken)
231224
return;
232225

233226
var statisticsEnabled = Session.Factory.Statistics.IsStatisticsEnabled;
234-
var queriesByCaches =
235-
_queries
236-
.SelectMany(q => q.QueryInformation)
237-
.Where(q => q.CachePutRequired)
238-
.GroupBy(
239-
q => Session.Factory.GetQueryCache(q.Parameters.CacheRegion),
240-
(cache, q) => new { cache, queryInfos = q });
241-
227+
var queriesByCaches = GetQueriesByCaches(ci => ci.CachePutRequired);
242228
foreach (var queriesByCache in queriesByCaches)
243229
{
244-
var queryInfos = queriesByCache.queryInfos.ToArray();
230+
var queryInfos = queriesByCache.ToArray();
231+
var cache = queriesByCache.Key;
245232
var keys = new QueryKey[queryInfos.Length];
246233
var parameters = new QueryParameters[queryInfos.Length];
247234
var returnTypes = new ICacheAssembler[queryInfos.Length][];
@@ -251,11 +238,11 @@ private async Task PutCacheableResultsAsync(CancellationToken cancellationToken)
251238
var queryInfo = queryInfos[i];
252239
keys[i] = queryInfo.CacheKey;
253240
parameters[i] = queryInfo.Parameters;
254-
returnTypes[i] = queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.Loader.ResultTypes);
241+
returnTypes[i] = queryInfo.CacheKey.ResultTransformer.GetCachedResultTypes(queryInfo.ResultTypes);
255242
results[i] = queryInfo.Result;
256243
}
257244

258-
var putted = await (queriesByCache.cache.PutManyAsync(keys, parameters, returnTypes, results, Session, cancellationToken)).ConfigureAwait(false);
245+
var putted = await (cache.PutManyAsync(keys, parameters, returnTypes, results, Session, cancellationToken)).ConfigureAwait(false);
259246

260247
if (!statisticsEnabled)
261248
continue;
@@ -265,7 +252,7 @@ private async Task PutCacheableResultsAsync(CancellationToken cancellationToken)
265252
if (putted[i])
266253
{
267254
Session.Factory.StatisticsImplementor.QueryCachePut(
268-
queryInfos[i].Loader.QueryIdentifier, queriesByCache.cache.RegionName);
255+
queryInfos[i].QueryIdentifier, cache.RegionName);
269256
}
270257
}
271258
}

src/NHibernate/Async/Multi/QueryBatchItemBase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using NHibernate.Cache;
1717
using NHibernate.Engine;
1818
using NHibernate.SqlCommand;
19+
using NHibernate.Type;
1920
using NHibernate.Util;
2021

2122
namespace NHibernate.Multi

src/NHibernate/Cache/StandardQueryCache.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,9 @@ private IList GetResultFromCacheable(
376376
// the UnresolvableObjectException could occur while resolving
377377
// associations, leaving the PC in an inconsistent state
378378
Log.Debug(ex, "could not reassemble cached result set");
379-
// TODO: add and handle a RemoveMany?
379+
// Handling a RemoveMany here does not look worth it, as this case short-circuits
380+
// the result-set. So a Many could only benefit batched queries, and only if many
381+
// of them are natural key lookup with an unresolvable object case.
380382
_queryCache.Remove(key);
381383
return null;
382384
}

src/NHibernate/Multi/CriteriaBatchItem.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using NHibernate.Engine;
34
using NHibernate.Impl;
45
using NHibernate.Loader.Criteria;
56
using NHibernate.Persister.Entity;
@@ -15,7 +16,7 @@ public CriteriaBatchItem(ICriteria query)
1516
_criteria = (CriteriaImpl) query ?? throw new ArgumentNullException(nameof(query));
1617
}
1718

18-
protected override List<QueryInfo> GetQueryInformation()
19+
protected override List<QueryInfo> GetQueryInformation(ISessionImplementor session)
1920
{
2021
var factory = Session.Factory;
2122
//for detached criteria
@@ -35,13 +36,7 @@ protected override List<QueryInfo> GetQueryInformation()
3536
Session.EnabledFilters
3637
);
3738

38-
list.Add(
39-
new QueryInfo()
40-
{
41-
Loader = loader,
42-
Parameters = loader.Translator.GetQueryParameters(),
43-
QuerySpaces = loader.QuerySpaces,
44-
});
39+
list.Add(new QueryInfo(loader.Translator.GetQueryParameters(), loader, loader.QuerySpaces, session));
4540
}
4641

4742
return list;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using NHibernate.Cache;
4+
using NHibernate.Engine;
5+
using NHibernate.Type;
6+
7+
namespace NHibernate.Multi
8+
{
9+
/// <summary>
10+
/// Querying information.
11+
/// </summary>
12+
public interface ICachingInformation
13+
{
14+
/// <summary>
15+
/// The query parameters.
16+
/// </summary>
17+
QueryParameters Parameters { get; }
18+
19+
/// <summary>
20+
/// The query result.
21+
/// </summary>
22+
IList Result { get; }
23+
24+
/// <summary>
25+
/// The query spaces.
26+
/// </summary>
27+
/// <remarks>
28+
/// Query spaces indicates which entity classes are used by the query and need to be flushed
29+
/// when auto-flush is enabled. It also indicates which cache update timestamps needs to be
30+
/// checked for up-to-date-ness.
31+
/// </remarks>
32+
ISet<string> QuerySpaces { get; }
33+
34+
/// <summary>
35+
/// Is the query cacheable?
36+
/// </summary>
37+
bool IsCacheable { get; }
38+
39+
/// <summary>
40+
/// Can the query be obtained from cache?
41+
/// </summary>
42+
bool CanGetFromCache { get; }
43+
44+
/// <summary>
45+
/// Should the query results be put in cache?
46+
/// </summary>
47+
bool CachePutRequired { get; }
48+
49+
/// <summary>
50+
/// The query result types.
51+
/// </summary>
52+
IType[] ResultTypes { get; }
53+
54+
/// <summary>
55+
/// The query identifier, for statistics purpose.
56+
/// </summary>
57+
string QueryIdentifier { get; }
58+
59+
/// <summary>
60+
/// The query cache key.
61+
/// </summary>
62+
QueryKey CacheKey { get; }
63+
}
64+
}

src/NHibernate/Multi/IQueryBatchItem.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,43 +32,57 @@ public interface IQueryBatchItem<TResult> : IQueryBatchItem
3232
public partial interface IQueryBatchItem
3333
{
3434
/// <summary>
35-
/// The query loading information.
35+
/// Optionally, the query caching information list, for batching. Each element matches
36+
/// a SQL-Query resulting from the query translation, in the order they are translated.
37+
/// It should be <see langword="null" /> if no batching of caching is handled for this
38+
/// query.
3639
/// </summary>
37-
IEnumerable<IQueryInformation> QueryInformation { get; }
40+
IEnumerable<ICachingInformation> CachingInformation { get; }
3841

3942
/// <summary>
40-
/// Method is called right before batch execution.
43+
/// Initialize the query. Method is called right before batch execution.
4144
/// Can be used for various delayed initialization logic.
4245
/// </summary>
4346
/// <param name="session"></param>
4447
void Init(ISessionImplementor session);
48+
49+
/// <summary>
50+
/// Get the query spaces.
51+
/// </summary>
52+
/// <remarks>
53+
/// Query spaces indicates which entity classes are used by the query and need to be flushed
54+
/// when auto-flush is enabled. It also indicates which cache update timestamps needs to be
55+
/// checked for up-to-date-ness.
56+
/// </remarks>
57+
IEnumerable<string> GetQuerySpaces();
4558

4659
/// <summary>
47-
/// Gets the commands to execute for getting the not-already cached results of this query.
60+
/// Get the commands to execute for getting the not-already cached results of this query.
4861
/// </summary>
4962
/// <param name="cachedResults">The cached results.</param>
5063
/// <returns>The commands for obtaining the results not already cached.</returns>
51-
IEnumerable<ISqlCommand> GetCommands(IDictionary<IQueryInformation, IList> cachedResults);
64+
IEnumerable<ISqlCommand> GetCommands(IDictionary<ICachingInformation, IList> cachedResults);
5265

5366
/// <summary>
54-
/// Returns delegates for processing result sets generated by <see cref="GetCommands"/>.
67+
/// Return delegates for processing result sets generated by <see cref="GetCommands"/>.
5568
/// Delegate should return the number of rows loaded by command.
5669
/// </summary>
5770
/// <returns></returns>
5871
IEnumerable<Func<DbDataReader, int>> GetResultSetHandler();
5972

6073
/// <summary>
61-
/// Executed after all commands in batch are processed
74+
/// Process the results of commands executed for the query.
6275
/// </summary>
6376
void ProcessResults();
6477

6578
/// <summary>
66-
/// Immediate query execution in case the dialect does not support batches
79+
/// Execute immediately the query as a single standalone query. Used in case the data-provider
80+
/// does not support batches.
6781
/// </summary>
6882
void ExecuteNonBatched();
6983

7084
/// <summary>
71-
/// Initialize loaded entities and collections.
85+
/// Initialize entities and collections loaded from database.
7286
/// </summary>
7387
/// <param name="reader">The reader having loaded the query.</param>
7488
/// <param name="cacheBatcher">A cache batcher for batching cache puts of entities and collections.</param>

src/NHibernate/Multi/IQueryInformation.cs

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)