From b971b1e63188586520d4b770df3e25539f4fceef Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 27 Mar 2019 12:48:18 +0200 Subject: [PATCH 1/9] Add support for caching fetched relations with Criteria --- .../Northwind/Entities/Northwind.cs | 4 +- .../Northwind/Entities/NorthwindQueryOver.cs | 102 ++++++++ .../ReadonlyTests/QueryOverCacheableTests.cs | 241 ++++++++++++++++++ .../CriteriaNorthwindReadonlyTestCase.cs | 67 +++++ .../ReadonlyTests/QueryOverCacheableTests.cs | 230 +++++++++++++++++ .../ReadonlyTests/_ReadonlyFixtureSetUp.cs | 12 + src/NHibernate/Async/Loader/Loader.cs | 1 - .../Cache/QueryCacheResultBuilder.cs | 55 +--- .../Hql/Ast/ANTLR/Tree/SelectClause.cs | 2 - .../Loader/Criteria/CriteriaLoader.cs | 6 +- src/NHibernate/Loader/Custom/CustomLoader.cs | 2 +- src/NHibernate/Loader/Hql/QueryLoader.cs | 24 +- src/NHibernate/Loader/Loader.cs | 56 +++- src/NHibernate/Loader/OuterJoinLoader.cs | 2 +- src/NHibernate/Util/ArrayHelper.cs | 9 + src/NHibernate/Util/EnumerableExtensions.cs | 5 + 16 files changed, 743 insertions(+), 75 deletions(-) create mode 100644 src/NHibernate.DomainModel/Northwind/Entities/NorthwindQueryOver.cs create mode 100644 src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs create mode 100644 src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs create mode 100644 src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs create mode 100644 src/NHibernate.Test/Criteria/ReadonlyTests/_ReadonlyFixtureSetUp.cs diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs index 5da2e35cadb..c4cbda23f26 100755 --- a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs @@ -94,9 +94,9 @@ public IQueryable Role get { return _session.Query(); } } - public IEnumerable IUsers + public IQueryable IUsers { get { return _session.Query(); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/NorthwindQueryOver.cs b/src/NHibernate.DomainModel/Northwind/Entities/NorthwindQueryOver.cs new file mode 100644 index 00000000000..0910e58ce99 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/NorthwindQueryOver.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Linq; + +namespace NHibernate.DomainModel.Northwind.Entities +{ + public class NorthwindQueryOver + { + private readonly ISession _session; + + public NorthwindQueryOver(ISession session) + { + _session = session; + } + + public IQueryOver Customers + { + get { return _session.QueryOver(); } + } + + public IQueryOver Products + { + get { return _session.QueryOver(); } + } + + public IQueryOver Shippers + { + get { return _session.QueryOver(); } + } + + public IQueryOver Orders + { + get { return _session.QueryOver(); } + } + + public IQueryOver OrderLines + { + get { return _session.QueryOver(); } + } + + public IQueryOver Employees + { + get { return _session.QueryOver(); } + } + + public IQueryOver Categories + { + get { return _session.QueryOver(); } + } + + public IQueryOver Timesheets + { + get { return _session.QueryOver(); } + } + + public IQueryOver Animals + { + get { return _session.QueryOver(); } + } + + public IQueryOver Mammals + { + get { return _session.QueryOver(); } + } + + public IQueryOver Users + { + get { return _session.QueryOver(); } + } + + public IQueryOver PatientRecords + { + get { return _session.QueryOver(); } + } + + public IQueryOver States + { + get { return _session.QueryOver(); } + } + + public IQueryOver Patients + { + get { return _session.QueryOver(); } + } + + public IQueryOver Physicians + { + get { return _session.QueryOver(); } + } + + public IQueryOver Role + { + get { return _session.QueryOver(); } + } + + public IQueryOver IUsers + { + get { return _session.QueryOver(); } + } + } +} diff --git a/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs new file mode 100644 index 00000000000..307099775a0 --- /dev/null +++ b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -0,0 +1,241 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + using System.Threading.Tasks; + [TestFixture] + public class QueryOverCacheableTestsAsync : CriteriaNorthwindReadonlyTestCase + { + //Just for discoverability + private class CriteriaCacheableTest{} + + protected override void Configure(Configuration cfg) + { + cfg.SetProperty(Environment.UseQueryCache, "true"); + cfg.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(cfg); + } + + [Test] + public async Task QueryIsCacheableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + var x = await (db.Customers.Cacheable().ListAsync()); + var x2 = await (db.Customers.Cacheable().ListAsync()); + + 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.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public async Task QueryIsCacheable2Async() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + var x = await (db.Customers.Cacheable().ListAsync()); + var x2 = await (db.Customers.ListAsync()); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "Unexpected cache hit count"); + } + + [Test] + public async Task QueryIsCacheableWithRegionAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + await (Sfi.EvictQueriesAsync("test")); + await (Sfi.EvictQueriesAsync("other")); + + var x = await (db.Customers.Cacheable().CacheRegion("test").ListAsync()); + var x2 = await (db.Customers.Cacheable().CacheRegion("test").ListAsync()); + var x3 = await (db.Customers.Cacheable().CacheRegion("other").ListAsync()); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + + [Test] + public async Task CanBeCombinedWithFetchAsync() + { + + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + await (db.Customers + .Cacheable() + .ListAsync()); + + await (db.Orders + .Cacheable() + .ListAsync()); + + await (db.Customers + .Fetch(SelectMode.Fetch, x => x.Orders) + .Cacheable() + .ListAsync()); + + await (db.Orders + .Fetch(SelectMode.Fetch, x => x.OrderLines) + .Cacheable() + .ListAsync()); + + var customer = await (db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefaultAsync()); + + customer = await (db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(customer.Address), Is.True, "Expected the fetched Address to be initialized"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(5), "Unexpected execution count"); + 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 FetchIsCacheableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + Order order; + order = (await (db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + 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(); + Session.Clear(); + + order = (await (db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + 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 FutureFetchIsCacheableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; + + Order order; + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + 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(); + Session.Clear(); + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + 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/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs new file mode 100644 index 00000000000..cd8cfd50fba --- /dev/null +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + public abstract class CriteriaNorthwindReadonlyTestCase : NHibernate.Test.Linq.ReadonlyTestCase + { + private ISession _session = null; + protected NorthwindQueryOver db; + + protected override string[] Mappings + { + get + { + return new[] + { + "Northwind.Mappings.Customer.hbm.xml", + "Northwind.Mappings.Employee.hbm.xml", + "Northwind.Mappings.Order.hbm.xml", + "Northwind.Mappings.OrderLine.hbm.xml", + "Northwind.Mappings.Product.hbm.xml", + "Northwind.Mappings.ProductCategory.hbm.xml", + "Northwind.Mappings.Region.hbm.xml", + "Northwind.Mappings.Shipper.hbm.xml", + "Northwind.Mappings.Supplier.hbm.xml", + "Northwind.Mappings.Territory.hbm.xml", + "Northwind.Mappings.AnotherEntity.hbm.xml", + "Northwind.Mappings.Role.hbm.xml", + "Northwind.Mappings.User.hbm.xml", + "Northwind.Mappings.TimeSheet.hbm.xml", + "Northwind.Mappings.Animal.hbm.xml", + "Northwind.Mappings.Patient.hbm.xml" + }; + } + } + + public ISession Session + { + get { return _session; } + } + + protected override void OnSetUp() + { + _session = OpenSession(); + db = new NorthwindQueryOver(_session); + base.OnSetUp(); + } + + protected override void OnTearDown() + { + if (_session.IsOpen) + { + _session.Close(); + } + } + + public static void AssertByIds(IEnumerable entities, TId[] expectedIds, Converter entityIdGetter) + { + Assert.That(entities.Select(x => entityIdGetter(x)), Is.EquivalentTo(expectedIds)); + } + + protected IQueryOver Customers => Session.QueryOver(); + } +} diff --git a/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs new file mode 100644 index 00000000000..330fda364ac --- /dev/null +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -0,0 +1,230 @@ +using System.Linq; +using NHibernate.Cfg; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + [TestFixture] + public class QueryOverCacheableTests : CriteriaNorthwindReadonlyTestCase + { + //Just for discoverability + private class CriteriaCacheableTest{} + + protected override void Configure(Configuration cfg) + { + cfg.SetProperty(Environment.UseQueryCache, "true"); + cfg.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(cfg); + } + + [Test] + public void QueryIsCacheable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + var x = db.Customers.Cacheable().List(); + var x2 = db.Customers.Cacheable().List(); + + 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.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public void QueryIsCacheable2() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + var x = db.Customers.Cacheable().List(); + var x2 = db.Customers.List(); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "Unexpected cache hit count"); + } + + [Test] + public void QueryIsCacheableWithRegion() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + Sfi.EvictQueries("test"); + Sfi.EvictQueries("other"); + + var x = db.Customers.Cacheable().CacheRegion("test").List(); + var x2 = db.Customers.Cacheable().CacheRegion("test").List(); + var x3 = db.Customers.Cacheable().CacheRegion("other").List(); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + + [Test] + public void CanBeCombinedWithFetch() + { + + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + db.Customers + .Cacheable() + .List(); + + db.Orders + .Cacheable() + .List(); + + db.Customers + .Fetch(SelectMode.Fetch, x => x.Orders) + .Cacheable() + .List(); + + db.Orders + .Fetch(SelectMode.Fetch, x => x.OrderLines) + .Cacheable() + .List(); + + var customer = db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefault(); + + customer = db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(customer.Address), Is.True, "Expected the fetched Address to be initialized"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(5), "Unexpected execution count"); + 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 FetchIsCacheable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + Order order; + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + 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(); + Session.Clear(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + 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 FutureFetchIsCacheable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; + + Order order; + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + 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(); + Session.Clear(); + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + 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/Criteria/ReadonlyTests/_ReadonlyFixtureSetUp.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/_ReadonlyFixtureSetUp.cs new file mode 100644 index 00000000000..b2ba33908d6 --- /dev/null +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/_ReadonlyFixtureSetUp.cs @@ -0,0 +1,12 @@ +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + /// + /// Single one-time fixture set up for all test fixtures in NHibernate.Test.Criteria.ReadonlyTests namespace + /// + [SetUpFixture] + public class ReadonlyFixtureSetUp : NHibernate.Test.Linq.LinqReadonlyTestsContext + { + } +} diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index 33dff86b077..6367dbd9711 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -27,7 +27,6 @@ using NHibernate.Exceptions; using NHibernate.Hql.Util; using NHibernate.Impl; -using NHibernate.Intercept; using NHibernate.Param; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; diff --git a/src/NHibernate/Cache/QueryCacheResultBuilder.cs b/src/NHibernate/Cache/QueryCacheResultBuilder.cs index f3f145ddad3..d64f908a4b8 100644 --- a/src/NHibernate/Cache/QueryCacheResultBuilder.cs +++ b/src/NHibernate/Cache/QueryCacheResultBuilder.cs @@ -1,12 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NHibernate.Collection; -using NHibernate.Engine; -using NHibernate.Persister.Collection; using NHibernate.Type; namespace NHibernate.Cache @@ -17,56 +12,31 @@ namespace NHibernate.Cache public sealed class QueryCacheResultBuilder { private readonly IType[] _resultTypes; - private readonly IType[] _cacheTypes; - private readonly List _entityFetchIndexes = new List(); - private readonly List _collectionFetchIndexes = new List(); - private readonly bool _hasFetches; + private readonly Loader.Loader.QueryCacheInfo _cacheInfo; + internal QueryCacheResultBuilder(Loader.Loader loader) { _resultTypes = loader.ResultTypes; - _cacheTypes = loader.CacheTypes; - - if (loader.EntityFetches != null) - { - 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++) + var cacheInfo = loader.CacheInfo; + if (cacheInfo?.CacheTypes.Length > _resultTypes.Length) { - if (loader.CollectionFetches[i]) - { - _collectionFetchIndexes.Add(i); - } + _cacheInfo = cacheInfo; } - - _hasFetches = _hasFetches || _collectionFetchIndexes.Count > 0; } internal IList Result { get; } = new List(); internal void AddRow(object result, object[] entities, IPersistentCollection[] collections) { - if (!_hasFetches) + if (_cacheInfo == null) { Result.Add(result); return; } - var row = new object[_cacheTypes.Length]; + var row = new object[_cacheInfo.CacheTypes.Length]; if (_resultTypes.Length == 1) { row[0] = result; @@ -77,14 +47,17 @@ internal void AddRow(object result, object[] entities, IPersistentCollection[] c } var i = _resultTypes.Length; - foreach (var index in _entityFetchIndexes) + foreach (var index in _cacheInfo.AdditionalEntities) { row[i++] = entities[index]; } - foreach (var index in _collectionFetchIndexes) + if (collections != null) { - row[i++] = collections[index]; + foreach (var collection in collections) + { + row[i++] = collection; + } } Result.Add(row); @@ -92,7 +65,7 @@ internal void AddRow(object result, object[] entities, IPersistentCollection[] c internal IList GetResultList(IList cacheList) { - if (!_hasFetches) + if (_cacheInfo == null) { return cacheList; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs index dfa5cf67f18..8636c918131 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs @@ -54,7 +54,6 @@ public void InitializeDerivedSelectClause(FromClause fromClause) ASTAppender appender = new ASTAppender(ASTFactory, this); // Get ready to start adding nodes. int size = fromElements.Count; - List sqlResultTypeList = new List(size); List queryReturnTypeList = new List(size); int k = 0; @@ -76,7 +75,6 @@ public void InitializeDerivedSelectClause(FromClause fromClause) } _fromElementsForLoad.Add(fromElement); - sqlResultTypeList.Add(type); // Generate the select expression. string text = fromElement.RenderIdentifierSelect(size, k); diff --git a/src/NHibernate/Loader/Criteria/CriteriaLoader.cs b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs index e3b816d2661..a0804bc223e 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaLoader.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs @@ -75,7 +75,7 @@ public CriteriaLoader(IOuterJoinLoadable persister, ISessionFactoryImplementor f userAliases = walker.UserAliases; ResultTypes = walker.ResultTypes; includeInResultRow = walker.IncludeInResultRow; - resultRowLength = ArrayHelper.CountTrue(IncludeInResultRow); + resultRowLength = ArrayHelper.CountTrue(includeInResultRow); childFetchEntities = walker.ChildFetchEntities; EntityFetchLazyProperties = walker.EntityFetchLazyProperties; // fill caching objects only if there is a projection @@ -85,6 +85,10 @@ public CriteriaLoader(IOuterJoinLoadable persister, ISessionFactoryImplementor f } PostInstantiate(); + if(!translator.HasProjection) + { + CachePersistersWithCollections(ArrayHelper.IndexesOf(includeInResultRow, true)); + } } // Not ported: scroll (not supported) diff --git a/src/NHibernate/Loader/Custom/CustomLoader.cs b/src/NHibernate/Loader/Custom/CustomLoader.cs index 504ce824a19..dea693cf540 100644 --- a/src/NHibernate/Loader/Custom/CustomLoader.cs +++ b/src/NHibernate/Loader/Custom/CustomLoader.cs @@ -275,7 +275,7 @@ public override ILoadable[] EntityPersisters get { return entityPersisters; } } - protected override ICollectionPersister[] CollectionPersisters + protected internal override ICollectionPersister[] CollectionPersisters { get { return collectionPersisters; } } diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index ad95fd5362e..d31e6fd822d 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -44,7 +44,6 @@ public partial class QueryLoader : BasicLoader private readonly NullableDictionary _sqlAliasByEntityAlias = new NullableDictionary(); private int _selectLength; private LockMode[] _defaultLockModes; - private IType[] _cacheTypes; private ISet _uncacheableCollectionPersisters; private Dictionary[] _collectionUserProvidedAliases; @@ -199,13 +198,11 @@ protected override string[] CollectionSuffixes get { return _collectionSuffixes; } } - protected override ICollectionPersister[] CollectionPersisters + protected internal override ICollectionPersister[] CollectionPersisters { get { return _collectionPersisters; } } - public override IType[] CacheTypes => _cacheTypes; - protected override IDictionary GetCollectionUserProvidedAlias(int index) { return _collectionUserProvidedAliases?[index]; @@ -230,7 +227,6 @@ private void Initialize(SelectClause selectClause) _collectionPersisters = new IQueryableCollection[length]; _collectionOwners = new int[length]; _collectionSuffixes = new string[length]; - CollectionFetches = new bool[length]; if (collectionFromElements.Any(qc => qc.QueryableCollection.IsManyToMany)) _collectionUserProvidedAliases = new Dictionary[length]; @@ -242,7 +238,6 @@ private void Initialize(SelectClause selectClause) // collectionSuffixes[i] = collectionFromElement.getColumnAliasSuffix(); // collectionSuffixes[i] = Integer.toString( i ) + "_"; _collectionSuffixes[i] = collectionFromElement.CollectionSuffix; - CollectionFetches[i] = collectionFromElement.IsFetch; } } @@ -256,8 +251,7 @@ 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); + List resultTypePersisters = new List(); for (int i = 0; i < size; i++) { @@ -280,14 +274,10 @@ 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++; + resultTypePersisters.Add(i); } if (collectionFromElements != null && element.IsFetch && element.QueryableCollection?.IsManyToMany == true @@ -323,16 +313,10 @@ private void Initialize(SelectClause selectClause) } } - if (_collectionPersisters != null) - { - cacheTypes.AddRange(_collectionPersisters.Where((t, i) => CollectionFetches[i]).Select(t => t.CollectionType)); - } - - _cacheTypes = cacheTypes.ToArray(); - //NONE, because its the requested lock mode, not the actual! _defaultLockModes = ArrayHelper.Fill(LockMode.None, size); _uncacheableCollectionPersisters = _queryTranslator.UncacheableCollectionPersisters; + CachePersistersWithCollections(resultTypePersisters); } public IList List(ISessionImplementor session, QueryParameters queryParameters) diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 988d08cef98..923f5707119 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -17,7 +17,6 @@ using NHibernate.Exceptions; using NHibernate.Hql.Util; using NHibernate.Impl; -using NHibernate.Intercept; using NHibernate.Param; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -52,8 +51,21 @@ namespace NHibernate.Loader /// public abstract partial class Loader { - private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(Loader)); + /// + /// DTO for providing all query cache related details + /// + public sealed class QueryCacheInfo + { + public IType[] CacheTypes { get; set; } + + /// + /// Loader.EntityPersister indexes to be cached. + /// + public IReadOnlyList AdditionalEntities { get; set; } + } + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(Loader)); + private Lazy _cacheInfo; private readonly ISessionFactoryImplementor _factory; private readonly SessionFactoryHelper _helper; private ColumnNameCache _columnNameCache; @@ -156,11 +168,18 @@ public virtual bool IsSubselectLoadingEnabled /// public IType[] ResultTypes { get; protected set; } - public bool[] EntityFetches { get; protected set; } + public IType[] CacheTypes => CacheInfo?.CacheTypes ?? ResultTypes; - public bool[] CollectionFetches { get; protected set; } + public virtual QueryCacheInfo CacheInfo => _cacheInfo?.Value; - public virtual IType[] CacheTypes => ResultTypes; + /// + /// Cache all additional persisters and collection persisters that were loaded by query (fetched entities and collections) + /// + /// Persister indexes that are cached as part of query result (so present in ResultTypes) + private protected void CachePersistersWithCollections(IEnumerable resultTypePersisters) + { + _cacheInfo = new Lazy(() => GetQueryCacheInfo(resultTypePersisters)); + } public ISessionFactoryImplementor Factory { @@ -186,7 +205,7 @@ public ISessionFactoryImplementor Factory /// An (optional) persister for a collection to be initialized; only collection loaders /// return a non-null value /// - protected virtual ICollectionPersister[] CollectionPersisters + protected internal virtual ICollectionPersister[] CollectionPersisters { get { return null; } } @@ -2080,6 +2099,31 @@ protected bool TryGetLimitString(Dialect.Dialect dialect, SqlString queryString, return false; } + private QueryCacheInfo GetQueryCacheInfo(IEnumerable resultTypePersisters) + { + var resultTypes = ResultTypes.EmptyIfNull(); + + var cacheTypes = new List(resultTypes.Count + EntityPersisters.Length + CollectionPersisters?.Length ?? 0); + cacheTypes.AddRange(resultTypes); + + int[] additionalEntities = null; + if (EntityPersisters.Length > 0) + { + additionalEntities = Enumerable.Range(0, EntityPersisters.Length).Except(resultTypePersisters).ToArray(); + cacheTypes.AddRange(additionalEntities.Select(i => EntityPersisters[i].EntityMetamodel.EntityType)); + } + + cacheTypes.AddRange(CollectionPersisters.EmptyIfNull().Select(p => p.CollectionType)); + + return cacheTypes.Count == resultTypes.Count + ? null + : new QueryCacheInfo + { + CacheTypes = cacheTypes.ToArray(), + AdditionalEntities = additionalEntities.EmptyIfNull(), + }; + } + #endregion } } diff --git a/src/NHibernate/Loader/OuterJoinLoader.cs b/src/NHibernate/Loader/OuterJoinLoader.cs index eb5d515b114..04874e5ef5e 100644 --- a/src/NHibernate/Loader/OuterJoinLoader.cs +++ b/src/NHibernate/Loader/OuterJoinLoader.cs @@ -94,7 +94,7 @@ protected override string[] Aliases get { return aliases; } } - protected override ICollectionPersister[] CollectionPersisters + protected internal override ICollectionPersister[] CollectionPersisters { get { return collectionPersisters; } } diff --git a/src/NHibernate/Util/ArrayHelper.cs b/src/NHibernate/Util/ArrayHelper.cs index 52d24942bc9..f078cfa8607 100644 --- a/src/NHibernate/Util/ArrayHelper.cs +++ b/src/NHibernate/Util/ArrayHelper.cs @@ -180,6 +180,15 @@ public static int CountTrue(bool[] array) return array.Count(t => t); } + internal static IEnumerable IndexesOf(T[] array, T value) + { + for (int i = 0; i < array.Length; i++) + { + if (EqualityComparer.Default.Equals(array[i], value)) + yield return i; + } + } + public static bool ArrayEquals(T[] a, T[] b) { return ArrayComparer.Default.Equals(a, b); diff --git a/src/NHibernate/Util/EnumerableExtensions.cs b/src/NHibernate/Util/EnumerableExtensions.cs index b0e35892b06..0fb37b8ffe3 100644 --- a/src/NHibernate/Util/EnumerableExtensions.cs +++ b/src/NHibernate/Util/EnumerableExtensions.cs @@ -102,5 +102,10 @@ internal static IList ToIList(this IEnumerable list) { return list as IList ?? list.ToList(); } + + internal static IReadOnlyList EmptyIfNull(this IReadOnlyList list) + { + return list ?? Array.Empty(); + } } } From 64f644c6dc8b65982c8de56b7d29d03b3faff1b3 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 27 Mar 2019 13:14:45 +0200 Subject: [PATCH 2/9] Fix unsupported by CI modifier --- src/NHibernate/Loader/Loader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 923f5707119..bb1ee29adb3 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -176,7 +176,7 @@ public virtual bool IsSubselectLoadingEnabled /// Cache all additional persisters and collection persisters that were loaded by query (fetched entities and collections) /// /// Persister indexes that are cached as part of query result (so present in ResultTypes) - private protected void CachePersistersWithCollections(IEnumerable resultTypePersisters) + protected void CachePersistersWithCollections(IEnumerable resultTypePersisters) { _cacheInfo = new Lazy(() => GetQueryCacheInfo(resultTypePersisters)); } From d83f4b4c37730ab3c7071601afd318418ee99bda Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 27 Mar 2019 19:54:41 +0200 Subject: [PATCH 3/9] Cache tuple without transformation for queries with fetches --- .../ReadonlyTests/QueryOverCacheableTests.cs | 72 ++++++++++++++++--- .../Async/Linq/QueryCacheableTests.cs | 44 ++++++++++++ .../ReadonlyTests/QueryOverCacheableTests.cs | 72 ++++++++++++++++--- .../Linq/QueryCacheableTests.cs | 44 ++++++++++++ .../Cache/QueryCacheResultBuilder.cs | 11 +-- src/NHibernate/Loader/Loader.cs | 4 +- .../Transform/CacheableResultTransformer.cs | 46 +++++++++--- 7 files changed, 263 insertions(+), 30 deletions(-) diff --git a/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs index 307099775a0..0fa9fea5c00 100644 --- a/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs +++ b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -11,6 +11,7 @@ using System.Linq; using NHibernate.Cfg; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.SqlCommand; using NUnit.Framework; namespace NHibernate.Test.Criteria.ReadonlyTests @@ -35,8 +36,8 @@ public async Task QueryIsCacheableAsync() Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); - var x = await (db.Customers.Cacheable().ListAsync()); - var x2 = await (db.Customers.Cacheable().ListAsync()); + var x = await (db.Customers.Cacheable().Take(1).ListAsync()); + var x2 = await (db.Customers.Cacheable().Take(1).ListAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -49,8 +50,8 @@ public async Task QueryIsCacheable2Async() Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); - var x = await (db.Customers.Cacheable().ListAsync()); - var x2 = await (db.Customers.ListAsync()); + var x = await (db.Customers.Cacheable().Take(1).ListAsync()); + var x2 = await (db.Customers.Take(1).ListAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -65,9 +66,9 @@ public async Task QueryIsCacheableWithRegionAsync() await (Sfi.EvictQueriesAsync("test")); await (Sfi.EvictQueriesAsync("other")); - var x = await (db.Customers.Cacheable().CacheRegion("test").ListAsync()); - var x2 = await (db.Customers.Cacheable().CacheRegion("test").ListAsync()); - var x3 = await (db.Customers.Cacheable().CacheRegion("other").ListAsync()); + var x = await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); + var x2 = await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); + var x3 = await (db.Customers.Cacheable().Take(1).CacheRegion("other").ListAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); @@ -88,16 +89,19 @@ public async Task CanBeCombinedWithFetchAsync() await (db.Orders .Cacheable() + .Take(1) .ListAsync()); await (db.Customers .Fetch(SelectMode.Fetch, x => x.Orders) .Cacheable() + .Take(1) .ListAsync()); await (db.Orders .Fetch(SelectMode.Fetch, x => x.OrderLines) .Cacheable() + .Take(1) .ListAsync()); var customer = await (db.Customers @@ -165,7 +169,57 @@ public async Task FetchIsCacheableAsync() 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 FetchIsCacheableForJoinAliasAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + Order order; + Customer customer = null; + OrderLine orderLines = null; + Product product = null; + OrderLine prOrderLines = null; + + order = (await (db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + 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(); + Session.Clear(); + + order = (await (db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + 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 FutureFetchIsCacheableAsync() { @@ -230,10 +284,12 @@ public async Task FutureFetchIsCacheableAsync() private static void AssertFetchedOrder(Order order) { + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); 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/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index 4abcd24c0af..4c3b0a2b772 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -12,6 +12,7 @@ using NHibernate.Cfg; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Linq; +using NHibernate.Transform; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -407,12 +408,55 @@ public async Task FutureFetchIsCachableAsync() Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); } + [Test] + public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + Order order; + + // the combination of query and transformer doesn't make sense. + // It's simply used as example of returned data being transformed before caching leading to mismatch between + // Loader.ResultTypes collection and provided tuple + order = await (session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResultAsync()); + + 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"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + + session.Clear(); + Sfi.Statistics.Clear(); + + order = await (session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResultAsync()); + + 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"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + } + private static void AssertFetchedOrder(Order order) { + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); 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/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs index 330fda364ac..2d91db9ecd2 100644 --- a/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -1,6 +1,7 @@ using System.Linq; using NHibernate.Cfg; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.SqlCommand; using NUnit.Framework; namespace NHibernate.Test.Criteria.ReadonlyTests @@ -24,8 +25,8 @@ public void QueryIsCacheable() Sfi.Statistics.Clear(); Sfi.EvictQueries(); - var x = db.Customers.Cacheable().List(); - var x2 = db.Customers.Cacheable().List(); + var x = db.Customers.Cacheable().Take(1).List(); + var x2 = db.Customers.Cacheable().Take(1).List(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -38,8 +39,8 @@ public void QueryIsCacheable2() Sfi.Statistics.Clear(); Sfi.EvictQueries(); - var x = db.Customers.Cacheable().List(); - var x2 = db.Customers.List(); + var x = db.Customers.Cacheable().Take(1).List(); + var x2 = db.Customers.Take(1).List(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -54,9 +55,9 @@ public void QueryIsCacheableWithRegion() Sfi.EvictQueries("test"); Sfi.EvictQueries("other"); - var x = db.Customers.Cacheable().CacheRegion("test").List(); - var x2 = db.Customers.Cacheable().CacheRegion("test").List(); - var x3 = db.Customers.Cacheable().CacheRegion("other").List(); + var x = db.Customers.Cacheable().Take(1).CacheRegion("test").List(); + var x2 = db.Customers.Cacheable().Take(1).CacheRegion("test").List(); + var x3 = db.Customers.Cacheable().Take(1).CacheRegion("other").List(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); @@ -77,16 +78,19 @@ public void CanBeCombinedWithFetch() db.Orders .Cacheable() + .Take(1) .List(); db.Customers .Fetch(SelectMode.Fetch, x => x.Orders) .Cacheable() + .Take(1) .List(); db.Orders .Fetch(SelectMode.Fetch, x => x.OrderLines) .Cacheable() + .Take(1) .List(); var customer = db.Customers @@ -154,7 +158,57 @@ public void FetchIsCacheable() 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 FetchIsCacheableForJoinAlias() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + Order order; + Customer customer = null; + OrderLine orderLines = null; + Product product = null; + OrderLine prOrderLines = null; + + order = db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + 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(); + Session.Clear(); + + order = db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + 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 FutureFetchIsCacheable() { @@ -219,10 +273,12 @@ public void FutureFetchIsCacheable() private static void AssertFetchedOrder(Order order) { + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); 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 79aedf77c5f..6d1629e90b1 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -2,6 +2,7 @@ using NHibernate.Cfg; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Linq; +using NHibernate.Transform; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -396,12 +397,55 @@ public void FutureFetchIsCachable() Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); } + [Test] + public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + Order order; + + // the combination of query and transformer doesn't make sense. + // It's simply used as example of returned data being transformed before caching leading to mismatch between + // Loader.ResultTypes collection and provided tuple + order = session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResult(); + + 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"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + + session.Clear(); + Sfi.Statistics.Clear(); + + order = session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResult(); + + 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"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + } + private static void AssertFetchedOrder(Order order) { + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); 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/Cache/QueryCacheResultBuilder.cs b/src/NHibernate/Cache/QueryCacheResultBuilder.cs index d64f908a4b8..fff6fe08d75 100644 --- a/src/NHibernate/Cache/QueryCacheResultBuilder.cs +++ b/src/NHibernate/Cache/QueryCacheResultBuilder.cs @@ -14,15 +14,18 @@ public sealed class QueryCacheResultBuilder private readonly IType[] _resultTypes; private readonly Loader.Loader.QueryCacheInfo _cacheInfo; - + public static bool IsCacheWithFetches(Loader.Loader loader) + { + return loader.CacheTypes.Length > loader.ResultTypes.Length; + } + internal QueryCacheResultBuilder(Loader.Loader loader) { _resultTypes = loader.ResultTypes; - var cacheInfo = loader.CacheInfo; - if (cacheInfo?.CacheTypes.Length > _resultTypes.Length) + if (IsCacheWithFetches(loader)) { - _cacheInfo = cacheInfo; + _cacheInfo = loader.CacheInfo; } } diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index bb1ee29adb3..49fd0974e38 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1886,9 +1886,11 @@ internal QueryKey GenerateQueryKey(ISessionImplementor session, QueryParameters private CacheableResultTransformer CreateCacheableResultTransformer(QueryParameters queryParameters) { + bool skipTransformer = QueryCacheResultBuilder.IsCacheWithFetches(this); + return CacheableResultTransformer.Create( queryParameters.ResultTransformer, ResultRowAliases, IncludeInResultRow, - queryParameters.HasAutoDiscoverScalarTypes, SqlString); + queryParameters.HasAutoDiscoverScalarTypes, SqlString, skipTransformer); } private IList GetResultFromQueryCache( diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index 97d64f459ff..bad4b7cc75f 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -24,6 +24,7 @@ public class CacheableResultTransformer : IResultTransformer public bool AutoDiscoverTypes { get; } private readonly SqlString _autoDiscoveredQuery; + private readonly bool _skipTransformer; private int _tupleLength; private int _tupleSubsetLength; @@ -60,7 +61,7 @@ public class CacheableResultTransformer : IResultTransformer /// a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. // Since v5.1 - [Obsolete("Please use overload with autoDiscoverTypes parameter.")] + [Obsolete("Please use override with skipTransformer parameter.")] public static CacheableResultTransformer Create(IResultTransformer transformer, string[] aliases, bool[] includeInTuple) @@ -68,6 +69,17 @@ public static CacheableResultTransformer Create(IResultTransformer transformer, return Create(transformer, aliases, includeInTuple, false, null); } + // Since 5.2 + [Obsolete("Please use override with skipTransformer parameter.")] + public static CacheableResultTransformer Create( + IResultTransformer transformer, string[] aliases, bool[] includeInTuple, bool autoDiscoverTypes, + SqlString autoDiscoveredQuery) + { + return autoDiscoverTypes + ? Create(autoDiscoveredQuery) + : Create(includeInTuple, GetIncludeInTransform(transformer, aliases, includeInTuple), false); + } + /// /// Returns a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. @@ -84,15 +96,25 @@ public static CacheableResultTransformer Create(IResultTransformer transformer, /// Indicates if types auto-discovery is enabled. /// If , the query for which they /// will be autodiscovered. + /// If true cache results untransformed. /// a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. public static CacheableResultTransformer Create( - IResultTransformer transformer, string[] aliases, bool[] includeInTuple, bool autoDiscoverTypes, - SqlString autoDiscoveredQuery) + IResultTransformer transformer, + string[] aliases, + bool[] includeInTuple, + bool autoDiscoverTypes, + SqlString autoDiscoveredQuery, + bool skipTransformer) { return autoDiscoverTypes ? Create(autoDiscoveredQuery) - : Create(includeInTuple, GetIncludeInTransform(transformer, aliases, includeInTuple)); + : Create( + includeInTuple, + skipTransformer + ? null + : GetIncludeInTransform(transformer, aliases, includeInTuple), + skipTransformer); } /// @@ -106,11 +128,12 @@ public static CacheableResultTransformer Create( /// must be non-null /// Indexes that are included in the transformation. /// null if all elements in the tuple are included. + /// /// a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. - private static CacheableResultTransformer Create(bool[] includeInTuple, bool[] includeInTransform) + private static CacheableResultTransformer Create(bool[] includeInTuple, bool[] includeInTransform, bool skipTransformer) { - return new CacheableResultTransformer(includeInTuple, includeInTransform); + return new CacheableResultTransformer(includeInTuple, includeInTransform, skipTransformer); } private static CacheableResultTransformer Create(SqlString autoDiscoveredQuery) @@ -136,8 +159,9 @@ private static bool[] GetIncludeInTransform(IResultTransformer transformer, stri return resultTransformer.IncludeInTransform(aliases, tupleLength); } - private CacheableResultTransformer(bool[] includeInTuple, bool[] includeInTransform) + private CacheableResultTransformer(bool[] includeInTuple, bool[] includeInTransform, bool skipTransformer) { + _skipTransformer = skipTransformer; InitializeTransformer(includeInTuple, includeInTransform); } @@ -212,7 +236,7 @@ public IList RetransformResults(IList transformedResults, if (_includeInTuple == null) throw new InvalidOperationException("This transformer is not initialized"); - if (!HasSameParameters(Create(transformer, aliases, includeInTuple, false, null))) + if (!HasSameParameters(Create(transformer, aliases, includeInTuple, false, null, _skipTransformer))) { throw new InvalidOperationException( "this CacheableResultTransformer is inconsistent with specified arguments; cannot re-transform" @@ -220,7 +244,11 @@ public IList RetransformResults(IList transformedResults, } bool requiresRetransform = true; string[] aliasesToUse = aliases == null ? null : Index(aliases); - if (transformer.Equals(_actualTransformer)) + + if (_skipTransformer) + { + } + else if (transformer.Equals(_actualTransformer)) { requiresRetransform = false; } From 07bef22480c1af6544aba2218d5753890649208c Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 28 Mar 2019 08:55:48 +0200 Subject: [PATCH 4/9] Adjust test for Oracle and slight refactoring --- .../Async/Linq/QueryCacheableTests.cs | 54 ++++++++++++++++--- .../Linq/QueryCacheableTests.cs | 54 ++++++++++++++++--- src/NHibernate/Loader/Hql/QueryLoader.cs | 4 +- 3 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index 4c3b0a2b772..ee5c461c1de 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -407,6 +407,47 @@ public async Task FutureFetchIsCachableAsync() Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); } + + [Explicit("Not working. dto.Customer retrieved from cache as uninitialized proxy")] + [Test] + public async Task ProjectedEntitiesAreCachableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + var dto = await (session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new {Customer = x.Customer, Order = x}) + .FirstOrDefaultAsync()); + + Assert.That(dto.Order, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); + Assert.That(dto.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + + 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(); + session.Clear(); + + dto = await (session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new {Customer = x.Customer, Order = x}) + .FirstOrDefaultAsync()); + + Assert.That(dto.Order, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); + Assert.That(dto.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + + 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 CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() @@ -418,11 +459,11 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple - order = await (session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") - .SetMaxResults(1) - .SetCacheable(true) - .SetResultTransformer(Transformers.RootEntity) - .UniqueResultAsync()); + order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResultAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -434,7 +475,7 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() session.Clear(); Sfi.Statistics.Clear(); - order = await (session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") + order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") .SetMaxResults(1) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) @@ -451,6 +492,7 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() private static void AssertFetchedOrder(Order order) { + Assert.That(NHibernateUtil.IsInitialized(order)); Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); 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"); diff --git a/src/NHibernate.Test/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Linq/QueryCacheableTests.cs index 6d1629e90b1..e3f0473a649 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -396,6 +396,47 @@ public void FutureFetchIsCachable() Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); } + + [Explicit("Not working. dto.Customer retrieved from cache as uninitialized proxy")] + [Test] + public void ProjectedEntitiesAreCachable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + var dto = session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new {Customer = x.Customer, Order = x}) + .FirstOrDefault(); + + Assert.That(dto.Order, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); + Assert.That(dto.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + + 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(); + session.Clear(); + + dto = session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new {Customer = x.Customer, Order = x}) + .FirstOrDefault(); + + Assert.That(dto.Order, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); + Assert.That(dto.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + + 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 CacheHqlQueryWithFetchAndTransformerThatChangeTuple() @@ -407,11 +448,11 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple - order = session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") - .SetMaxResults(1) - .SetCacheable(true) - .SetResultTransformer(Transformers.RootEntity) - .UniqueResult(); + order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResult(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -423,7 +464,7 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() session.Clear(); Sfi.Statistics.Clear(); - order = session.CreateQuery("select o.Customer.CompanyName, o from Order o join fetch o.Customer") + order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") .SetMaxResults(1) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) @@ -440,6 +481,7 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() private static void AssertFetchedOrder(Order order) { + Assert.That(NHibernateUtil.IsInitialized(order)); Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); 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"); diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index d31e6fd822d..62d9f6df466 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -251,7 +251,6 @@ private void Initialize(SelectClause selectClause) _includeInSelect = new bool[size]; _owners = new int[size]; _ownerAssociationTypes = new EntityType[size]; - List resultTypePersisters = new List(); for (int i = 0; i < size; i++) { @@ -277,7 +276,6 @@ private void Initialize(SelectClause selectClause) if (_includeInSelect[i]) { _selectLength++; - resultTypePersisters.Add(i); } if (collectionFromElements != null && element.IsFetch && element.QueryableCollection?.IsManyToMany == true @@ -316,7 +314,7 @@ private void Initialize(SelectClause selectClause) //NONE, because its the requested lock mode, not the actual! _defaultLockModes = ArrayHelper.Fill(LockMode.None, size); _uncacheableCollectionPersisters = _queryTranslator.UncacheableCollectionPersisters; - CachePersistersWithCollections(resultTypePersisters); + CachePersistersWithCollections(ArrayHelper.IndexesOf(_includeInSelect, true)); } public IList List(ISessionImplementor session, QueryParameters queryParameters) From e1f238d979e656867596a6db763fa0ee8eae6284 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 28 Mar 2019 13:20:23 +0200 Subject: [PATCH 5/9] ignore test for oracle --- .../Async/Linq/QueryCacheableTests.cs | 15 +++++++++------ src/NHibernate.Test/Linq/QueryCacheableTests.cs | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index ee5c461c1de..8a3b100d191 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -452,10 +452,13 @@ public async Task ProjectedEntitiesAreCachableAsync() [Test] public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() { + if (!TestDialect.SupportsDuplicatedColumnAliases) + Assert.Ignore("Ignored due to GH-2092"); + Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); Order order; - + // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple @@ -471,15 +474,15 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() Assert.That(order, Is.Not.Null); Assert.That(order.Customer, Is.Not.Null); Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); - + session.Clear(); Sfi.Statistics.Clear(); order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") - .SetMaxResults(1) - .SetCacheable(true) - .SetResultTransformer(Transformers.RootEntity) - .UniqueResultAsync()); + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResultAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); diff --git a/src/NHibernate.Test/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Linq/QueryCacheableTests.cs index e3f0473a649..3d4b8401bba 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -441,10 +441,13 @@ public void ProjectedEntitiesAreCachable() [Test] public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() { + if (!TestDialect.SupportsDuplicatedColumnAliases) + Assert.Ignore("Ignored due to GH-2092"); + Sfi.Statistics.Clear(); Sfi.EvictQueries(); Order order; - + // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple @@ -460,15 +463,15 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() Assert.That(order, Is.Not.Null); Assert.That(order.Customer, Is.Not.Null); Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); - + session.Clear(); Sfi.Statistics.Clear(); order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") - .SetMaxResults(1) - .SetCacheable(true) - .SetResultTransformer(Transformers.RootEntity) - .UniqueResult(); + .SetMaxResults(1) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResult(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); From 8cc73030eb7d401bbac122d7f9fcfe87c6a84e42 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 28 Mar 2019 21:19:56 +0200 Subject: [PATCH 6/9] Another attempt to fix Oracle --- src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs | 8 ++++---- src/NHibernate.Test/Linq/QueryCacheableTests.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index 8a3b100d191..067d2ba68f4 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -462,8 +462,8 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple - order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") - .SetMaxResults(1) + order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) .UniqueResultAsync()); @@ -478,8 +478,8 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() session.Clear(); Sfi.Statistics.Clear(); - order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") - .SetMaxResults(1) + order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) .UniqueResultAsync()); diff --git a/src/NHibernate.Test/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Linq/QueryCacheableTests.cs index 3d4b8401bba..e722044220e 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -451,8 +451,8 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple - order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") - .SetMaxResults(1) + order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) .UniqueResult(); @@ -467,8 +467,8 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() session.Clear(); Sfi.Statistics.Clear(); - order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer") - .SetMaxResults(1) + order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) .UniqueResult(); From f512086bf5cebf6aac8ca99fb293331d77be7f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sat, 4 Apr 2020 21:03:23 +0200 Subject: [PATCH 7/9] Do some cleanup --- .../ReadonlyTests/QueryOverCacheableTests.cs | 45 ++++++++----------- .../Async/Linq/QueryCacheableTests.cs | 31 ++++++------- .../ReadonlyTests/QueryOverCacheableTests.cs | 45 ++++++++----------- ...ixtureSetUp.cs => ReadonlyFixtureSetUp.cs} | 0 .../Linq/QueryCacheableTests.cs | 31 ++++++------- .../Loader/Criteria/CriteriaLoader.cs | 2 +- 6 files changed, 71 insertions(+), 83 deletions(-) rename src/NHibernate.Test/Criteria/ReadonlyTests/{_ReadonlyFixtureSetUp.cs => ReadonlyFixtureSetUp.cs} (100%) diff --git a/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs index 0fa9fea5c00..b3f6e51b233 100644 --- a/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs +++ b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -23,11 +23,11 @@ public class QueryOverCacheableTestsAsync : CriteriaNorthwindReadonlyTestCase //Just for discoverability private class CriteriaCacheableTest{} - protected override void Configure(Configuration cfg) + protected override void Configure(Configuration config) { - cfg.SetProperty(Environment.UseQueryCache, "true"); - cfg.SetProperty(Environment.GenerateStatistics, "true"); - base.Configure(cfg); + config.SetProperty(Environment.UseQueryCache, "true"); + config.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(config); } [Test] @@ -36,8 +36,8 @@ public async Task QueryIsCacheableAsync() Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); - var x = await (db.Customers.Cacheable().Take(1).ListAsync()); - var x2 = await (db.Customers.Cacheable().Take(1).ListAsync()); + await (db.Customers.Cacheable().Take(1).ListAsync()); + await (db.Customers.Cacheable().Take(1).ListAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -50,8 +50,8 @@ public async Task QueryIsCacheable2Async() Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); - var x = await (db.Customers.Cacheable().Take(1).ListAsync()); - var x2 = await (db.Customers.Take(1).ListAsync()); + await (db.Customers.Cacheable().Take(1).ListAsync()); + await (db.Customers.Take(1).ListAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -66,20 +66,18 @@ public async Task QueryIsCacheableWithRegionAsync() await (Sfi.EvictQueriesAsync("test")); await (Sfi.EvictQueriesAsync("other")); - var x = await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); - var x2 = await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); - var x3 = await (db.Customers.Cacheable().Take(1).CacheRegion("other").ListAsync()); + await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); + await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); + await (db.Customers.Cacheable().Take(1).CacheRegion("other").ListAsync()); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); } - [Test] public async Task CanBeCombinedWithFetchAsync() { - Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); @@ -104,13 +102,13 @@ public async Task CanBeCombinedWithFetchAsync() .Take(1) .ListAsync()); - var customer = await (db.Customers + await (db.Customers .Fetch(SelectMode.Fetch, x => x.Address) .Where(x => x.CustomerId == "VINET") .Cacheable() .SingleOrDefaultAsync()); - customer = await (db.Customers + var customer = await (db.Customers .Fetch(SelectMode.Fetch, x => x.Address) .Where(x => x.CustomerId == "VINET") .Cacheable() @@ -128,8 +126,7 @@ public async Task FetchIsCacheableAsync() Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); - Order order; - order = (await (db.Orders + var order = (await (db.Orders .Fetch( SelectMode.Fetch, x => x.Customer, @@ -170,20 +167,18 @@ public async Task FetchIsCacheableAsync() Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); } - [Test] public async Task FetchIsCacheableForJoinAliasAsync() { Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); - Order order; Customer customer = null; OrderLine orderLines = null; Product product = null; OrderLine prOrderLines = null; - - order = (await (db.Orders + + var order = (await (db.Orders .JoinAlias(x => x.Customer, () => customer) .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) .JoinAlias(() => orderLines.Product, () => product) @@ -227,15 +222,13 @@ public async Task FutureFetchIsCacheableAsync() await (Sfi.EvictQueriesAsync()); var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; - Order order; - db.Orders .Fetch(SelectMode.Fetch, x => x.Customer) .Where(x => x.OrderId == 10248) .Cacheable() .Future(); - order = db.Orders + var order = db.Orders .Fetch( SelectMode.Fetch, x => x.OrderLines, @@ -284,12 +277,12 @@ public async Task FutureFetchIsCacheableAsync() private static void AssertFetchedOrder(Order order) { - Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); 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/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index 067d2ba68f4..54a8a6cfc44 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -417,13 +417,14 @@ public async Task ProjectedEntitiesAreCachableAsync() var dto = await (session.Query() .WithOptions(o => o.SetCacheable(true)) .Where(x => x.OrderId == 10248) - .Select(x => new {Customer = x.Customer, Order = x}) + .Select(x => new { x.Customer, Order = x }) .FirstOrDefaultAsync()); - Assert.That(dto.Order, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); - Assert.That(dto.Customer, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + Assert.That(dto, Is.Not.Null, "dto should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -435,13 +436,14 @@ public async Task ProjectedEntitiesAreCachableAsync() dto = await (session.Query() .WithOptions(o => o.SetCacheable(true)) .Where(x => x.OrderId == 10248) - .Select(x => new {Customer = x.Customer, Order = x}) + .Select(x => new { x.Customer, Order = x }) .FirstOrDefaultAsync()); - Assert.That(dto.Order, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); - Assert.That(dto.Customer, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + Assert.That(dto, Is.Not.Null, "dto from cache should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order from cache should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); @@ -457,12 +459,11 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); - Order order; // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple - order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + var order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") .SetInt32("id", 10248) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) @@ -495,13 +496,13 @@ public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() private static void AssertFetchedOrder(Order order) { - Assert.That(NHibernateUtil.IsInitialized(order)); - Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(order), "Expected the order to be initialized"); + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); 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/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs index 2d91db9ecd2..749a5184354 100644 --- a/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -12,11 +12,11 @@ public class QueryOverCacheableTests : CriteriaNorthwindReadonlyTestCase //Just for discoverability private class CriteriaCacheableTest{} - protected override void Configure(Configuration cfg) + protected override void Configure(Configuration config) { - cfg.SetProperty(Environment.UseQueryCache, "true"); - cfg.SetProperty(Environment.GenerateStatistics, "true"); - base.Configure(cfg); + config.SetProperty(Environment.UseQueryCache, "true"); + config.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(config); } [Test] @@ -25,8 +25,8 @@ public void QueryIsCacheable() Sfi.Statistics.Clear(); Sfi.EvictQueries(); - var x = db.Customers.Cacheable().Take(1).List(); - var x2 = db.Customers.Cacheable().Take(1).List(); + db.Customers.Cacheable().Take(1).List(); + db.Customers.Cacheable().Take(1).List(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -39,8 +39,8 @@ public void QueryIsCacheable2() Sfi.Statistics.Clear(); Sfi.EvictQueries(); - var x = db.Customers.Cacheable().Take(1).List(); - var x2 = db.Customers.Take(1).List(); + db.Customers.Cacheable().Take(1).List(); + db.Customers.Take(1).List(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -55,20 +55,18 @@ public void QueryIsCacheableWithRegion() Sfi.EvictQueries("test"); Sfi.EvictQueries("other"); - var x = db.Customers.Cacheable().Take(1).CacheRegion("test").List(); - var x2 = db.Customers.Cacheable().Take(1).CacheRegion("test").List(); - var x3 = db.Customers.Cacheable().Take(1).CacheRegion("other").List(); + db.Customers.Cacheable().Take(1).CacheRegion("test").List(); + db.Customers.Cacheable().Take(1).CacheRegion("test").List(); + db.Customers.Cacheable().Take(1).CacheRegion("other").List(); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); } - [Test] public void CanBeCombinedWithFetch() { - Sfi.Statistics.Clear(); Sfi.EvictQueries(); @@ -93,13 +91,13 @@ public void CanBeCombinedWithFetch() .Take(1) .List(); - var customer = db.Customers + db.Customers .Fetch(SelectMode.Fetch, x => x.Address) .Where(x => x.CustomerId == "VINET") .Cacheable() .SingleOrDefault(); - customer = db.Customers + var customer = db.Customers .Fetch(SelectMode.Fetch, x => x.Address) .Where(x => x.CustomerId == "VINET") .Cacheable() @@ -117,8 +115,7 @@ public void FetchIsCacheable() Sfi.Statistics.Clear(); Sfi.EvictQueries(); - Order order; - order = db.Orders + var order = db.Orders .Fetch( SelectMode.Fetch, x => x.Customer, @@ -159,20 +156,18 @@ public void FetchIsCacheable() Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); } - [Test] public void FetchIsCacheableForJoinAlias() { Sfi.Statistics.Clear(); Sfi.EvictQueries(); - Order order; Customer customer = null; OrderLine orderLines = null; Product product = null; OrderLine prOrderLines = null; - - order = db.Orders + + var order = db.Orders .JoinAlias(x => x.Customer, () => customer) .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) .JoinAlias(() => orderLines.Product, () => product) @@ -216,15 +211,13 @@ public void FutureFetchIsCacheable() Sfi.EvictQueries(); var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; - Order order; - db.Orders .Fetch(SelectMode.Fetch, x => x.Customer) .Where(x => x.OrderId == 10248) .Cacheable() .Future(); - order = db.Orders + var order = db.Orders .Fetch( SelectMode.Fetch, x => x.OrderLines, @@ -273,12 +266,12 @@ public void FutureFetchIsCacheable() private static void AssertFetchedOrder(Order order) { - Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); 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/Criteria/ReadonlyTests/_ReadonlyFixtureSetUp.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/ReadonlyFixtureSetUp.cs similarity index 100% rename from src/NHibernate.Test/Criteria/ReadonlyTests/_ReadonlyFixtureSetUp.cs rename to src/NHibernate.Test/Criteria/ReadonlyTests/ReadonlyFixtureSetUp.cs diff --git a/src/NHibernate.Test/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Linq/QueryCacheableTests.cs index e722044220e..4475971f09e 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -406,13 +406,14 @@ public void ProjectedEntitiesAreCachable() var dto = session.Query() .WithOptions(o => o.SetCacheable(true)) .Where(x => x.OrderId == 10248) - .Select(x => new {Customer = x.Customer, Order = x}) + .Select(x => new { x.Customer, Order = x }) .FirstOrDefault(); - Assert.That(dto.Order, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); - Assert.That(dto.Customer, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + Assert.That(dto, Is.Not.Null, "dto should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); @@ -424,13 +425,14 @@ public void ProjectedEntitiesAreCachable() dto = session.Query() .WithOptions(o => o.SetCacheable(true)) .Where(x => x.OrderId == 10248) - .Select(x => new {Customer = x.Customer, Order = x}) + .Select(x => new { x.Customer, Order = x }) .FirstOrDefault(); - Assert.That(dto.Order, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True); - Assert.That(dto.Customer, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True); + Assert.That(dto, Is.Not.Null, "dto from cache should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order from cache should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); @@ -446,12 +448,11 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() Sfi.Statistics.Clear(); Sfi.EvictQueries(); - Order order; // the combination of query and transformer doesn't make sense. // It's simply used as example of returned data being transformed before caching leading to mismatch between // Loader.ResultTypes collection and provided tuple - order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + var order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") .SetInt32("id", 10248) .SetCacheable(true) .SetResultTransformer(Transformers.RootEntity) @@ -484,13 +485,13 @@ public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() private static void AssertFetchedOrder(Order order) { - Assert.That(NHibernateUtil.IsInitialized(order)); - Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(order), "Expected the order to be initialized"); + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); 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(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be initialized"); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); 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/Loader/Criteria/CriteriaLoader.cs b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs index a0804bc223e..e3a5ff5dfda 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaLoader.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs @@ -85,7 +85,7 @@ public CriteriaLoader(IOuterJoinLoadable persister, ISessionFactoryImplementor f } PostInstantiate(); - if(!translator.HasProjection) + if (!translator.HasProjection) { CachePersistersWithCollections(ArrayHelper.IndexesOf(includeInResultRow, true)); } From 7dba7e2dd7360c33ec598fa80f3e922d98268553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 5 Apr 2020 19:22:52 +0200 Subject: [PATCH 8/9] Revert a wording change --- src/NHibernate/Transform/CacheableResultTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index bad4b7cc75f..16de47a035c 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -61,7 +61,7 @@ public class CacheableResultTransformer : IResultTransformer /// a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. // Since v5.1 - [Obsolete("Please use override with skipTransformer parameter.")] + [Obsolete("Please use overload with skipTransformer parameter.")] public static CacheableResultTransformer Create(IResultTransformer transformer, string[] aliases, bool[] includeInTuple) From f72135e58f950ae2cfceb352f412245c56d81b0a Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 5 Apr 2020 20:30:21 +0300 Subject: [PATCH 9/9] Fix wording --- src/NHibernate/Transform/CacheableResultTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index 16de47a035c..caed8a31221 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -70,7 +70,7 @@ public static CacheableResultTransformer Create(IResultTransformer transformer, } // Since 5.2 - [Obsolete("Please use override with skipTransformer parameter.")] + [Obsolete("Please use overload with skipTransformer parameter.")] public static CacheableResultTransformer Create( IResultTransformer transformer, string[] aliases, bool[] includeInTuple, bool autoDiscoverTypes, SqlString autoDiscoveredQuery)