Skip to content

Commit af9021d

Browse files
Fix shortcomings of QueryBatcher initial implementation (#1789)
* AutoFlush is handled too late, causing stale data to be yielded * Cacheable result transformation is not fully done * Auto discovery of types fails * InitializeEntitiesAndCollections may be wrongly skipped or called * DoExecute should be renamed ExecuteBatched * BeginProcess should span the whole processing Follow up to #1718. See its reviews for more details on the above shortcomings.
1 parent 25777a3 commit af9021d

File tree

8 files changed

+565
-186
lines changed

8 files changed

+565
-186
lines changed

src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,116 @@ public async Task FutureForEagerMappedCollectionAsync()
278278
}
279279
}
280280

281+
[Test]
282+
public async Task AutoDiscoverWorksWithFutureAsync()
283+
{
284+
using (var s = OpenSession())
285+
using (var t = s.BeginTransaction())
286+
{
287+
var future =
288+
s
289+
.CreateSQLQuery("select count(*) as childCount from EntitySimpleChild where Name like :pattern")
290+
.AddScalar("childCount", NHibernateUtil.Int64)
291+
.SetString("pattern", "Chi%")
292+
.SetCacheable(true)
293+
.FutureValue<long>();
294+
295+
Assert.That(await (future.GetValueAsync()), Is.EqualTo(2L), "From DB");
296+
await (t.CommitAsync());
297+
}
298+
299+
using (var s = OpenSession())
300+
using (var t = s.BeginTransaction())
301+
{
302+
var future =
303+
s
304+
.CreateSQLQuery("select count(*) as childCount from EntitySimpleChild where Name like :pattern")
305+
.AddScalar("childCount", NHibernateUtil.Int64)
306+
.SetString("pattern", "Chi%")
307+
.SetCacheable(true)
308+
.FutureValue<long>();
309+
310+
Assert.That(await (future.GetValueAsync()), Is.EqualTo(2L), "From cache");
311+
await (t.CommitAsync());
312+
}
313+
}
314+
315+
[Test]
316+
public async Task AutoFlushCacheInvalidationWorksWithFutureAsync()
317+
{
318+
using (var s = OpenSession())
319+
using (var t = s.BeginTransaction())
320+
{
321+
var futureResults =
322+
(await (s
323+
.CreateQuery("from EntitySimpleChild")
324+
.SetCacheable(true)
325+
.Future<EntitySimpleChild>()
326+
.GetEnumerableAsync()))
327+
.ToList();
328+
329+
Assert.That(futureResults, Has.Count.EqualTo(2), "First call");
330+
331+
await (t.CommitAsync());
332+
}
333+
334+
using (var s = OpenSession())
335+
using (var t = s.BeginTransaction())
336+
{
337+
var deleted = await (s.Query<EntitySimpleChild>().FirstAsync());
338+
// We need to get rid of a referencing entity for the delete.
339+
deleted.Parent.Child1 = null;
340+
deleted.Parent.Child2 = null;
341+
await (s.DeleteAsync(deleted));
342+
343+
var future =
344+
s
345+
.CreateQuery("from EntitySimpleChild")
346+
.SetCacheable(true)
347+
.Future<EntitySimpleChild>();
348+
349+
Assert.That((await (future.GetEnumerableAsync())).ToList(), Has.Count.EqualTo(1), "After delete");
350+
await (t.CommitAsync());
351+
}
352+
}
353+
354+
[Test]
355+
public async Task UsingHqlToFutureWithCacheAndTransformerDoesntThrowAsync()
356+
{
357+
// Adapted from #383
358+
using (var session = OpenSession())
359+
using (var t = session.BeginTransaction())
360+
{
361+
//store values in cache
362+
await (session
363+
.CreateQuery("from EntitySimpleChild")
364+
.SetResultTransformer(Transformers.DistinctRootEntity)
365+
.SetCacheable(true)
366+
.SetCacheMode(CacheMode.Normal)
367+
.Future<EntitySimpleChild>()
368+
.GetEnumerableAsync());
369+
await (t.CommitAsync());
370+
}
371+
372+
using (var session = OpenSession())
373+
using (var t = session.BeginTransaction())
374+
{
375+
//get values from cache
376+
var results =
377+
(await (session
378+
.CreateQuery("from EntitySimpleChild")
379+
.SetResultTransformer(Transformers.DistinctRootEntity)
380+
.SetCacheable(true)
381+
.SetCacheMode(CacheMode.Normal)
382+
.Future<EntitySimpleChild>()
383+
.GetEnumerableAsync()))
384+
.ToList();
385+
386+
Assert.That(results.Count, Is.EqualTo(2));
387+
await (t.CommitAsync());
388+
}
389+
}
390+
281391
#region Test Setup
282392

283393
protected override HbmMapping GetMappings()

src/NHibernate.Test/Futures/QueryBatchFixture.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,116 @@ public void FutureForEagerMappedCollection()
266266
}
267267
}
268268

269+
[Test]
270+
public void AutoDiscoverWorksWithFuture()
271+
{
272+
using (var s = OpenSession())
273+
using (var t = s.BeginTransaction())
274+
{
275+
var future =
276+
s
277+
.CreateSQLQuery("select count(*) as childCount from EntitySimpleChild where Name like :pattern")
278+
.AddScalar("childCount", NHibernateUtil.Int64)
279+
.SetString("pattern", "Chi%")
280+
.SetCacheable(true)
281+
.FutureValue<long>();
282+
283+
Assert.That(future.Value, Is.EqualTo(2L), "From DB");
284+
t.Commit();
285+
}
286+
287+
using (var s = OpenSession())
288+
using (var t = s.BeginTransaction())
289+
{
290+
var future =
291+
s
292+
.CreateSQLQuery("select count(*) as childCount from EntitySimpleChild where Name like :pattern")
293+
.AddScalar("childCount", NHibernateUtil.Int64)
294+
.SetString("pattern", "Chi%")
295+
.SetCacheable(true)
296+
.FutureValue<long>();
297+
298+
Assert.That(future.Value, Is.EqualTo(2L), "From cache");
299+
t.Commit();
300+
}
301+
}
302+
303+
[Test]
304+
public void AutoFlushCacheInvalidationWorksWithFuture()
305+
{
306+
using (var s = OpenSession())
307+
using (var t = s.BeginTransaction())
308+
{
309+
var futureResults =
310+
s
311+
.CreateQuery("from EntitySimpleChild")
312+
.SetCacheable(true)
313+
.Future<EntitySimpleChild>()
314+
.GetEnumerable()
315+
.ToList();
316+
317+
Assert.That(futureResults, Has.Count.EqualTo(2), "First call");
318+
319+
t.Commit();
320+
}
321+
322+
using (var s = OpenSession())
323+
using (var t = s.BeginTransaction())
324+
{
325+
var deleted = s.Query<EntitySimpleChild>().First();
326+
// We need to get rid of a referencing entity for the delete.
327+
deleted.Parent.Child1 = null;
328+
deleted.Parent.Child2 = null;
329+
s.Delete(deleted);
330+
331+
var future =
332+
s
333+
.CreateQuery("from EntitySimpleChild")
334+
.SetCacheable(true)
335+
.Future<EntitySimpleChild>();
336+
337+
Assert.That(future.GetEnumerable().ToList(), Has.Count.EqualTo(1), "After delete");
338+
t.Commit();
339+
}
340+
}
341+
342+
[Test]
343+
public void UsingHqlToFutureWithCacheAndTransformerDoesntThrow()
344+
{
345+
// Adapted from #383
346+
using (var session = OpenSession())
347+
using (var t = session.BeginTransaction())
348+
{
349+
//store values in cache
350+
session
351+
.CreateQuery("from EntitySimpleChild")
352+
.SetResultTransformer(Transformers.DistinctRootEntity)
353+
.SetCacheable(true)
354+
.SetCacheMode(CacheMode.Normal)
355+
.Future<EntitySimpleChild>()
356+
.GetEnumerable();
357+
t.Commit();
358+
}
359+
360+
using (var session = OpenSession())
361+
using (var t = session.BeginTransaction())
362+
{
363+
//get values from cache
364+
var results =
365+
session
366+
.CreateQuery("from EntitySimpleChild")
367+
.SetResultTransformer(Transformers.DistinctRootEntity)
368+
.SetCacheable(true)
369+
.SetCacheMode(CacheMode.Normal)
370+
.Future<EntitySimpleChild>()
371+
.GetEnumerable()
372+
.ToList();
373+
374+
Assert.That(results.Count, Is.EqualTo(2));
375+
t.Commit();
376+
}
377+
}
378+
269379
#region Test Setup
270380

271381
protected override HbmMapping GetMappings()

src/NHibernate/Async/Multi/IQueryBatchItem.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,30 @@ public partial interface IQueryBatchItem
2323
{
2424

2525
/// <summary>
26-
/// Returns commands generated by query
26+
/// Get the commands to execute for getting the not-already cached results of this query.
2727
/// </summary>
2828
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
29+
/// <returns>The commands for obtaining the results not already cached.</returns>
2930
Task<IEnumerable<ISqlCommand>> GetCommandsAsync(CancellationToken cancellationToken);
3031

3132
/// <summary>
32-
/// Executed after all commands in batch are processed
33+
/// Process the result sets generated by <see cref="GetCommands"/>. Advance the results set
34+
/// to the next query, or to its end if this is the last query.
35+
/// </summary>
36+
/// <returns>The number of rows processed.</returns>
37+
Task<int> ProcessResultsSetAsync(DbDataReader reader, CancellationToken cancellationToken);
38+
39+
/// <summary>
40+
/// Process the results of the query, including cached results.
3341
/// </summary>
3442
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
43+
/// <remarks>Any result from the database must have been previously processed
44+
/// through <see cref="ProcessResultsSet"/>.</remarks>
3545
Task ProcessResultsAsync(CancellationToken cancellationToken);
3646

3747
/// <summary>
38-
/// Immediate query execution in case the dialect does not support batches
48+
/// Execute immediately the query as a single standalone query. Used in case the data-provider
49+
/// does not support batches.
3950
/// </summary>
4051
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
4152
Task ExecuteNonBatchedAsync(CancellationToken cancellationToken);

0 commit comments

Comments
 (0)