Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

using System.Linq;
using NHibernate.Cfg;
using NHibernate.DomainModel.Northwind.Entities;
using NHibernate.Linq;
using NUnit.Framework;

Expand Down Expand Up @@ -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<Order>()
.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<Order>()
.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<Order>()
.WithOptions(o => o.SetCacheable(true))
.Fetch(x => x.Customer)
.Where(x => x.OrderId == 10248)
.ToFuture();

order = s.Query<Order>()
.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<Order>()
.WithOptions(o => o.SetCacheable(true))
.Fetch(x => x.Customer)
.Where(x => x.OrderId == 10248)
.ToFuture();

order = s.Query<Order>()
.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");
}
}
}
136 changes: 136 additions & 0 deletions src/NHibernate.Test/Linq/QueryCacheableTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq;
using NHibernate.Cfg;
using NHibernate.DomainModel.Northwind.Entities;
using NHibernate.Linq;
using NUnit.Framework;

Expand Down Expand Up @@ -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<Order>()
.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<Order>()
.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<Order>()
.WithOptions(o => o.SetCacheable(true))
.Fetch(x => x.Customer)
.Where(x => x.OrderId == 10248)
.ToFuture();

order = s.Query<Order>()
.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<Order>()
.WithOptions(o => o.SetCacheable(true))
.Fetch(x => x.Customer)
.Where(x => x.OrderId == 10248)
.ToFuture();

order = s.Query<Order>()
.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");
}
}
}
44 changes: 29 additions & 15 deletions src/NHibernate/Async/Cache/StandardQueryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Linq;
using NHibernate.Cfg;
using NHibernate.Engine;
using NHibernate.Persister.Collection;
using NHibernate.Type;
using NHibernate.Util;

Expand Down Expand Up @@ -296,39 +297,52 @@ private async Task<IList> GetResultFromCacheableAsync(
var returnType = returnTypes[0];

// Skip first element, it is the timestamp
var rows = new List<object>(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
{
var collectionIndexes = new Dictionary<int, ICollectionPersister>();
var nonCollectionTypeIndexes = new List<int>();
for (var i = 0; i < returnTypes.Length; i++)
{
if (returnTypes[i] is CollectionType collectionType)
{
collectionIndexes.Add(i, session.Factory.GetCollectionPersister(collectionType.Role));
}
else
{
nonCollectionTypeIndexes.Add(i);
}
}

// Skip first element, it is the timestamp
var rows = new List<object[]>(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)
for (var i = 1; i < cacheable.Count; i++)
{
await (TypeHelper.BeforeAssembleAsync(row, returnTypes, session, cancellationToken)).ConfigureAwait(false);
result.Add(await (TypeHelper.AssembleAsync((object[]) cacheable[i], returnTypes, nonCollectionTypeIndexes, session, cancellationToken)).ConfigureAwait(false));
}

foreach (var row in rows)
// 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.
if (collectionIndexes.Count > 0)
{
result.Add(await (TypeHelper.AssembleAsync(row, returnTypes, session, null, 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);
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/NHibernate/Async/Impl/MultiCriteriaImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Async/Impl/MultiQueryImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ protected async Task<List<object>> 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);

Expand Down
Loading