diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2559/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2559/FixtureByCode.cs new file mode 100644 index 00000000000..8dea23c6d76 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2559/FixtureByCode.cs @@ -0,0 +1,156 @@ +//------------------------------------------------------------------------------ +// +// 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.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + using System.Threading.Tasks; + [TestFixture] + public class ByCodeFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Guid)); + rc.Property(x => x.Name); + rc.Property(x => x.Age); + rc.Set( + x => x.Children, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Set( + x => x.Cars, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Parent); + + ch.Set( + x => x.Pets, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var person = new Person { Name = "Person 1", Age = 18 }; + + var car1 = new Car { Name = "Car1", Owner = person }; + var car2 = new Car { Name = "Car2", Owner = person }; + session.Save(car1); + session.Save(car2); + + session.Save(person); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Pet").ExecuteUpdate(); + session.CreateQuery("delete from Child").ExecuteUpdate(); + session.CreateQuery("delete from Car").ExecuteUpdate(); + session.CreateQuery("delete from Person").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public async Task TestQueryCachingWithThenFetchManyAsync() + { + Person dbPerson; + Person cachePerson; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = + session + .Query() + .FetchMany(p => p.Children) + .ThenFetchMany(ch => ch.Pets) + .FetchMany(p => p.Cars) as IQueryable; + + query = query.WithOptions(opt => + opt.SetCacheable(true) + .SetCacheMode(CacheMode.Normal) + .SetCacheRegion("Long_Cache")); + + dbPerson = (await (query.ToListAsync())).First(); // First time the result will be cached + cachePerson = (await (query.ToListAsync())).First(); + + await (transaction.CommitAsync()); + } + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Cars), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Cars), Is.True); + Assert.That(dbPerson.Cars, Has.Count.EqualTo(2)); + Assert.That(cachePerson.Cars, Has.Count.EqualTo(2)); + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Children), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Children), Is.True); + Assert.That(dbPerson.Children, Has.Count.EqualTo(0)); + Assert.That(cachePerson.Children, Has.Count.EqualTo(0)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Car.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Car.cs new file mode 100644 index 00000000000..b93e028c8b6 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Car.cs @@ -0,0 +1,12 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Car + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + + public virtual Person Owner { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Child.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Child.cs new file mode 100644 index 00000000000..43f56d8b701 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Child.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Child + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + + public virtual Person Parent { get; set; } + + public virtual ISet Pets + { + get => _pets ?? (_pets = new HashSet()); + set => _pets = value; + } + private ISet _pets; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/FixtureByCode.cs new file mode 100644 index 00000000000..33348db8bd9 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/FixtureByCode.cs @@ -0,0 +1,145 @@ +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Guid)); + rc.Property(x => x.Name); + rc.Property(x => x.Age); + rc.Set( + x => x.Children, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Set( + x => x.Cars, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Parent); + + ch.Set( + x => x.Pets, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var person = new Person { Name = "Person 1", Age = 18 }; + + var car1 = new Car { Name = "Car1", Owner = person }; + var car2 = new Car { Name = "Car2", Owner = person }; + session.Save(car1); + session.Save(car2); + + session.Save(person); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Pet").ExecuteUpdate(); + session.CreateQuery("delete from Child").ExecuteUpdate(); + session.CreateQuery("delete from Car").ExecuteUpdate(); + session.CreateQuery("delete from Person").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public void TestQueryCachingWithThenFetchMany() + { + Person dbPerson; + Person cachePerson; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = + session + .Query() + .FetchMany(p => p.Children) + .ThenFetchMany(ch => ch.Pets) + .FetchMany(p => p.Cars) as IQueryable; + + query = query.WithOptions(opt => + opt.SetCacheable(true) + .SetCacheMode(CacheMode.Normal) + .SetCacheRegion("Long_Cache")); + + dbPerson = query.ToList().First(); // First time the result will be cached + cachePerson = query.ToList().First(); + + transaction.Commit(); + } + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Cars), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Cars), Is.True); + Assert.That(dbPerson.Cars, Has.Count.EqualTo(2)); + Assert.That(cachePerson.Cars, Has.Count.EqualTo(2)); + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Children), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Children), Is.True); + Assert.That(dbPerson.Children, Has.Count.EqualTo(0)); + Assert.That(cachePerson.Children, Has.Count.EqualTo(0)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Person.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Person.cs new file mode 100644 index 00000000000..8d125021ec9 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Person.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Person + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual int Age { get; set; } + + public virtual ISet Cars + { + get => _cars ?? (_cars = new HashSet()); + set => _cars = value; + } + private ISet _cars; + + public virtual ISet Children + { + get => _children ?? (_children = new HashSet()); + set => _children = value; + } + private ISet _children; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Pet.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Pet.cs new file mode 100644 index 00000000000..7b580d24bd1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Pet.cs @@ -0,0 +1,12 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Pet + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + + public virtual Child Owner { get; set; } + } +} diff --git a/src/NHibernate/Async/Type/CollectionType.cs b/src/NHibernate/Async/Type/CollectionType.cs index b103b445c22..432c11957a8 100644 --- a/src/NHibernate/Async/Type/CollectionType.cs +++ b/src/NHibernate/Async/Type/CollectionType.cs @@ -104,6 +104,11 @@ public override async Task DisassembleAsync(object value, ISessionImplem public override async Task BeforeAssembleAsync(object oid, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + if (oid == null) + { + return; + } + var queryCacheQueue = session.PersistenceContext.BatchFetchQueue.QueryCacheQueue; if (queryCacheQueue == null) { diff --git a/src/NHibernate/Type/CollectionType.cs b/src/NHibernate/Type/CollectionType.cs index 4ac7c434d53..408e03d94ba 100644 --- a/src/NHibernate/Type/CollectionType.cs +++ b/src/NHibernate/Type/CollectionType.cs @@ -160,6 +160,11 @@ public override object Disassemble(object value, ISessionImplementor session, ob public override void BeforeAssemble(object oid, ISessionImplementor session) { + if (oid == null) + { + return; + } + var queryCacheQueue = session.PersistenceContext.BatchFetchQueue.QueryCacheQueue; if (queryCacheQueue == null) {