Skip to content

Commit 12a96a9

Browse files
committed
Add support for caching fetched relations with linq query provider
1 parent aff89d4 commit 12a96a9

File tree

15 files changed

+630
-89
lines changed

15 files changed

+630
-89
lines changed

src/AsyncGenerator.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@
104104
- conversion: Ignore
105105
name: GetEnumerator
106106
containingTypeName: IFutureEnumerable
107+
# 6.0 TODO: Remove QueryCacheResultBuilder from ignore
108+
- conversion: Ignore
109+
containingTypeName: QueryCacheResultBuilder
107110
- conversion: ToAsync
108111
name: ExecuteReader
109112
containingTypeName: IBatcher

src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs

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

1111
using System.Linq;
1212
using NHibernate.Cfg;
13+
using NHibernate.DomainModel.Northwind.Entities;
1314
using NHibernate.Linq;
1415
using NUnit.Framework;
1516

@@ -281,5 +282,140 @@ public async Task CanBeCombinedWithFetchAsync()
281282
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "Unexpected cache put count");
282283
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count");
283284
}
285+
286+
[Test]
287+
public async Task FetchIsCachableAsync()
288+
{
289+
Sfi.Statistics.Clear();
290+
await (Sfi.EvictQueriesAsync());
291+
292+
Order order;
293+
294+
using (var s = Sfi.OpenSession())
295+
using (var t = s.BeginTransaction())
296+
{
297+
order = (await (s.Query<Order>()
298+
.WithOptions(o => o.SetCacheable(true))
299+
.Fetch(x => x.Customer)
300+
.FetchMany(x => x.OrderLines)
301+
.ThenFetch(x => x.Product)
302+
.ThenFetchMany(x => x.OrderLines)
303+
.Where(x => x.OrderId == 10248)
304+
.ToListAsync()))
305+
.First();
306+
307+
await (t.CommitAsync());
308+
}
309+
310+
AssertFetchedOrder(order);
311+
312+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count");
313+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count");
314+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count");
315+
316+
Sfi.Statistics.Clear();
317+
318+
using (var s = Sfi.OpenSession())
319+
using (var t = s.BeginTransaction())
320+
{
321+
order = (await (s.Query<Order>()
322+
.WithOptions(o => o.SetCacheable(true))
323+
.Fetch(x => x.Customer)
324+
.FetchMany(x => x.OrderLines)
325+
.ThenFetch(x => x.Product)
326+
.ThenFetchMany(x => x.OrderLines)
327+
.Where(x => x.OrderId == 10248)
328+
.ToListAsync()))
329+
.First();
330+
await (t.CommitAsync());
331+
}
332+
333+
AssertFetchedOrder(order);
334+
335+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count");
336+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count");
337+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
338+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count");
339+
340+
}
341+
342+
[Test]
343+
public async Task FutureFetchIsCachableAsync()
344+
{
345+
Sfi.Statistics.Clear();
346+
await (Sfi.EvictQueriesAsync());
347+
var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries;
348+
349+
Order order;
350+
351+
using (var s = Sfi.OpenSession())
352+
using (var t = s.BeginTransaction())
353+
{
354+
s.Query<Order>()
355+
.WithOptions(o => o.SetCacheable(true))
356+
.Fetch(x => x.Customer)
357+
.Where(x => x.OrderId == 10248)
358+
.ToFuture();
359+
360+
order = s.Query<Order>()
361+
.WithOptions(o => o.SetCacheable(true))
362+
.FetchMany(x => x.OrderLines)
363+
.ThenFetch(x => x.Product)
364+
.ThenFetchMany(x => x.OrderLines)
365+
.Where(x => x.OrderId == 10248)
366+
.ToFuture()
367+
.ToList()
368+
.First();
369+
370+
await (t.CommitAsync());
371+
}
372+
373+
AssertFetchedOrder(order);
374+
375+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(multiQueries ? 1 : 2), "Unexpected execution count");
376+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count");
377+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(2), "Unexpected cache miss count");
378+
379+
Sfi.Statistics.Clear();
380+
381+
using (var s = Sfi.OpenSession())
382+
using (var t = s.BeginTransaction())
383+
{
384+
s.Query<Order>()
385+
.WithOptions(o => o.SetCacheable(true))
386+
.Fetch(x => x.Customer)
387+
.Where(x => x.OrderId == 10248)
388+
.ToFuture();
389+
390+
order = s.Query<Order>()
391+
.WithOptions(o => o.SetCacheable(true))
392+
.FetchMany(x => x.OrderLines)
393+
.ThenFetch(x => x.Product)
394+
.ThenFetchMany(x => x.OrderLines)
395+
.Where(x => x.OrderId == 10248)
396+
.ToFuture()
397+
.ToList()
398+
.First();
399+
400+
await (t.CommitAsync());
401+
}
402+
403+
AssertFetchedOrder(order);
404+
405+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count");
406+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count");
407+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
408+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count");
409+
}
410+
411+
private static void AssertFetchedOrder(Order order)
412+
{
413+
Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized");
414+
Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized");
415+
Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items");
416+
var orderLine = order.OrderLines.First();
417+
Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized");
418+
Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized");
419+
}
284420
}
285421
}

src/NHibernate.Test/Linq/QueryCacheableTests.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Linq;
22
using NHibernate.Cfg;
3+
using NHibernate.DomainModel.Northwind.Entities;
34
using NHibernate.Linq;
45
using NUnit.Framework;
56

@@ -270,5 +271,140 @@ public void CanBeCombinedWithFetch()
270271
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "Unexpected cache put count");
271272
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count");
272273
}
274+
275+
[Test]
276+
public void FetchIsCachable()
277+
{
278+
Sfi.Statistics.Clear();
279+
Sfi.EvictQueries();
280+
281+
Order order;
282+
283+
using (var s = Sfi.OpenSession())
284+
using (var t = s.BeginTransaction())
285+
{
286+
order = s.Query<Order>()
287+
.WithOptions(o => o.SetCacheable(true))
288+
.Fetch(x => x.Customer)
289+
.FetchMany(x => x.OrderLines)
290+
.ThenFetch(x => x.Product)
291+
.ThenFetchMany(x => x.OrderLines)
292+
.Where(x => x.OrderId == 10248)
293+
.ToList()
294+
.First();
295+
296+
t.Commit();
297+
}
298+
299+
AssertFetchedOrder(order);
300+
301+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count");
302+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count");
303+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count");
304+
305+
Sfi.Statistics.Clear();
306+
307+
using (var s = Sfi.OpenSession())
308+
using (var t = s.BeginTransaction())
309+
{
310+
order = s.Query<Order>()
311+
.WithOptions(o => o.SetCacheable(true))
312+
.Fetch(x => x.Customer)
313+
.FetchMany(x => x.OrderLines)
314+
.ThenFetch(x => x.Product)
315+
.ThenFetchMany(x => x.OrderLines)
316+
.Where(x => x.OrderId == 10248)
317+
.ToList()
318+
.First();
319+
t.Commit();
320+
}
321+
322+
AssertFetchedOrder(order);
323+
324+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count");
325+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count");
326+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
327+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count");
328+
329+
}
330+
331+
[Test]
332+
public void FutureFetchIsCachable()
333+
{
334+
Sfi.Statistics.Clear();
335+
Sfi.EvictQueries();
336+
var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries;
337+
338+
Order order;
339+
340+
using (var s = Sfi.OpenSession())
341+
using (var t = s.BeginTransaction())
342+
{
343+
s.Query<Order>()
344+
.WithOptions(o => o.SetCacheable(true))
345+
.Fetch(x => x.Customer)
346+
.Where(x => x.OrderId == 10248)
347+
.ToFuture();
348+
349+
order = s.Query<Order>()
350+
.WithOptions(o => o.SetCacheable(true))
351+
.FetchMany(x => x.OrderLines)
352+
.ThenFetch(x => x.Product)
353+
.ThenFetchMany(x => x.OrderLines)
354+
.Where(x => x.OrderId == 10248)
355+
.ToFuture()
356+
.ToList()
357+
.First();
358+
359+
t.Commit();
360+
}
361+
362+
AssertFetchedOrder(order);
363+
364+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(multiQueries ? 1 : 2), "Unexpected execution count");
365+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count");
366+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(2), "Unexpected cache miss count");
367+
368+
Sfi.Statistics.Clear();
369+
370+
using (var s = Sfi.OpenSession())
371+
using (var t = s.BeginTransaction())
372+
{
373+
s.Query<Order>()
374+
.WithOptions(o => o.SetCacheable(true))
375+
.Fetch(x => x.Customer)
376+
.Where(x => x.OrderId == 10248)
377+
.ToFuture();
378+
379+
order = s.Query<Order>()
380+
.WithOptions(o => o.SetCacheable(true))
381+
.FetchMany(x => x.OrderLines)
382+
.ThenFetch(x => x.Product)
383+
.ThenFetchMany(x => x.OrderLines)
384+
.Where(x => x.OrderId == 10248)
385+
.ToFuture()
386+
.ToList()
387+
.First();
388+
389+
t.Commit();
390+
}
391+
392+
AssertFetchedOrder(order);
393+
394+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count");
395+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count");
396+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
397+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count");
398+
}
399+
400+
private static void AssertFetchedOrder(Order order)
401+
{
402+
Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized");
403+
Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized");
404+
Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items");
405+
var orderLine = order.OrderLines.First();
406+
Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized");
407+
Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized");
408+
}
273409
}
274410
}

src/NHibernate/Async/Cache/StandardQueryCache.cs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -296,39 +296,27 @@ private async Task<IList> GetResultFromCacheableAsync(
296296
var returnType = returnTypes[0];
297297

298298
// Skip first element, it is the timestamp
299-
var rows = new List<object>(cacheable.Count - 1);
300299
for (var i = 1; i < cacheable.Count; i++)
301300
{
302-
rows.Add(cacheable[i]);
301+
await (returnType.BeforeAssembleAsync(cacheable[i], session, cancellationToken)).ConfigureAwait(false);
303302
}
304303

305-
foreach (var row in rows)
306-
{
307-
await (returnType.BeforeAssembleAsync(row, session, cancellationToken)).ConfigureAwait(false);
308-
}
309-
310-
foreach (var row in rows)
304+
for (var i = 1; i < cacheable.Count; i++)
311305
{
312-
result.Add(await (returnType.AssembleAsync(row, session, null, cancellationToken)).ConfigureAwait(false));
306+
result.Add(await (returnType.AssembleAsync(cacheable[i], session, null, cancellationToken)).ConfigureAwait(false));
313307
}
314308
}
315309
else
316310
{
317311
// Skip first element, it is the timestamp
318-
var rows = new List<object[]>(cacheable.Count - 1);
319312
for (var i = 1; i < cacheable.Count; i++)
320313
{
321-
rows.Add((object[]) cacheable[i]);
314+
await (TypeHelper.BeforeAssembleAsync((object[]) cacheable[i], returnTypes, session, cancellationToken)).ConfigureAwait(false);
322315
}
323316

324-
foreach (var row in rows)
325-
{
326-
await (TypeHelper.BeforeAssembleAsync(row, returnTypes, session, cancellationToken)).ConfigureAwait(false);
327-
}
328-
329-
foreach (var row in rows)
317+
for (var i = 1; i < cacheable.Count; i++)
330318
{
331-
result.Add(await (TypeHelper.AssembleAsync(row, returnTypes, session, null, cancellationToken)).ConfigureAwait(false));
319+
result.Add(await (TypeHelper.AssembleAsync((object[]) cacheable[i], returnTypes, session, null, cancellationToken)).ConfigureAwait(false));
332320
}
333321
}
334322

src/NHibernate/Async/Loader/Hql/QueryLoader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Collections.Generic;
1414
using System.Data.Common;
1515
using System.Diagnostics;
16+
using System.Linq;
1617
using NHibernate.Engine;
1718
using NHibernate.Event;
1819
using NHibernate.Hql.Ast.ANTLR;

0 commit comments

Comments
 (0)