diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/CriteriaTestFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/CriteriaTestFixture.cs new file mode 100644 index 00000000000..3955fe3e783 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/CriteriaTestFixture.cs @@ -0,0 +1,221 @@ +//------------------------------------------------------------------------------ +// +// 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.Collections.Generic; +using NHibernate.Criterion; +using NHibernate.SqlCommand; +using NHibernate.Transform; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + using System.Threading.Tasks; + using System.Threading; + public class CriteriaTestFixtureAsync : FixtureAsync + { + protected override Task> GetCustomersWithOrdersEagerLoadedAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .Fetch("Orders") + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingOnClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingFetchAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, "Orders") + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingFetchAndWhereClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .Fetch(SelectMode.Fetch, "Orders") + .CreateCriteria("Orders", JoinType.InnerJoin) + .Add(Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClauseAsync(ISession session, int orderNumber, string name, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, "Orders") + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin, Restrictions.Eq("Name", name)) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictionsAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .Fetch(SelectMode.Fetch, "Orders") + .CreateAlias("Orders", "order", JoinType.InnerJoin) + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersWithCompaniesByOrderNumberUsingOnClauseAsync( + ISession session, + int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingWhereClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .CreateCriteria("Orders", "Order", JoinType.LeftOuterJoin) + .Add(Restrictions.Eq("Number", orderNumber)) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByNameUsingWhereClauseAsync(ISession session, string customerName, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin) + .Add(Restrictions.Eq("Name", "First Customer")) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClauseAsync( + ISession session, + int orderNumber, + string customerName, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + var detachedCriteria = + DetachedCriteria + .For() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .SetProjection(Projections.Id()); + + return + session + .CreateCriteria() + .CreateAlias("Orders", "order1", JoinType.LeftOuterJoin) + .Add(Subqueries.PropertyIn("Id", detachedCriteria)) + .Add(Restrictions.Eq("Name", customerName)) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetAllCustomersAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return session.CreateCriteria().ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/Fixture.cs new file mode 100644 index 00000000000..be5a8125f28 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/Fixture.cs @@ -0,0 +1,620 @@ +//------------------------------------------------------------------------------ +// +// 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; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using NHibernate.Cache; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + using System.Threading.Tasks; + using System.Threading; + [TestFixture] + public abstract class FixtureAsync : TestCaseMappingByCode + { + protected Customer Customer1; + protected Customer Customer2; + protected Customer Customer3; + protected const int OrderNumber = 2; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Table("Customers"); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Orders, m => + { + m.Inverse(true); + m.Key(k => + { + k.Column("CustomerId"); + k.NotNullable(true); + }); + m.Cascade(Mapping.ByCode.Cascade.All.Include(Mapping.ByCode.Cascade.DeleteOrphans)); + m.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, m => m.OneToMany()); + + rc.Set(x => x.Companies, m => + { + m.Inverse(true); + m.Key(k => + { + k.Column("CustomerId"); + k.NotNullable(true); + }); + m.Cascade(Mapping.ByCode.Cascade.All.Include(Mapping.ByCode.Cascade.DeleteOrphans)); + m.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, m => m.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Table("Orders"); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Number, m => m.Column("`Number`")); + rc.ManyToOne(x => x.Customer, m => m.Column("CustomerId")); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + mapper.Class(rc => + { + rc.Table("Companies"); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Customer, m => m.Column("CustomerId")); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + configuration.Cache(c => + { + c.UseQueryCache = true; + c.Provider(); + }); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Customer1 = new Customer { Name = "First Customer" }; + + Customer2 = new Customer { Name = "Second Customer" }; + + Customer3 = new Customer { Name = "Third Customer" }; + + var customer1Order1 = new Order { Number = 1 }; + var customer1Company1 = new Company { Name = "First" }; + Customer1.AddOrder(customer1Order1); + Customer1.AddCompany(customer1Company1); + + var customer1Order2 = new Order { Number = 2 }; + var customer1Company2 = new Company { Name = "Second" }; + Customer1.AddOrder(customer1Order2); + Customer1.AddCompany(customer1Company2); + + var customer2Order1 = new Order { Number = 1 }; + var customer2Company1 = new Company { Name = "First" }; + Customer2.AddOrder(customer2Order1); + Customer2.AddCompany(customer2Company1); + + var customer2Order2 = new Order { Number = 2 }; + var customer2Company2 = new Company { Name = "Second" }; + Customer2.AddOrder(customer2Order2); + Customer2.AddCompany(customer2Company2); + + var customer3Company1 = new Company { Name = "First" }; + var customer3Order1 = new Order { Number = 1 }; + Customer3.AddOrder(customer3Order1); + Customer3.AddCompany(customer3Company1); + + session.Save(Customer1); + session.Save(Customer2); + session.Save(Customer3); + + transaction.Commit(); + session.Flush(); + } + } + + protected override void OnTearDown() + { + ClearSecondLevelCacheFor(typeof(Customer)); + ClearCollectionCache(n => n.Orders); + ClearCollectionCache(n => n.Companies); + ClearSecondLevelCacheFor(typeof(Order)); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public virtual async Task ChildCollectionsFromLeftOuterJoinWithOnClauseRestrictionOnCollectionShouldNotBeInSecondLevelCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = await (GetCustomersByOrderNumberUsingOnClauseAsync(firstSession, OrderNumber)); + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual async Task ChildCollectionsWithSelectModeFetchOnCollectionShouldNotBeInSecondLevelCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = await (GetCustomersByOrderNumberUsingFetchAsync(firstSession, OrderNumber)); + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2, + Has.Count.EqualTo(2)); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual async Task ChildCollectionsWithSelectModeFetchAndWhereClauseShouldNotBeInSecondLevelCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = await (GetCustomersByOrderNumberUsingFetchAndWhereClauseAsync(firstSession, OrderNumber)); + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2, + Has.Count.EqualTo(2)); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + + [Test] + public async Task ChildCollectionsFromLeftOuterJoinWithWhereClauseRestrictionOnCollectionShouldNotBeInSecondLevelCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = await (GetCustomersByOrderNumberUsingWhereClauseAsync(firstSession, OrderNumber)); + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public async Task ChildCollectionsEagerFetchedShouldBeInSecondLevelCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = await (GetCustomersWithOrdersEagerLoadedAsync(firstSession)); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count)); + + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public async Task ChildCollectionsFromLeftOuterJoinWithWhereClauseRestrictionOnRootShouldBeInSecondLevelCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = await (GetCustomersByNameUsingWhereClauseAsync(firstSession, "First Customer")); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = await (secondSession.GetAsync(Customer1.Id)); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public async Task ChildCollectionsFromLeftOuterJoinShouldBeInSecondLevelCacheIfQueryContainsSubqueryWithRestrictionOnLeftOuterJoinAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = + await (GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClauseAsync( + firstSession, + OrderNumber, + Customer1.Name)); + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count)); + + using (var thirdSession = OpenSession()) + using (IDbCommand cmd = thirdSession.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(0)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual async Task ChildCollectionsFromLeftOuterJoinOnlyWithRestrictionShouldNotBeIn2LvlCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2AndCompanies = + await (GetCustomersWithCompaniesByOrderNumberUsingOnClauseAsync(firstSession, OrderNumber)); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Companies"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count)); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count)); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count)); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(0)); + + Assert.That( + customers.Single(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count)); + Assert.That( + customers.Single(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count)); + Assert.That( + customers.Single(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual async Task ChildCollectionsWithoutRestrictionShouldBeIn2LvlCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrdersAndCompaniesWithoutRestrictions = + await (GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictionsAsync(firstSession)); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Companies"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count())); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count())); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count())); + + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count)); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count)); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count)); + + Assert.That( + customers.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count())); + Assert.That( + customers.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count())); + Assert.That( + customers.First(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count())); + + Assert.That( + customers.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count())); + Assert.That( + customers.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count())); + Assert.That( + customers.First(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count())); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual async Task ChildCollectionsWithRestrictionShouldNotBeIn2LvlCacheAsync() + { + var firstSession = OpenSession(); + var customersWithOrdersAndCompaniesWithRestrictions = + await (GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClauseAsync(firstSession, OrderNumber, "Second")); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Companies"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = await (GetAllCustomersAsync(secondSession)); + + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count(n => n.Name == "Second"))); + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count(n => n.Name == "Second"))); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(0)); + + Assert.That( + customers.Single(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(0)); + Assert.That( + customers.Single(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(0)); + Assert.That( + customers.Single(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(0)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + protected async Task ClearSecondLevelCacheForAsync(System.Type entity, CancellationToken cancellationToken = default(CancellationToken)) + { + var entityName = entity.FullName; + await (Sfi.EvictEntityAsync(entityName, cancellationToken)); + var entityPersister = Sfi.GetEntityPersister(entityName); + if (!entityPersister.HasCache) + return; + + var querySpaces = entityPersister.QuerySpaces.ToList().AsReadOnly(); + await (Sfi.UpdateTimestampsCache.PreInvalidateAsync(querySpaces, cancellationToken)); + } + + protected void ClearSecondLevelCacheFor(System.Type entity) + { + var entityName = entity.FullName; + Sfi.EvictEntity(entityName); + var entityPersister = Sfi.GetEntityPersister(entityName); + if (!entityPersister.HasCache) + return; + + var querySpaces = entityPersister.QuerySpaces.ToList().AsReadOnly(); + Sfi.UpdateTimestampsCache.PreInvalidate(querySpaces); + } + + protected Task ClearCollectionCacheAsync(Expression> pathToCollection, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + var rootEntityTypeFullPath = typeof(T).FullName; + var memberExpression = pathToCollection.Body as MemberExpression; + if (memberExpression == null) + return Task.FromException(new ArgumentException("pathToCollection should be member expression")); + + var role = $"{rootEntityTypeFullPath}.{memberExpression.Member.Name}"; + return Sfi.EvictCollectionAsync(role, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + protected void ClearCollectionCache(Expression> pathToCollection) + { + var rootEntityTypeFullPath = typeof(T).FullName; + var memberExpression = pathToCollection.Body as MemberExpression; + if (memberExpression == null) + throw new ArgumentException("pathToCollection should be member expression"); + + var role = $"{rootEntityTypeFullPath}.{memberExpression.Member.Name}"; + Sfi.EvictCollection(role); + } + + protected abstract Task> GetCustomersWithOrdersEagerLoadedAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersByOrderNumberUsingOnClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersByOrderNumberUsingWhereClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersByNameUsingWhereClauseAsync(ISession session, string customerName, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersByOrderNumberUsingFetchAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersByOrderNumberUsingFetchAndWhereClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClauseAsync(ISession session, int orderNumber, string name, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictionsAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClauseAsync( + ISession session, + int orderNumber, + string customerName, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetCustomersWithCompaniesByOrderNumberUsingOnClauseAsync( + ISession session, + int orderNumber, CancellationToken cancellationToken = default(CancellationToken)); + protected abstract Task> GetAllCustomersAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/QueryOverTestFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/QueryOverTestFixture.cs new file mode 100644 index 00000000000..d38763a2748 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/QueryOverTestFixture.cs @@ -0,0 +1,272 @@ +//------------------------------------------------------------------------------ +// +// 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.Collections.Generic; +using NHibernate.Criterion; +using NHibernate.SqlCommand; +using NHibernate.Transform; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + using System.Threading.Tasks; + using System.Threading; + public class QueryOverTestFixtureAsync : FixtureAsync + { + protected override Task> GetCustomersWithOrdersEagerLoadedAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return + session + .QueryOver() + .Fetch(SelectMode.Fetch, n => n.Orders) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingOnClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingFetchAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + + return + session + .QueryOver() + .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingFetchAndWhereClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + + return + session + .QueryOver() + .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin) + .Where(Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClauseAsync(ISession session, int orderNumber, string name, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + Company companiesAlias = null; + + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.InnerJoin, + Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Name", name)) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictionsAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + Company companiesAlias = null; + + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.InnerJoin) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersWithCompaniesByOrderNumberUsingOnClauseAsync( + ISession session, + int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + Company companiesAlias = null; + + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingWhereClauseAsync(ISession session, int orderNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + return + session + .QueryOver() + .JoinQueryOver(n => n.Orders, () => ordersAlias, JoinType.LeftOuterJoin) + .Where(Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByNameUsingWhereClauseAsync(ISession session, string customerName, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin) + .Where(Restrictions.Eq("Name", customerName)) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClauseAsync( + ISession session, + int orderNumber, + string customerName, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + Order ordersAlias = null; + Order ordersAlias2 = null; + var subquery = + QueryOver + .Of() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .Select(n => n.Id); + + return + session + .QueryOver() + .JoinAlias(n => n.Orders, () => ordersAlias2, JoinType.LeftOuterJoin) + .WithSubquery.WhereProperty(n => n.Id).In(subquery) + .Where(Restrictions.Eq("Name", customerName)) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + + protected override Task> GetAllCustomersAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return session.QueryOver().ListAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException>(ex); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3848/Company.cs b/src/NHibernate.Test/NHSpecificTest/NH3848/Company.cs new file mode 100644 index 00000000000..42214e07f8b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3848/Company.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + public class Company + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual Customer Customer { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3848/CriteriaTestFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3848/CriteriaTestFixture.cs new file mode 100644 index 00000000000..c0d6597ebe3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3848/CriteriaTestFixture.cs @@ -0,0 +1,132 @@ +using System.Collections.Generic; +using NHibernate.Criterion; +using NHibernate.SqlCommand; +using NHibernate.Transform; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + public class CriteriaTestFixture : Fixture + { + protected override IList GetCustomersWithOrdersEagerLoaded(ISession session) + { + return + session + .CreateCriteria() + .Fetch("Orders") + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingOnClause(ISession session, int orderNumber) + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingFetch(ISession session, int orderNumber) + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, "Orders") + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingFetchAndWhereClause(ISession session, int orderNumber) + { + return + session + .CreateCriteria() + .Fetch(SelectMode.Fetch, "Orders") + .CreateCriteria("Orders", JoinType.InnerJoin) + .Add(Restrictions.Eq("Number", orderNumber)) + .List(); + } + + protected override IList GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClause(ISession session, int orderNumber, string name) + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, "Orders") + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin, Restrictions.Eq("Name", name)) + .List(); + } + + protected override IList GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictions(ISession session) + { + return + session + .CreateCriteria() + .Fetch(SelectMode.Fetch, "Orders") + .CreateAlias("Orders", "order", JoinType.InnerJoin) + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) + .List(); + } + + protected override IList GetCustomersWithCompaniesByOrderNumberUsingOnClause( + ISession session, + int orderNumber) + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingWhereClause(ISession session, int orderNumber) + { + return + session + .CreateCriteria() + .CreateCriteria("Orders", "Order", JoinType.LeftOuterJoin) + .Add(Restrictions.Eq("Number", orderNumber)) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .List(); + } + + protected override IList GetCustomersByNameUsingWhereClause(ISession session, string customerName) + { + return + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin) + .Add(Restrictions.Eq("Name", "First Customer")) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClause( + ISession session, + int orderNumber, + string customerName) + { + var detachedCriteria = + DetachedCriteria + .For() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .SetProjection(Projections.Id()); + + return + session + .CreateCriteria() + .CreateAlias("Orders", "order1", JoinType.LeftOuterJoin) + .Add(Subqueries.PropertyIn("Id", detachedCriteria)) + .Add(Restrictions.Eq("Name", customerName)) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .List(); + } + + protected override IList GetAllCustomers(ISession session) + { + return session.CreateCriteria().List(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3848/Customer.cs b/src/NHibernate.Test/NHSpecificTest/NH3848/Customer.cs new file mode 100644 index 00000000000..135b848c641 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3848/Customer.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + public class Customer + { + public virtual Guid Id { get; set; } + public virtual ISet Orders { get; set; } = new HashSet(); + public virtual ISet Companies { get; set; } = new HashSet(); + public virtual string Name { get; set; } + + public virtual void AddOrder(Order order) + { + Orders.Add(order); + order.Customer = this; + } + + public virtual void AddCompany(Company company) + { + Companies.Add(company); + company.Customer = this; + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3848/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3848/Fixture.cs new file mode 100644 index 00000000000..84fca8232d3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3848/Fixture.cs @@ -0,0 +1,578 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using NHibernate.Cache; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + [TestFixture] + public abstract class Fixture : TestCaseMappingByCode + { + protected Customer Customer1; + protected Customer Customer2; + protected Customer Customer3; + protected const int OrderNumber = 2; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Table("Customers"); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Orders, m => + { + m.Inverse(true); + m.Key(k => + { + k.Column("CustomerId"); + k.NotNullable(true); + }); + m.Cascade(Mapping.ByCode.Cascade.All.Include(Mapping.ByCode.Cascade.DeleteOrphans)); + m.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, m => m.OneToMany()); + + rc.Set(x => x.Companies, m => + { + m.Inverse(true); + m.Key(k => + { + k.Column("CustomerId"); + k.NotNullable(true); + }); + m.Cascade(Mapping.ByCode.Cascade.All.Include(Mapping.ByCode.Cascade.DeleteOrphans)); + m.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, m => m.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Table("Orders"); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Number, m => m.Column("`Number`")); + rc.ManyToOne(x => x.Customer, m => m.Column("CustomerId")); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + mapper.Class(rc => + { + rc.Table("Companies"); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Customer, m => m.Column("CustomerId")); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + configuration.Cache(c => + { + c.UseQueryCache = true; + c.Provider(); + }); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Customer1 = new Customer { Name = "First Customer" }; + + Customer2 = new Customer { Name = "Second Customer" }; + + Customer3 = new Customer { Name = "Third Customer" }; + + var customer1Order1 = new Order { Number = 1 }; + var customer1Company1 = new Company { Name = "First" }; + Customer1.AddOrder(customer1Order1); + Customer1.AddCompany(customer1Company1); + + var customer1Order2 = new Order { Number = 2 }; + var customer1Company2 = new Company { Name = "Second" }; + Customer1.AddOrder(customer1Order2); + Customer1.AddCompany(customer1Company2); + + var customer2Order1 = new Order { Number = 1 }; + var customer2Company1 = new Company { Name = "First" }; + Customer2.AddOrder(customer2Order1); + Customer2.AddCompany(customer2Company1); + + var customer2Order2 = new Order { Number = 2 }; + var customer2Company2 = new Company { Name = "Second" }; + Customer2.AddOrder(customer2Order2); + Customer2.AddCompany(customer2Company2); + + var customer3Company1 = new Company { Name = "First" }; + var customer3Order1 = new Order { Number = 1 }; + Customer3.AddOrder(customer3Order1); + Customer3.AddCompany(customer3Company1); + + session.Save(Customer1); + session.Save(Customer2); + session.Save(Customer3); + + transaction.Commit(); + session.Flush(); + } + } + + protected override void OnTearDown() + { + ClearSecondLevelCacheFor(typeof(Customer)); + ClearCollectionCache(n => n.Orders); + ClearCollectionCache(n => n.Companies); + ClearSecondLevelCacheFor(typeof(Order)); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public virtual void ChildCollectionsFromLeftOuterJoinWithOnClauseRestrictionOnCollectionShouldNotBeInSecondLevelCache() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = GetCustomersByOrderNumberUsingOnClause(firstSession, OrderNumber); + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual void ChildCollectionsWithSelectModeFetchOnCollectionShouldNotBeInSecondLevelCache() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = GetCustomersByOrderNumberUsingFetch(firstSession, OrderNumber); + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2, + Has.Count.EqualTo(2)); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual void ChildCollectionsWithSelectModeFetchAndWhereClauseShouldNotBeInSecondLevelCache() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = GetCustomersByOrderNumberUsingFetchAndWhereClause(firstSession, OrderNumber); + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2, + Has.Count.EqualTo(2)); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + + [Test] + public void ChildCollectionsFromLeftOuterJoinWithWhereClauseRestrictionOnCollectionShouldNotBeInSecondLevelCache() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = GetCustomersByOrderNumberUsingWhereClause(firstSession, OrderNumber); + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public void ChildCollectionsEagerFetchedShouldBeInSecondLevelCache() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = GetCustomersWithOrdersEagerLoaded(firstSession); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count)); + + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(Customer3.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(Customer2.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public void ChildCollectionsFromLeftOuterJoinWithWhereClauseRestrictionOnRootShouldBeInSecondLevelCache() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = GetCustomersByNameUsingWhereClause(firstSession, "First Customer"); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = secondSession.Get(Customer1.Id); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public void ChildCollectionsFromLeftOuterJoinShouldBeInSecondLevelCacheIfQueryContainsSubqueryWithRestrictionOnLeftOuterJoin() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2 = + GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClause( + firstSession, + OrderNumber, + Customer1.Name); + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrderNumberEqualsTo2.Single(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count)); + + using (var thirdSession = OpenSession()) + using (IDbCommand cmd = thirdSession.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(Customer1.Orders.Count)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(0)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual void ChildCollectionsFromLeftOuterJoinOnlyWithRestrictionShouldNotBeIn2LvlCache() + { + var firstSession = OpenSession(); + var customersWithOrderNumberEqualsTo2AndCompanies = + GetCustomersWithCompaniesByOrderNumberUsingOnClause(firstSession, OrderNumber); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Companies"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count)); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count)); + Assert.That( + customersWithOrderNumberEqualsTo2AndCompanies.First(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count)); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(0)); + + Assert.That( + customers.Single(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count)); + Assert.That( + customers.Single(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count)); + Assert.That( + customers.Single(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual void ChildCollectionsWithoutRestrictionShouldBeIn2LvlCache() + { + var firstSession = OpenSession(); + var customersWithOrdersAndCompaniesWithoutRestrictions = + GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictions(firstSession); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Companies"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count())); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count())); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count())); + + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count)); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count)); + Assert.That( + customersWithOrdersAndCompaniesWithoutRestrictions.First(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count)); + + Assert.That( + customers.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count())); + Assert.That( + customers.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count())); + Assert.That( + customers.First(n => n.Id == Customer3.Id).Orders, + Has.Count.EqualTo(Customer3.Orders.Count())); + + Assert.That( + customers.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count())); + Assert.That( + customers.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count())); + Assert.That( + customers.First(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(Customer3.Companies.Count())); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + [Test] + public virtual void ChildCollectionsWithRestrictionShouldNotBeIn2LvlCache() + { + var firstSession = OpenSession(); + var customersWithOrdersAndCompaniesWithRestrictions = + GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClause(firstSession, OrderNumber, "Second"); + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Orders"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + using (var session = OpenSession()) + using (IDbCommand cmd = session.Connection.CreateCommand()) + { + cmd.CommandText = "DELETE FROM Companies"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + } + + var secondSession = OpenSession(); + var customers = GetAllCustomers(secondSession); + + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer1.Id).Orders, + Has.Count.EqualTo(Customer1.Orders.Count(n => n.Number == OrderNumber))); + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer2.Id).Orders, + Has.Count.EqualTo(Customer2.Orders.Count(n => n.Number == OrderNumber))); + + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(Customer1.Companies.Count(n => n.Name == "Second"))); + Assert.That( + customersWithOrdersAndCompaniesWithRestrictions.First(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(Customer2.Companies.Count(n => n.Name == "Second"))); + + Assert.That(customers.Single(n => n.Id == Customer1.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer2.Id).Orders, Has.Count.EqualTo(0)); + Assert.That(customers.Single(n => n.Id == Customer3.Id).Orders, Has.Count.EqualTo(0)); + + Assert.That( + customers.Single(n => n.Id == Customer1.Id).Companies, + Has.Count.EqualTo(0)); + Assert.That( + customers.Single(n => n.Id == Customer2.Id).Companies, + Has.Count.EqualTo(0)); + Assert.That( + customers.Single(n => n.Id == Customer3.Id).Companies, + Has.Count.EqualTo(0)); + + firstSession.Dispose(); + secondSession.Dispose(); + } + + protected void ClearSecondLevelCacheFor(System.Type entity) + { + var entityName = entity.FullName; + Sfi.EvictEntity(entityName); + var entityPersister = Sfi.GetEntityPersister(entityName); + if (!entityPersister.HasCache) + return; + + var querySpaces = entityPersister.QuerySpaces.ToList().AsReadOnly(); + Sfi.UpdateTimestampsCache.PreInvalidate(querySpaces); + } + + protected void ClearCollectionCache(Expression> pathToCollection) + { + var rootEntityTypeFullPath = typeof(T).FullName; + var memberExpression = pathToCollection.Body as MemberExpression; + if (memberExpression == null) + throw new ArgumentException("pathToCollection should be member expression"); + + var role = $"{rootEntityTypeFullPath}.{memberExpression.Member.Name}"; + Sfi.EvictCollection(role); + } + + protected abstract IList GetCustomersWithOrdersEagerLoaded(ISession session); + protected abstract IList GetCustomersByOrderNumberUsingOnClause(ISession session, int orderNumber); + protected abstract IList GetCustomersByOrderNumberUsingWhereClause(ISession session, int orderNumber); + protected abstract IList GetCustomersByNameUsingWhereClause(ISession session, string customerName); + protected abstract IList GetCustomersByOrderNumberUsingFetch(ISession session, int orderNumber); + protected abstract IList GetCustomersByOrderNumberUsingFetchAndWhereClause(ISession session, int orderNumber); + protected abstract IList GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClause(ISession session, int orderNumber, string name); + protected abstract IList GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictions(ISession session); + protected abstract IList GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClause( + ISession session, + int orderNumber, + string customerName); + protected abstract IList GetCustomersWithCompaniesByOrderNumberUsingOnClause( + ISession session, + int orderNumber); + protected abstract IList GetAllCustomers(ISession session); + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3848/Order.cs b/src/NHibernate.Test/NHSpecificTest/NH3848/Order.cs new file mode 100644 index 00000000000..946e98bdeba --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3848/Order.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + public class Order + { + public virtual Guid Id { get; set; } + public virtual int Number { get; set; } + public virtual Customer Customer { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3848/QueryOverTestFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3848/QueryOverTestFixture.cs new file mode 100644 index 00000000000..bc8533de113 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3848/QueryOverTestFixture.cs @@ -0,0 +1,183 @@ +using System.Collections.Generic; +using NHibernate.Criterion; +using NHibernate.SqlCommand; +using NHibernate.Transform; + +namespace NHibernate.Test.NHSpecificTest.NH3848 +{ + public class QueryOverTestFixture : Fixture + { + protected override IList GetCustomersWithOrdersEagerLoaded(ISession session) + { + return + session + .QueryOver() + .Fetch(SelectMode.Fetch, n => n.Orders) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingOnClause(ISession session, int orderNumber) + { + Order ordersAlias = null; + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingFetch(ISession session, int orderNumber) + { + Order ordersAlias = null; + + return + session + .QueryOver() + .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingFetchAndWhereClause(ISession session, int orderNumber) + { + Order ordersAlias = null; + + return + session + .QueryOver() + .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin) + .Where(Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + } + + protected override IList GetCustomersAndCompaniesByOrderNumberUsingFetchAndWhereClause(ISession session, int orderNumber, string name) + { + Order ordersAlias = null; + Company companiesAlias = null; + + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.InnerJoin, + Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Name", name)) + .List(); + } + + protected override IList GetCustomersAndCompaniesByOrderNumberUsingFetchWithoutRestrictions(ISession session) + { + Order ordersAlias = null; + Company companiesAlias = null; + + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.InnerJoin) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin) + .List(); + } + + protected override IList GetCustomersWithCompaniesByOrderNumberUsingOnClause( + ISession session, + int orderNumber) + { + Order ordersAlias = null; + Company companiesAlias = null; + + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingWhereClause(ISession session, int orderNumber) + { + Order ordersAlias = null; + return + session + .QueryOver() + .JoinQueryOver(n => n.Orders, () => ordersAlias, JoinType.LeftOuterJoin) + .Where(Restrictions.Eq("Number", orderNumber)) + .List(); + } + + protected override IList GetCustomersByNameUsingWhereClause(ISession session, string customerName) + { + Order ordersAlias = null; + return + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin) + .Where(Restrictions.Eq("Name", customerName)) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .List(); + } + + protected override IList GetCustomersByOrderNumberUsingSubqueriesAndByNameUsingWhereClause( + ISession session, + int orderNumber, + string customerName) + { + Order ordersAlias = null; + Order ordersAlias2 = null; + var subquery = + QueryOver + .Of() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .Select(n => n.Id); + + return + session + .QueryOver() + .JoinAlias(n => n.Orders, () => ordersAlias2, JoinType.LeftOuterJoin) + .WithSubquery.WhereProperty(n => n.Id).In(subquery) + .Where(Restrictions.Eq("Name", customerName)) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .List(); + } + + protected override IList GetAllCustomers(ISession session) + { + return session.QueryOver().List(); + } + } +} diff --git a/src/NHibernate/Async/Engine/Loading/CollectionLoadContext.cs b/src/NHibernate/Async/Engine/Loading/CollectionLoadContext.cs index be9f1d105ee..921c2a40c52 100644 --- a/src/NHibernate/Async/Engine/Loading/CollectionLoadContext.cs +++ b/src/NHibernate/Async/Engine/Loading/CollectionLoadContext.cs @@ -27,14 +27,33 @@ namespace NHibernate.Engine.Loading public partial class CollectionLoadContext { - /// - /// Finish the process of collection-loading for this bound result set. Mainly this + /// + /// Finish the process of collection-loading for this bound result set. Mainly this /// involves cleaning up resources and notifying the collections that loading is - /// complete. + /// complete. /// - /// The persister for which to complete loading. + /// The persister for which to complete loading. /// A cancellation token that can be used to cancel the work - public async Task EndLoadingCollectionsAsync(ICollectionPersister persister, CancellationToken cancellationToken) + // Since v5.2 + [Obsolete("Please use overload with skipCache parameter instead.")] + public Task EndLoadingCollectionsAsync(ICollectionPersister persister, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return EndLoadingCollectionsAsync(persister, false, cancellationToken); + } + + /// + /// Finish the process of collection-loading for this bound result set. Mainly this + /// involves cleaning up resources and notifying the collections that loading is + /// complete. + /// + /// The persister for which to complete loading. + /// Indicates if collection must not be put in cache. + /// A cancellation token that can be used to cancel the work + public async Task EndLoadingCollectionsAsync(ICollectionPersister persister, bool skipCache, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (!loadContexts.HasLoadingCollectionEntries && (localLoadingCollectionKeys.Count == 0)) @@ -80,7 +99,7 @@ public async Task EndLoadingCollectionsAsync(ICollectionPersister persister, Can } localLoadingCollectionKeys.ExceptWith(toRemove); - await (EndLoadingCollectionsAsync(persister, matches, cancellationToken)).ConfigureAwait(false); + await (EndLoadingCollectionsAsync(persister, matches, skipCache, cancellationToken)).ConfigureAwait(false); if ((localLoadingCollectionKeys.Count == 0)) { // todo : hack!!! @@ -93,7 +112,7 @@ public async Task EndLoadingCollectionsAsync(ICollectionPersister persister, Can } } - private async Task EndLoadingCollectionsAsync(ICollectionPersister persister, IList matchedCollectionEntries, CancellationToken cancellationToken) + private async Task EndLoadingCollectionsAsync(ICollectionPersister persister, IList matchedCollectionEntries, bool skipCache, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (matchedCollectionEntries == null || matchedCollectionEntries.Count == 0) @@ -114,8 +133,11 @@ private async Task EndLoadingCollectionsAsync(ICollectionPersister persister, IL var cacheBatcher = new CacheBatcher(LoadContext.PersistenceContext.Session); for (int i = 0; i < count; i++) { - await (EndLoadingCollectionAsync(matchedCollectionEntries[i], persister, - data => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false); + await (EndLoadingCollectionAsync( + matchedCollectionEntries[i], + persister, + data => cacheBatcher.AddToBatch(persister, data), + skipCache, cancellationToken)).ConfigureAwait(false); } await (cacheBatcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false); @@ -125,8 +147,11 @@ private async Task EndLoadingCollectionsAsync(ICollectionPersister persister, IL } } - private async Task EndLoadingCollectionAsync(LoadingCollectionEntry lce, ICollectionPersister persister, - Action cacheBatchingHandler, CancellationToken cancellationToken) + private async Task EndLoadingCollectionAsync( + LoadingCollectionEntry lce, + ICollectionPersister persister, + Action cacheBatchingHandler, + bool skipCache, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (log.IsDebugEnabled()) @@ -161,8 +186,10 @@ private async Task EndLoadingCollectionAsync(LoadingCollectionEntry lce, ICollec ce.PostInitialize(lce.Collection, persistenceContext); } - bool addToCache = hasNoQueuedOperations && persister.HasCache && - session.CacheMode.HasFlag(CacheMode.Put) && !ce.IsDoremove; // and this is not a forced initialization during flush + bool addToCache = hasNoQueuedOperations && !skipCache && persister.HasCache && + session.CacheMode.HasFlag(CacheMode.Put) && + // and this is not a forced initialization during flush + !ce.IsDoremove; if (addToCache) { diff --git a/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs b/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs index ab3d62dc218..ccae5a22c23 100644 --- a/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs +++ b/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs @@ -8,17 +8,16 @@ //------------------------------------------------------------------------------ -using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Param; +using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Transform; -using NHibernate.Type; using NHibernate.Util; namespace NHibernate.Loader.Criteria @@ -38,7 +37,7 @@ public Task ListAsync(ISessionImplementor session, CancellationToken canc { return ListAsync(session, translator.GetQueryParameters(), querySpaces, cancellationToken); } - catch (Exception ex) + catch (System.Exception ex) { return Task.FromException(ex); } diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index f511e4eebe7..d37de7757ca 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -333,23 +333,23 @@ private async Task DoQueryAsync(ISessionImplementor session, QueryParamet } internal async Task InitializeEntitiesAndCollectionsAsync( - IList hydratedObjects, object resultSetId, ISessionImplementor session, bool readOnly, + IList hydratedObjects, DbDataReader reader, ISessionImplementor session, bool readOnly, CacheBatcher cacheBatcher = null, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ICollectionPersister[] collectionPersisters = CollectionPersisters; if (collectionPersisters != null) { - for (int i = 0; i < collectionPersisters.Length; i++) + foreach (var collectionPersister in collectionPersisters) { - if (collectionPersisters[i].IsArray) + if (collectionPersister.IsArray) { //for arrays, we should end the collection load before resolving //the entities, since the actual array instances are not instantiated //during loading //TODO: or we could do this polymorphically, and have two // different operations implemented differently for arrays - await (EndCollectionLoadAsync(resultSetId, session, collectionPersisters[i], cancellationToken)).ConfigureAwait(false); + await (EndCollectionLoadAsync(reader, session, collectionPersister, cancellationToken)).ConfigureAwait(false); } } } @@ -392,21 +392,21 @@ internal async Task InitializeEntitiesAndCollectionsAsync( if (collectionPersisters != null) { - for (int i = 0; i < collectionPersisters.Length; i++) + foreach (var collectionPersister in collectionPersisters) { - if (!collectionPersisters[i].IsArray) + if (!collectionPersister.IsArray) { //for sets, we should end the collection load after resolving //the entities, since we might call hashCode() on the elements //TODO: or we could do this polymorphically, and have two // different operations implemented differently for arrays - await (EndCollectionLoadAsync(resultSetId, session, collectionPersisters[i], cancellationToken)).ConfigureAwait(false); + await (EndCollectionLoadAsync(reader, session, collectionPersister, cancellationToken)).ConfigureAwait(false); } } } } - private static Task EndCollectionLoadAsync(object resultSetId, ISessionImplementor session, ICollectionPersister collectionPersister, CancellationToken cancellationToken) + private Task EndCollectionLoadAsync(DbDataReader reader, ISessionImplementor session, ICollectionPersister collectionPersister, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -415,8 +415,8 @@ private static Task EndCollectionLoadAsync(object resultSetId, ISessionImplement try { //this is a query and we are loading multiple instances of the same collection role - return session.PersistenceContext.LoadContexts.GetCollectionLoadContext((DbDataReader)resultSetId).EndLoadingCollectionsAsync( - collectionPersister, cancellationToken); + return session.PersistenceContext.LoadContexts.GetCollectionLoadContext(reader).EndLoadingCollectionsAsync( + collectionPersister, !IsCollectionPersisterCacheable(collectionPersister), cancellationToken); } catch (Exception ex) { diff --git a/src/NHibernate/Engine/Loading/CollectionLoadContext.cs b/src/NHibernate/Engine/Loading/CollectionLoadContext.cs index a0a9c46c134..e41b0f9f404 100644 --- a/src/NHibernate/Engine/Loading/CollectionLoadContext.cs +++ b/src/NHibernate/Engine/Loading/CollectionLoadContext.cs @@ -139,13 +139,27 @@ public IPersistentCollection GetLoadingCollection(ICollectionPersister persister } } - /// - /// Finish the process of collection-loading for this bound result set. Mainly this + /// + /// Finish the process of collection-loading for this bound result set. Mainly this /// involves cleaning up resources and notifying the collections that loading is - /// complete. + /// complete. /// - /// The persister for which to complete loading. + /// The persister for which to complete loading. + // Since v5.2 + [Obsolete("Please use overload with skipCache parameter instead.")] public void EndLoadingCollections(ICollectionPersister persister) + { + EndLoadingCollections(persister, false); + } + + /// + /// Finish the process of collection-loading for this bound result set. Mainly this + /// involves cleaning up resources and notifying the collections that loading is + /// complete. + /// + /// The persister for which to complete loading. + /// Indicates if collection must not be put in cache. + public void EndLoadingCollections(ICollectionPersister persister, bool skipCache) { if (!loadContexts.HasLoadingCollectionEntries && (localLoadingCollectionKeys.Count == 0)) { @@ -190,7 +204,7 @@ public void EndLoadingCollections(ICollectionPersister persister) } localLoadingCollectionKeys.ExceptWith(toRemove); - EndLoadingCollections(persister, matches); + EndLoadingCollections(persister, matches, skipCache); if ((localLoadingCollectionKeys.Count == 0)) { // todo : hack!!! @@ -203,7 +217,7 @@ public void EndLoadingCollections(ICollectionPersister persister) } } - private void EndLoadingCollections(ICollectionPersister persister, IList matchedCollectionEntries) + private void EndLoadingCollections(ICollectionPersister persister, IList matchedCollectionEntries, bool skipCache) { if (matchedCollectionEntries == null || matchedCollectionEntries.Count == 0) { @@ -223,8 +237,11 @@ private void EndLoadingCollections(ICollectionPersister persister, IList cacheBatcher.AddToBatch(persister, data)); + EndLoadingCollection( + matchedCollectionEntries[i], + persister, + data => cacheBatcher.AddToBatch(persister, data), + skipCache); } cacheBatcher.ExecuteBatch(); @@ -234,8 +251,11 @@ private void EndLoadingCollections(ICollectionPersister persister, IList cacheBatchingHandler) + private void EndLoadingCollection( + LoadingCollectionEntry lce, + ICollectionPersister persister, + Action cacheBatchingHandler, + bool skipCache) { if (log.IsDebugEnabled()) { @@ -269,8 +289,10 @@ private void EndLoadingCollection(LoadingCollectionEntry lce, ICollectionPersist ce.PostInitialize(lce.Collection, persistenceContext); } - bool addToCache = hasNoQueuedOperations && persister.HasCache && - session.CacheMode.HasFlag(CacheMode.Put) && !ce.IsDoremove; // and this is not a forced initialization during flush + bool addToCache = hasNoQueuedOperations && !skipCache && persister.HasCache && + session.CacheMode.HasFlag(CacheMode.Put) && + // and this is not a forced initialization during flush + !ce.IsDoremove; if (addToCache) { diff --git a/src/NHibernate/Impl/CriteriaImpl.cs b/src/NHibernate/Impl/CriteriaImpl.cs index 511ca3e651f..a86f5062fb6 100644 --- a/src/NHibernate/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Impl/CriteriaImpl.cs @@ -688,6 +688,7 @@ public sealed partial class Subcriteria : ICriteria, ISupportSelectModeCriteria private LockMode lockMode; private readonly JoinType joinType; private ICriterion withClause; + private bool hasRestrictions; internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string alias, JoinType joinType, ICriterion withClause, string joinEntityName = null) { @@ -698,6 +699,7 @@ internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string al this.joinType = joinType; this.withClause = withClause; JoinEntityName = joinEntityName; + hasRestrictions = withClause != null; root.subcriteriaList.Add(this); @@ -731,6 +733,11 @@ public string Path get { return path; } } + public bool HasRestrictions + { + get { return hasRestrictions; } + } + public ICriteria Parent { get { return parent; } @@ -770,6 +777,7 @@ public ICriteria SetLockMode(LockMode lockMode) public ICriteria Add(ICriterion expression) { + hasRestrictions = true; root.Add(this, expression); return this; } diff --git a/src/NHibernate/Loader/Criteria/CriteriaLoader.cs b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs index 658896a5096..8f4b99e58c0 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaLoader.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs @@ -1,14 +1,13 @@ -using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Param; +using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Transform; -using NHibernate.Type; using NHibernate.Util; namespace NHibernate.Loader.Criteria @@ -29,6 +28,9 @@ public partial class CriteriaLoader : OuterJoinLoader private readonly string[] userAliases; private readonly bool[] includeInResultRow; private readonly int resultRowLength; + + private readonly ISet _uncacheableCollectionPersisters; + // caching NH-3486 private readonly string[] cachedProjectedColumnAliases; private bool[] childFetchEntities; @@ -46,6 +48,7 @@ public CriteriaLoader(IOuterJoinLoadable persister, ISessionFactoryImplementor f InitFromWalker(walker); + _uncacheableCollectionPersisters = translator.UncacheableCollectionPersisters; userAliases = walker.UserAliases; ResultTypes = walker.ResultTypes; includeInResultRow = walker.IncludeInResultRow; @@ -146,7 +149,6 @@ protected override object[] GetResultRow(object[] row, DbDataReader rs, ISession return result; } - private object[] ToResultRow(object[] row) { if (resultRowLength == row.Length) @@ -227,5 +229,10 @@ protected override IEnumerable GetParameterSpecificatio { return translator.CollectedParameterSpecifications; } + + protected override bool IsCollectionPersisterCacheable(ICollectionPersister collectionPersister) + { + return !_uncacheableCollectionPersisters.Contains(collectionPersister); + } } } diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index c46efecfc8f..9a9acd430fd 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -41,10 +41,11 @@ public class EntityJoinInfo private readonly IDictionary nameCriteriaInfoMap = new Dictionary(); + private readonly HashSet uncacheableCollectionPersisters = new HashSet(); private readonly ISet criteriaCollectionPersisters = new HashSet(); private readonly IDictionary criteriaSQLAliasMap = new Dictionary(); private readonly IDictionary aliasCriteriaMap = new Dictionary(); - private readonly IDictionary associationPathCriteriaMap = new LinkedHashMap(); + private readonly Dictionary associationPathCriteriaMap = new Dictionary(); private readonly IDictionary associationPathJoinTypesMap = new LinkedHashMap(); private readonly IDictionary withClauseMap = new Dictionary(); private readonly ISessionFactoryImplementor sessionFactory; @@ -212,6 +213,8 @@ public string[] ProjectedAliases get { return rootCriteria.Projection.Aliases; } } + public ISet UncacheableCollectionPersisters => uncacheableCollectionPersisters; + public IList GetEntityProjections() { return entityProjections; @@ -292,8 +295,7 @@ public JoinType GetJoinType(string path) public ICriteria GetCriteria(string path) { - ICriteria result; - associationPathCriteriaMap.TryGetValue(path, out result); + associationPathCriteriaMap.TryGetValue(path, out var result); logger.Debug("getCriteria for path={0} crit={1}", path, result); return result; } @@ -403,7 +405,7 @@ private void CreateCriteriaEntityNameMap() nameCriteriaInfoMap.Add(rootProvider.Name, rootProvider); - foreach (KeyValuePair me in associationPathCriteriaMap) + foreach (KeyValuePair me in associationPathCriteriaMap) { ICriteriaInfoProvider info = GetPathInfo(me.Key, rootProvider); criteriaInfoMap.Add(me.Value, info); @@ -430,12 +432,16 @@ private void CreateEntityJoinMap() private void CreateCriteriaCollectionPersisters() { - foreach (KeyValuePair me in associationPathCriteriaMap) + foreach (KeyValuePair me in associationPathCriteriaMap) { - NHibernate_Persister_Entity.IJoinable joinable = GetPathJoinable(me.Key); - if (joinable != null && joinable.IsCollection) + if (GetPathJoinable(me.Key) is ICollectionPersister collectionPersister) { - criteriaCollectionPersisters.Add((ICollectionPersister)joinable); + criteriaCollectionPersisters.Add(collectionPersister); + + if (collectionPersister.HasCache && me.Value.HasRestrictions) + { + uncacheableCollectionPersisters.Add(collectionPersister); + } } } } diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index c138c22bfb5..d50d0200dcc 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -600,22 +600,22 @@ private IEnumerable CreateSubselects(IList keys, Qu } internal void InitializeEntitiesAndCollections( - IList hydratedObjects, object resultSetId, ISessionImplementor session, bool readOnly, + IList hydratedObjects, DbDataReader reader, ISessionImplementor session, bool readOnly, CacheBatcher cacheBatcher = null) { ICollectionPersister[] collectionPersisters = CollectionPersisters; if (collectionPersisters != null) { - for (int i = 0; i < collectionPersisters.Length; i++) + foreach (var collectionPersister in collectionPersisters) { - if (collectionPersisters[i].IsArray) + if (collectionPersister.IsArray) { //for arrays, we should end the collection load before resolving //the entities, since the actual array instances are not instantiated //during loading //TODO: or we could do this polymorphically, and have two // different operations implemented differently for arrays - EndCollectionLoad(resultSetId, session, collectionPersisters[i]); + EndCollectionLoad(reader, session, collectionPersister); } } } @@ -658,28 +658,32 @@ internal void InitializeEntitiesAndCollections( if (collectionPersisters != null) { - for (int i = 0; i < collectionPersisters.Length; i++) + foreach (var collectionPersister in collectionPersisters) { - if (!collectionPersisters[i].IsArray) + if (!collectionPersister.IsArray) { //for sets, we should end the collection load after resolving //the entities, since we might call hashCode() on the elements //TODO: or we could do this polymorphically, and have two // different operations implemented differently for arrays - EndCollectionLoad(resultSetId, session, collectionPersisters[i]); + EndCollectionLoad(reader, session, collectionPersister); } } } } - private static void EndCollectionLoad(object resultSetId, ISessionImplementor session, ICollectionPersister collectionPersister) + private void EndCollectionLoad(DbDataReader reader, ISessionImplementor session, ICollectionPersister collectionPersister) { //this is a query and we are loading multiple instances of the same collection role - session.PersistenceContext.LoadContexts.GetCollectionLoadContext((DbDataReader)resultSetId).EndLoadingCollections( - collectionPersister); + session.PersistenceContext.LoadContexts.GetCollectionLoadContext(reader).EndLoadingCollections( + collectionPersister, !IsCollectionPersisterCacheable(collectionPersister)); + } + + protected virtual bool IsCollectionPersisterCacheable(ICollectionPersister collectionPersister) + { + return true; } - /// /// Determine the actual ResultTransformer that will be used to transform query results. ///