From 96ef51c42abd377f9fd989806e7e6e45ff9cfa18 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Wed, 21 Dec 2011 06:49:39 +1300 Subject: [PATCH 01/10] NH-2319 - Add ability to query collections with AsQueryable() --- .../Async/NHSpecificTest/NH2319/Fixture.cs | 368 ++++++++++++++++++ .../NHSpecificTest/NH2319/Fixture.cs | 356 +++++++++++++++++ .../NHSpecificTest/NH2319/Model.cs | 26 ++ .../Generic/PersistentGenericBag.cs | 5 +- .../Generic/PersistentGenericIdentifierBag.cs | 6 +- .../Generic/PersistentGenericList.cs | 5 +- .../Generic/PersistentGenericSet.cs | 4 +- .../Async/Engine/ISessionImplementor.cs | 7 + .../Async/Impl/AbstractSessionImpl.cs | 14 + .../Async/Impl/ExpressionQueryImpl.cs | 51 +++ src/NHibernate/Async/Impl/SessionImpl.cs | 127 +++++- .../Async/Impl/StatelessSessionImpl.cs | 10 + .../Generic/PersistentGenericBag.cs | 20 +- .../Generic/PersistentGenericIdentifierBag.cs | 23 +- .../Generic/PersistentGenericList.cs | 20 +- .../Generic/PersistentGenericSet.cs | 19 +- src/NHibernate/Engine/ISessionImplementor.cs | 7 + .../Engine/Query/FilterQueryPlan.cs | 2 +- .../Engine/Query/QueryExpressionPlan.cs | 6 + src/NHibernate/Engine/Query/QueryPlanCache.cs | 18 +- src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs | 17 +- .../Hql/Ast/ANTLR/Tree/FromClause.cs | 7 + src/NHibernate/Impl/AbstractSessionImpl.cs | 13 + src/NHibernate/Impl/ExpressionQueryImpl.cs | 53 ++- src/NHibernate/Impl/SessionImpl.cs | 135 ++++++- src/NHibernate/Impl/StatelessSessionImpl.cs | 10 + src/NHibernate/Linq/DefaultQueryProvider.cs | 17 +- src/NHibernate/Linq/NhQueryable.cs | 8 +- src/NHibernate/Mapping/ByCode/ModelMapper.cs | 4 +- 29 files changed, 1301 insertions(+), 57 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs create mode 100644 src/NHibernate/Async/Impl/ExpressionQueryImpl.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs new file mode 100644 index 00000000000..2ec6fe4fc83 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs @@ -0,0 +1,368 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Collection; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.NH2319 +{ + using System.Threading.Tasks; + [TestFixture] + public abstract class FixtureBaseAsync : TestCaseMappingByCode + { + private Guid _parentId; + private Guid _child1Id; + + [Test] + public async Task ShouldBeAbleToFindChildrenByNameAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parentId)); + + Assert.That(parent, Is.Not.Null); + + var filtered = await (parent.Children + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToListAsync()); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(filtered[0].Id, Is.EqualTo(_child1Id)); + } + } + + [Test] + public async Task ShouldBeAbleToPerformComplexFilteringAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parentId)); + + Assert.NotNull(parent); + + var filtered = await (parent.Children + .AsQueryable() + .Where(x => x.Name == "Piter") + .SelectMany(x => x.GrandChildren) + .Select(x => x.Id) + .CountAsync()); + + Assert.That(filtered, Is.EqualTo(2)); + } + } + + [Test] + public async Task ShouldNotInitializeCollectionWhenPerformingQueryAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parentId)); + Assert.That(parent, Is.Not.Null); + + var persistentCollection = (IPersistentCollection) parent.Children; + + var filtered = await (parent.Children + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToListAsync()); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(persistentCollection.WasInitialized, Is.False); + } + } + + [Test] + public async Task ShouldPerformSqlQueryEvenIfCollectionAlreadyInitializedAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parentId)); + Assert.That(parent, Is.Not.Null); + + var loaded = parent.Children.ToList(); + Assert.That(loaded, Has.Count.EqualTo(2)); + + var countBeforeFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + var filtered = await (parent.Children + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToListAsync()); + + var countAfterFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(countAfterFiltering, Is.EqualTo(countBeforeFiltering + 1)); + } + } + + [Test] + public async Task TestFilterAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parentId)); + Assert.That(parent, Is.Not.Null); + + var children = await ((await (session.CreateFilterAsync(parent.Children, "where this.Name = 'Jack'"))) + .ListAsync()); + + Assert.That(children, Has.Count.EqualTo(1)); + } + } + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("show_sql", "true"); + configuration.SetProperty("generate_statistics", "true"); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var parent1 = new Parent {Name = "Bob"}; + _parentId = (Guid) session.Save(parent1); + + var parent2 = new Parent {Name = "Martin"}; + session.Save(parent2); + + var child1 = new Child + { + Name = "Jack", + Parent = parent1 + }; + parent1.Children.Add(child1); + _child1Id = (Guid) session.Save(child1); + + var child2 = new Child + { + Name = "Piter", + Parent = parent1 + }; + parent1.Children.Add(child2); + session.Save(child2); + + var grandChild1 = new GrandChild + { + Name = "Kate", + Child = child2 + }; + child2.GrandChildren.Add(grandChild1); + session.Save(grandChild1); + + var grandChild2 = new GrandChild + { + Name = "Mary", + Child = child2 + }; + child2.GrandChildren.Add(grandChild2); + session.Save(grandChild2); + + var child3 = new Child + { + Name = "Jack", + Parent = parent2 + }; + parent2.Children.Add(child3); + session.Save(child3); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + } + + [TestFixture] + public class BagFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + rc.Bag(x => x.GrandChildren, map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + } + + [TestFixture] + public class SetFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class ListFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.List( + x => x.Children, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + list.Index(i => i.Column("i")); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + rc.List( + c => c.GrandChildren, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + list.Index(i => i.Column("i")); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class IdBagFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.IdBag( + x => x.Children, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + //rc.ManyToOne(x => x.Parent); + rc.IdBag( + c => c.GrandChildren, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + //rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs new file mode 100644 index 00000000000..5c3371506b5 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs @@ -0,0 +1,356 @@ +using System; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Collection; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH2319 +{ + [TestFixture] + public abstract class FixtureBase : TestCaseMappingByCode + { + private Guid _parentId; + private Guid _child1Id; + + [Test] + public void ShouldBeAbleToFindChildrenByName() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parentId); + + Assert.That(parent, Is.Not.Null); + + var filtered = parent.Children + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToList(); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(filtered[0].Id, Is.EqualTo(_child1Id)); + } + } + + [Test] + public void ShouldBeAbleToPerformComplexFiltering() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parentId); + + Assert.NotNull(parent); + + var filtered = parent.Children + .AsQueryable() + .Where(x => x.Name == "Piter") + .SelectMany(x => x.GrandChildren) + .Select(x => x.Id) + .Count(); + + Assert.That(filtered, Is.EqualTo(2)); + } + } + + [Test] + public void ShouldNotInitializeCollectionWhenPerformingQuery() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parentId); + Assert.That(parent, Is.Not.Null); + + var persistentCollection = (IPersistentCollection) parent.Children; + + var filtered = parent.Children + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToList(); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(persistentCollection.WasInitialized, Is.False); + } + } + + [Test] + public void ShouldPerformSqlQueryEvenIfCollectionAlreadyInitialized() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parentId); + Assert.That(parent, Is.Not.Null); + + var loaded = parent.Children.ToList(); + Assert.That(loaded, Has.Count.EqualTo(2)); + + var countBeforeFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + var filtered = parent.Children + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToList(); + + var countAfterFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(countAfterFiltering, Is.EqualTo(countBeforeFiltering + 1)); + } + } + + [Test] + public void TestFilter() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parentId); + Assert.That(parent, Is.Not.Null); + + var children = session.CreateFilter(parent.Children, "where this.Name = 'Jack'") + .List(); + + Assert.That(children, Has.Count.EqualTo(1)); + } + } + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("show_sql", "true"); + configuration.SetProperty("generate_statistics", "true"); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var parent1 = new Parent {Name = "Bob"}; + _parentId = (Guid) session.Save(parent1); + + var parent2 = new Parent {Name = "Martin"}; + session.Save(parent2); + + var child1 = new Child + { + Name = "Jack", + Parent = parent1 + }; + parent1.Children.Add(child1); + _child1Id = (Guid) session.Save(child1); + + var child2 = new Child + { + Name = "Piter", + Parent = parent1 + }; + parent1.Children.Add(child2); + session.Save(child2); + + var grandChild1 = new GrandChild + { + Name = "Kate", + Child = child2 + }; + child2.GrandChildren.Add(grandChild1); + session.Save(grandChild1); + + var grandChild2 = new GrandChild + { + Name = "Mary", + Child = child2 + }; + child2.GrandChildren.Add(grandChild2); + session.Save(grandChild2); + + var child3 = new Child + { + Name = "Jack", + Parent = parent2 + }; + parent2.Children.Add(child3); + session.Save(child3); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + } + + [TestFixture] + public class BagFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + rc.Bag(x => x.GrandChildren, map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + } + + [TestFixture] + public class SetFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class ListFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.List( + x => x.Children, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + list.Index(i => i.Column("i")); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + rc.List( + c => c.GrandChildren, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + list.Index(i => i.Column("i")); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class IdBagFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.IdBag( + x => x.Children, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + //rc.ManyToOne(x => x.Parent); + rc.IdBag( + c => c.GrandChildren, + list => + { + list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + //rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs new file mode 100644 index 00000000000..fec687c6ee3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH2319 +{ + class Parent + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual ICollection Children { get; set; } = new List(); + } + + class Child + { + public virtual Parent Parent { get; set; } + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual ICollection GrandChildren { get; set; } = new List(); + } + class GrandChild + { + public virtual Child Child { get; set; } + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs index f681e4940b9..4275faabc27 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs @@ -13,8 +13,11 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -27,7 +30,7 @@ namespace NHibernate.Collection.Generic /// /// Contains generated async methods /// - public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IList + public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IList, IQueryable { public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs index b56e346783e..f2a3ed3bcd5 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -14,9 +14,11 @@ using System.Data.Common; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Id; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -28,7 +30,7 @@ namespace NHibernate.Collection.Generic /// /// Contains generated async methods /// - public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IList + public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IList, IQueryable { /// @@ -200,4 +202,4 @@ public override async Task PreInsertAsync(ICollectionPersister persister, Cancel } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs index f8de6b71f80..04c658fcdeb 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs @@ -13,8 +13,11 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -27,7 +30,7 @@ namespace NHibernate.Collection.Generic /// /// Contains generated async methods /// - public partial class PersistentGenericList : AbstractPersistentCollection, IList, IList + public partial class PersistentGenericList : AbstractPersistentCollection, IList, IList, IQueryable { public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs index 9f381dd659f..96138b1b7e3 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs @@ -14,9 +14,11 @@ using System.Data.Common; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using NHibernate.Collection.Generic.SetHelpers; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -29,7 +31,7 @@ namespace NHibernate.Collection.Generic /// /// Contains generated async methods /// - public partial class PersistentGenericSet : AbstractPersistentCollection, ISet + public partial class PersistentGenericSet : AbstractPersistentCollection, ISet, IQueryable { public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Engine/ISessionImplementor.cs b/src/NHibernate/Async/Engine/ISessionImplementor.cs index d24e0437948..f294f5b27af 100644 --- a/src/NHibernate/Async/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Async/Engine/ISessionImplementor.cs @@ -111,6 +111,11 @@ public partial interface ISessionImplementor /// Task ListFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); + /// + /// Execute a filter + /// + Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, CancellationToken cancellationToken); + /// /// Execute a filter (strongly-typed version). /// @@ -176,5 +181,7 @@ public partial interface ISessionImplementor /// Execute a HQL update or delete query Task ExecuteUpdateAsync(IQueryExpression query, QueryParameters queryParameters, CancellationToken cancellationToken); + + Task CreateFilterAsync(object collection, IQueryExpression queryExpression, CancellationToken cancellationToken); } } diff --git a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs index c033e918250..9ab156012d1 100644 --- a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs @@ -92,6 +92,18 @@ public virtual async Task ListAsync(CriteriaImpl criteria, CancellationTo } public abstract Task ListFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); + public async Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var results = (IList)typeof(List<>).MakeGenericType(queryExpression.Type) + .GetConstructor(System.Type.EmptyTypes) + .Invoke(null); + + await (ListFilterAsync(collection, queryExpression, parameters, results, cancellationToken)).ConfigureAwait(false); + return results; + } + protected abstract Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, IList results, CancellationToken cancellationToken); + public abstract Task> ListFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); public abstract Task EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); public abstract Task> EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); @@ -170,6 +182,8 @@ protected async Task AfterOperationAsync(bool success, CancellationToken cancell } } + public abstract Task CreateFilterAsync(object collection, IQueryExpression queryExpression, CancellationToken cancellationToken); + public abstract Task EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); public abstract Task> EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); diff --git a/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs b/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs new file mode 100644 index 00000000000..91e5c6b04c4 --- /dev/null +++ b/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs @@ -0,0 +1,51 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using System.Text; +using NHibernate.Engine; +using NHibernate.Engine.Query; +using NHibernate.Hql.Ast.ANTLR; +using NHibernate.Hql.Ast.ANTLR.Tree; +using NHibernate.Hql.Ast.ANTLR.Util; +using NHibernate.Type; +using NHibernate.Util; + +namespace NHibernate.Impl +{ + using System.Threading.Tasks; + using System.Threading; + + /// + /// Contains generated async methods + /// + partial class ExpressionFilterImpl : ExpressionQueryImpl + { + + public override async Task ListAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + VerifyParameters(); + var namedParams = NamedParams; + Before(); + try + { + return await (Session.ListFilterAsync(collection, ExpandParameters(namedParams), GetQueryParameters(namedParams), cancellationToken)).ConfigureAwait(false); + } + finally + { + After(); + } + } + } +} diff --git a/src/NHibernate/Async/Impl/SessionImpl.cs b/src/NHibernate/Async/Impl/SessionImpl.cs index 0155eb0b253..182ced5e7d5 100644 --- a/src/NHibernate/Async/Impl/SessionImpl.cs +++ b/src/NHibernate/Async/Impl/SessionImpl.cs @@ -236,6 +236,43 @@ public override async Task ListAsync(IQueryExpression queryExpression, QueryPara CheckAndUpdateSessionStatus(); queryParameters.ValidateParameters(); var plan = GetHQLQueryPlan(queryExpression, false); + + await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false); + + bool success = false; + using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called + { + try + { + await (plan.PerformListAsync(queryParameters, this, results, cancellationToken)).ConfigureAwait(false); + success = true; + } + catch (HibernateException) + { + // Do not call Convert on HibernateExceptions + throw; + } + catch (Exception e) + { + throw Convert(e, "Could not execute query"); + } + finally + { + await (AfterOperationAsync(success, cancellationToken)).ConfigureAwait(false); + } + } + } + } + + protected override async Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters queryParameters, IList results, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + using (new SessionIdLoggingContext(SessionId)) + { + CheckAndUpdateSessionStatus(); + queryParameters.ValidateParameters(); + var plan = await (GetFilterQueryPlanAsync(collection, queryExpression, queryParameters, false, cancellationToken)).ConfigureAwait(false); + await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false); bool success = false; @@ -377,13 +414,6 @@ public override async Task EnumerableAsync(IQueryExpression queryEx } } - /// - /// - /// - /// - /// - /// A cancellation token that can be used to cancel the work - /// public async Task CreateFilterAsync(object collection, string queryString, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -391,15 +421,82 @@ public override async Task EnumerableAsync(IQueryExpression queryEx { CheckAndUpdateSessionStatus(); - CollectionFilterImpl filter = - new CollectionFilterImpl(queryString, collection, this, - (await (GetFilterQueryPlanAsync(collection, queryString, null, false, cancellationToken)).ConfigureAwait(false)).ParameterMetadata); + var plan = await (GetFilterQueryPlanAsync(collection, queryString, null, false, cancellationToken)).ConfigureAwait(false); + var filter = new CollectionFilterImpl(queryString, collection, this, plan.ParameterMetadata); //filter.SetComment(queryString); return filter; } } - private async Task GetFilterQueryPlanAsync(object collection, string filter, QueryParameters parameters, bool shallow, CancellationToken cancellationToken) + public override async Task CreateFilterAsync(object collection, IQueryExpression queryExpression, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + using (new SessionIdLoggingContext(SessionId)) + { + CheckAndUpdateSessionStatus(); + + var plan = await (GetFilterQueryPlanAsync(collection, queryExpression, null, false, cancellationToken)).ConfigureAwait(false); + var filter = new ExpressionFilterImpl(plan.QueryExpression, collection, this, plan.ParameterMetadata); + return filter; + } + } + + private async Task GetFilterQueryPlanAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, bool shallow, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + using (new SessionIdLoggingContext(SessionId)) + { + CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); + ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; + + IQueryExpressionPlan plan; + if (roleBeforeFlush == null) + { + // if it was previously unreferenced, we need to flush in order to + // get its state into the database in order to execute query + await (FlushAsync(cancellationToken)).ConfigureAwait(false); + entry = persistenceContext.GetCollectionEntryOrNull(collection); + ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + if (roleAfterFlush == null) + { + throw new QueryException("The collection was unreferenced"); + } + plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); + } + else + { + // otherwise, we only need to flush if there are in-memory changes + // to the queried tables + plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleBeforeFlush.Role, shallow, EnabledFilters); + + if (await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false)) + { + // might need to run a different filter entirely after the flush + // because the collection role may have changed + entry = persistenceContext.GetCollectionEntryOrNull(collection); + ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + if (roleBeforeFlush != roleAfterFlush) + { + if (roleAfterFlush == null) + { + throw new QueryException("The collection was dereferenced"); + } + plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); + } + } + } + + if (parameters != null) + { + parameters.PositionalParameterValues[0] = entry.LoadedKey; + parameters.PositionalParameterTypes[0] = entry.LoadedPersister.KeyType; + } + + return plan; + } + } + + private async Task GetFilterQueryPlanAsync(object collection, string filter, QueryParameters parameters, bool shallow, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (new SessionIdLoggingContext(SessionId)) @@ -412,7 +509,7 @@ private async Task GetFilterQueryPlanAsync(object collection, s CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; - FilterQueryPlan plan; + IQueryExpressionPlan plan; if (roleBeforeFlush == null) { // if it was previously unreferenced, we need to flush in order to @@ -1021,7 +1118,7 @@ private async Task FilterAsync(object collection, string filter, QueryParameters using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - FilterQueryPlan plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, false, cancellationToken)).ConfigureAwait(false); + var plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, false, cancellationToken)).ConfigureAwait(false); bool success = false; using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called @@ -1076,7 +1173,7 @@ public override async Task EnumerableFilterAsync(object collection, using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - FilterQueryPlan plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, true, cancellationToken)).ConfigureAwait(false); + var plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, true, cancellationToken)).ConfigureAwait(false); return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false); } } @@ -1087,7 +1184,7 @@ public override async Task> EnumerableFilterAsync(object colle using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - FilterQueryPlan plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, true, cancellationToken)).ConfigureAwait(false); + var plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, true, cancellationToken)).ConfigureAwait(false); return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Impl/StatelessSessionImpl.cs b/src/NHibernate/Async/Impl/StatelessSessionImpl.cs index 3862392cb28..a5c8a51f222 100644 --- a/src/NHibernate/Async/Impl/StatelessSessionImpl.cs +++ b/src/NHibernate/Async/Impl/StatelessSessionImpl.cs @@ -92,6 +92,11 @@ public override Task ImmediateLoadAsync(string entityName, object id, Ca throw new SessionException("proxies cannot be fetched by a stateless session"); } + public override Task CreateFilterAsync(object collection, IQueryExpression queryExpression, CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + public override async Task ListAsync(IQueryExpression queryExpression, QueryParameters queryParameters, IList results, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -181,6 +186,11 @@ public override Task ListFilterAsync(object collection, string filter, Qu throw new NotSupportedException(); } + protected override Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, IList results, CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + public override Task> ListFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken) { throw new NotSupportedException(); diff --git a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs index f8fec50608c..51dbeaf98b9 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -22,7 +25,7 @@ namespace NHibernate.Collection.Generic /// The underlying collection used is an [Serializable] [DebuggerTypeProxy(typeof (CollectionProxy<>))] - public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IList + public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IList, IQueryable { // TODO NH: find a way to writeonce (no duplicated code from PersistentBag) @@ -504,6 +507,21 @@ public override string ToString() return StringHelper.CollectionToString(_gbag); } + #region IQueryable Members + + [NonSerialized] + IQueryable _queryable; + + Expression IQueryable.Expression => InnerQueryable.Expression; + + System.Type IQueryable.ElementType => InnerQueryable.ElementType; + + IQueryProvider IQueryable.Provider => InnerQueryable.Provider; + + IQueryable InnerQueryable => _queryable ?? (_queryable = new NhQueryable(Session, this)); + + #endregion + /// /// Counts the number of times that the occurs /// in the . diff --git a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs index 774882ee9d2..85bb25d93c5 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -4,9 +4,11 @@ using System.Data.Common; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Id; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -30,7 +32,7 @@ namespace NHibernate.Collection.Generic /// [Serializable] [DebuggerTypeProxy(typeof (CollectionProxy<>))] - public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IList + public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IList, IQueryable { /* NH considerations: * For various reason we know that the underlining type will be a List or a @@ -42,7 +44,7 @@ public partial class PersistentIdentifierBag : AbstractPersistentCollection, private Dictionary _identifiers; //index -> id private IList _values; //element - + public PersistentIdentifierBag() {} public PersistentIdentifierBag(ISessionImplementor session) : base(session) {} @@ -514,5 +516,20 @@ public override int GetHashCode() return (Id != null ? Id.GetHashCode() : 0); } } + + #region IQueryable Members + + [NonSerialized] + IQueryable _queryable; + + Expression IQueryable.Expression => InnerQueryable.Expression; + + System.Type IQueryable.ElementType => InnerQueryable.ElementType; + + IQueryProvider IQueryable.Provider => InnerQueryable.Provider; + + IQueryable InnerQueryable => _queryable ?? (_queryable = new NhQueryable(Session, this)); + + #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Collection/Generic/PersistentGenericList.cs index 15631e9fb68..e913a9d539d 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericList.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -19,7 +22,7 @@ namespace NHibernate.Collection.Generic /// The underlying collection used is a [Serializable] [DebuggerTypeProxy(typeof (CollectionProxy<>))] - public partial class PersistentGenericList : AbstractPersistentCollection, IList, IList + public partial class PersistentGenericList : AbstractPersistentCollection, IList, IList, IQueryable { protected IList WrappedList; @@ -497,7 +500,6 @@ IEnumerator IEnumerable.GetEnumerator() #endregion - #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() @@ -508,6 +510,20 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + #region IQueryable Members + + [NonSerialized] + IQueryable _queryable; + + Expression IQueryable.Expression => InnerQueryable.Expression; + + System.Type IQueryable.ElementType => InnerQueryable.ElementType; + + IQueryProvider IQueryable.Provider => InnerQueryable.Provider; + + IQueryable InnerQueryable => _queryable ?? (_queryable = new NhQueryable(Session, this)); + + #endregion #region DelayedOperations diff --git a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs index bf25259c16d..f7b6110f578 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs @@ -4,9 +4,11 @@ using System.Data.Common; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using NHibernate.Collection.Generic.SetHelpers; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -19,7 +21,7 @@ namespace NHibernate.Collection.Generic /// [Serializable] [DebuggerTypeProxy(typeof(CollectionProxy<>))] - public partial class PersistentGenericSet : AbstractPersistentCollection, ISet + public partial class PersistentGenericSet : AbstractPersistentCollection, ISet, IQueryable { /// /// The that NHibernate is wrapping. @@ -492,7 +494,6 @@ public bool IsSynchronized get { return false; } } - void ICollection.Add(T item) { Add(item); @@ -520,6 +521,20 @@ public IEnumerator GetEnumerator() #endregion + #region IQueryable Members + + [NonSerialized] + IQueryable _queryable; + + Expression IQueryable.Expression => InnerQueryable.Expression; + + System.Type IQueryable.ElementType => InnerQueryable.ElementType; + + IQueryProvider IQueryable.Provider => InnerQueryable.Provider; + + IQueryable InnerQueryable => _queryable ?? (_queryable = new NhQueryable(Session, this)); + + #endregion #region DelayedOperations diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index 86c2e5945eb..934e8669d20 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -123,6 +123,11 @@ public partial interface ISessionImplementor /// IList ListFilter(object collection, string filter, QueryParameters parameters); + /// + /// Execute a filter + /// + IList ListFilter(object collection, IQueryExpression queryExpression, QueryParameters parameters); + /// /// Execute a filter (strongly-typed version). /// @@ -317,6 +322,8 @@ public partial interface ISessionImplementor void CloseSessionFromSystemTransaction(); + IQuery CreateFilter(object collection, IQueryExpression queryExpression); + EntityKey GenerateEntityKey(object id, IEntityPersister persister); CacheKey GenerateCacheKey(object id, IType type, string entityOrRoleName); diff --git a/src/NHibernate/Engine/Query/FilterQueryPlan.cs b/src/NHibernate/Engine/Query/FilterQueryPlan.cs index 33faa73eee1..330899f1f98 100644 --- a/src/NHibernate/Engine/Query/FilterQueryPlan.cs +++ b/src/NHibernate/Engine/Query/FilterQueryPlan.cs @@ -13,7 +13,7 @@ public class FilterQueryPlan : QueryExpressionPlan private readonly string collectionRole; public FilterQueryPlan(IQueryExpression queryExpression, string collectionRole, bool shallow, IDictionary enabledFilters, ISessionFactoryImplementor factory) - : base(queryExpression.Key, CreateTranslators(queryExpression, collectionRole, shallow, enabledFilters, factory)) + : base(queryExpression, collectionRole, shallow, enabledFilters, factory) { this.collectionRole = collectionRole; } diff --git a/src/NHibernate/Engine/Query/QueryExpressionPlan.cs b/src/NHibernate/Engine/Query/QueryExpressionPlan.cs index d2ff2cef1bd..617e31e1ced 100644 --- a/src/NHibernate/Engine/Query/QueryExpressionPlan.cs +++ b/src/NHibernate/Engine/Query/QueryExpressionPlan.cs @@ -10,6 +10,12 @@ public class QueryExpressionPlan : HQLQueryPlan, IQueryExpressionPlan { public IQueryExpression QueryExpression { get; private set; } + public QueryExpressionPlan(IQueryExpression queryExpression, string collectionRole, bool shallow, IDictionary enabledFilters, ISessionFactoryImplementor factory) + : this(queryExpression.Key, CreateTranslators(queryExpression, collectionRole, shallow, enabledFilters, factory)) + { + QueryExpression = queryExpression; + } + public QueryExpressionPlan(IQueryExpression queryExpression, bool shallow, IDictionary enabledFilters, ISessionFactoryImplementor factory) : this(queryExpression.Key, CreateTranslators(queryExpression, null, shallow, enabledFilters, factory)) { diff --git a/src/NHibernate/Engine/Query/QueryPlanCache.cs b/src/NHibernate/Engine/Query/QueryPlanCache.cs index b62021874fe..8782cad0854 100644 --- a/src/NHibernate/Engine/Query/QueryPlanCache.cs +++ b/src/NHibernate/Engine/Query/QueryPlanCache.cs @@ -89,26 +89,30 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo return plan; } - public FilterQueryPlan GetFilterQueryPlan(string filterString, string collectionRole, bool shallow, IDictionary enabledFilters) + public IQueryExpressionPlan GetFilterQueryPlan(string filterString, string collectionRole, bool shallow, IDictionary enabledFilters) { - var key = new FilterQueryPlanKey(filterString, collectionRole, shallow, enabledFilters); - var plan = (FilterQueryPlan) planCache[key]; + return GetFilterQueryPlan(new StringQueryExpression(filterString), collectionRole, shallow, enabledFilters); + } + + public IQueryExpressionPlan GetFilterQueryPlan(IQueryExpression queryExpression, string collectionRole, bool shallow, IDictionary enabledFilters) + { + var key = new FilterQueryPlanKey(queryExpression.Key, collectionRole, shallow, enabledFilters); + var plan = (IQueryExpressionPlan) planCache[key]; if (plan == null) { if (log.IsDebugEnabled) { - log.Debug("unable to locate collection-filter query plan in cache; generating (" + collectionRole + " : " - + filterString + ")"); + log.Debug(string.Format("unable to locate collection-filter query plan in cache; generating ({0} : {1})", collectionRole, queryExpression.Key)); } - plan = new FilterQueryPlan(filterString.ToQueryExpression(), collectionRole, shallow, enabledFilters, factory); + plan = new FilterQueryPlan(queryExpression, collectionRole, shallow, enabledFilters, factory); planCache.Put(key, plan); } else { if (log.IsDebugEnabled) { - log.Debug("located collection-filter query plan in cache (" + collectionRole + " : " + filterString + ")"); + log.Debug(string.Format("located collection-filter query plan in cache ({0} : {1})", collectionRole, queryExpression.Key)); } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index 1cec6041a05..e6131efd52b 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -730,7 +730,17 @@ IASTNode CreateFromElement(string path, IASTNode pathNode, IASTNode alias, IASTN IASTNode CreateFromFilterElement(IASTNode filterEntity, IASTNode alias) { - FromElement fromElement = _currentFromClause.AddFromElement(filterEntity.Text, alias); + var fromElementFound = true; + + var fromElement = _currentFromClause.GetFromElement(alias.Text) ?? + _currentFromClause.GetFromElementByClassName(filterEntity.Text); + + if (fromElement == null) + { + fromElementFound = false; + fromElement = _currentFromClause.AddFromElement(filterEntity.Text, alias); + } + FromClause fromClause = fromElement.FromClause; IQueryableCollection persister = _sessionFactoryHelper.GetCollectionPersister(_collectionFilterRole); @@ -760,7 +770,10 @@ IASTNode CreateFromFilterElement(IASTNode filterEntity, IASTNode alias) { log.Debug("createFromFilterElement() : processed filter FROM element."); } - + + if (fromElementFound) + return (IASTNode) adaptor.Nil(); + return fromElement; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs index f34c11b0c47..0707c72c694 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; + using Antlr.Runtime; using NHibernate.Hql.Ast.ANTLR.Util; @@ -376,5 +378,10 @@ public virtual void Resolve() } } } + + public FromElement GetFromElementByClassName(string className) + { + return _fromElementByClassAlias.Values.FirstOrDefault(variable => variable.ClassName == className); + } } } diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index ce4c8c2c56d..05c6184c4e0 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -128,6 +128,17 @@ public virtual IList List(CriteriaImpl criteria) } public abstract IList ListFilter(object collection, string filter, QueryParameters parameters); + public IList ListFilter(object collection, IQueryExpression queryExpression, QueryParameters parameters) + { + var results = (IList)typeof(List<>).MakeGenericType(queryExpression.Type) + .GetConstructor(System.Type.EmptyTypes) + .Invoke(null); + + ListFilter(collection, queryExpression, parameters, results); + return results; + } + protected abstract void ListFilter(object collection, IQueryExpression queryExpression, QueryParameters parameters, IList results); + public abstract IList ListFilter(object collection, string filter, QueryParameters parameters); public abstract IEnumerable EnumerableFilter(object collection, string filter, QueryParameters parameters); public abstract IEnumerable EnumerableFilter(object collection, string filter, QueryParameters parameters); @@ -413,6 +424,8 @@ public void JoinTransaction() _factory.TransactionFactory.ExplicitJoinSystemTransaction(this); } + public abstract IQuery CreateFilter(object collection, IQueryExpression queryExpression); + internal IOuterJoinLoadable GetOuterJoinLoadable(string entityName) { using (new SessionIdLoggingContext(SessionId)) diff --git a/src/NHibernate/Impl/ExpressionQueryImpl.cs b/src/NHibernate/Impl/ExpressionQueryImpl.cs index 41ee788d6ae..f647ba2a076 100644 --- a/src/NHibernate/Impl/ExpressionQueryImpl.cs +++ b/src/NHibernate/Impl/ExpressionQueryImpl.cs @@ -8,6 +8,7 @@ using NHibernate.Hql.Ast.ANTLR; using NHibernate.Hql.Ast.ANTLR.Tree; using NHibernate.Hql.Ast.ANTLR.Util; +using NHibernate.Type; using NHibernate.Util; namespace NHibernate.Impl @@ -82,6 +83,56 @@ protected override IQueryExpression ExpandParameters(IDictionary typeList = Types; + int size = typeList.Count; + var result = new IType[size + 1]; + for (int i = 0; i < size; i++) + { + result[i + 1] = typeList[i]; + } + return result; + } + + public override object[] ValueArray() + { + IList valueList = Values; + int size = valueList.Count; + var result = new object[size + 1]; + for (int i = 0; i < size; i++) + { + result[i + 1] = valueList[i]; + } + return result; + } + } + internal class ExpandedQueryExpression : IQueryExpression { private readonly IASTNode _tree; @@ -225,4 +276,4 @@ private IList LocateParameters() return _nodes; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index 486701f7cb1..f7ab6d747b3 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -536,6 +536,18 @@ public override void CloseSessionFromSystemTransaction() Dispose(true); } + public override IQuery CreateQuery(IQueryExpression queryExpression) + { + using (new SessionIdLoggingContext(SessionId)) + { + CheckAndUpdateSessionStatus(); + var plan = GetHQLQueryPlan(queryExpression, false); + var query = new ExpressionQueryImpl(plan.QueryExpression, this, plan.ParameterMetadata); + query.SetComment("[expression]"); + return query; + } + } + public override void List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results) { using (new SessionIdLoggingContext(SessionId)) @@ -543,6 +555,42 @@ public override void List(IQueryExpression queryExpression, QueryParameters quer CheckAndUpdateSessionStatus(); queryParameters.ValidateParameters(); var plan = GetHQLQueryPlan(queryExpression, false); + + AutoFlushIfRequired(plan.QuerySpaces); + + bool success = false; + using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called + { + try + { + plan.PerformList(queryParameters, this, results); + success = true; + } + catch (HibernateException) + { + // Do not call Convert on HibernateExceptions + throw; + } + catch (Exception e) + { + throw Convert(e, "Could not execute query"); + } + finally + { + AfterOperation(success); + } + } + } + } + + protected override void ListFilter(object collection, IQueryExpression queryExpression, QueryParameters queryParameters, IList results) + { + using (new SessionIdLoggingContext(SessionId)) + { + CheckAndUpdateSessionStatus(); + queryParameters.ValidateParameters(); + var plan = GetFilterQueryPlan(collection, queryExpression, queryParameters, false); + AutoFlushIfRequired(plan.QuerySpaces); bool success = false; @@ -676,27 +724,86 @@ public void Lock(string entityName, object obj, LockMode lockMode) } } - /// - /// - /// - /// - /// - /// public IQuery CreateFilter(object collection, string queryString) { using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - CollectionFilterImpl filter = - new CollectionFilterImpl(queryString, collection, this, - GetFilterQueryPlan(collection, queryString, null, false).ParameterMetadata); + var plan = GetFilterQueryPlan(collection, queryString, null, false); + var filter = new CollectionFilterImpl(queryString, collection, this, plan.ParameterMetadata); //filter.SetComment(queryString); return filter; } } - private FilterQueryPlan GetFilterQueryPlan(object collection, string filter, QueryParameters parameters, bool shallow) + public override IQuery CreateFilter(object collection, IQueryExpression queryExpression) + { + using (new SessionIdLoggingContext(SessionId)) + { + CheckAndUpdateSessionStatus(); + + var plan = GetFilterQueryPlan(collection, queryExpression, null, false); + var filter = new ExpressionFilterImpl(plan.QueryExpression, collection, this, plan.ParameterMetadata); + return filter; + } + } + + private IQueryExpressionPlan GetFilterQueryPlan(object collection, IQueryExpression queryExpression, QueryParameters parameters, bool shallow) + { + using (new SessionIdLoggingContext(SessionId)) + { + CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); + ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; + + IQueryExpressionPlan plan; + if (roleBeforeFlush == null) + { + // if it was previously unreferenced, we need to flush in order to + // get its state into the database in order to execute query + Flush(); + entry = persistenceContext.GetCollectionEntryOrNull(collection); + ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + if (roleAfterFlush == null) + { + throw new QueryException("The collection was unreferenced"); + } + plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); + } + else + { + // otherwise, we only need to flush if there are in-memory changes + // to the queried tables + plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleBeforeFlush.Role, shallow, EnabledFilters); + + if (AutoFlushIfRequired(plan.QuerySpaces)) + { + // might need to run a different filter entirely after the flush + // because the collection role may have changed + entry = persistenceContext.GetCollectionEntryOrNull(collection); + ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + if (roleBeforeFlush != roleAfterFlush) + { + if (roleAfterFlush == null) + { + throw new QueryException("The collection was dereferenced"); + } + plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); + } + } + } + + if (parameters != null) + { + parameters.PositionalParameterValues[0] = entry.LoadedKey; + parameters.PositionalParameterTypes[0] = entry.LoadedPersister.KeyType; + } + + return plan; + } + } + + private IQueryExpressionPlan GetFilterQueryPlan(object collection, string filter, QueryParameters parameters, bool shallow) { using (new SessionIdLoggingContext(SessionId)) { @@ -708,7 +815,7 @@ private FilterQueryPlan GetFilterQueryPlan(object collection, string filter, Que CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; - FilterQueryPlan plan; + IQueryExpressionPlan plan; if (roleBeforeFlush == null) { // if it was previously unreferenced, we need to flush in order to @@ -1648,7 +1755,7 @@ private void Filter(object collection, string filter, QueryParameters queryParam using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - FilterQueryPlan plan = GetFilterQueryPlan(collection, filter, queryParameters, false); + var plan = GetFilterQueryPlan(collection, filter, queryParameters, false); bool success = false; using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called @@ -1700,7 +1807,7 @@ public override IEnumerable EnumerableFilter(object collection, string filter, Q using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - FilterQueryPlan plan = GetFilterQueryPlan(collection, filter, queryParameters, true); + var plan = GetFilterQueryPlan(collection, filter, queryParameters, true); return plan.PerformIterate(queryParameters, this); } } @@ -1710,7 +1817,7 @@ public override IEnumerable EnumerableFilter(object collection, string fil using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - FilterQueryPlan plan = GetFilterQueryPlan(collection, filter, queryParameters, true); + var plan = GetFilterQueryPlan(collection, filter, queryParameters, true); return plan.PerformIterate(queryParameters, this); } } diff --git a/src/NHibernate/Impl/StatelessSessionImpl.cs b/src/NHibernate/Impl/StatelessSessionImpl.cs index 32c55b71928..bc0f9aced09 100644 --- a/src/NHibernate/Impl/StatelessSessionImpl.cs +++ b/src/NHibernate/Impl/StatelessSessionImpl.cs @@ -110,6 +110,11 @@ public override void CloseSessionFromSystemTransaction() Dispose(true); } + public override IQuery CreateFilter(object collection, IQueryExpression queryExpression) + { + throw new NotSupportedException(); + } + public override void List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results) { using (new SessionIdLoggingContext(SessionId)) @@ -197,6 +202,11 @@ public override IList ListFilter(object collection, string filter, QueryParamete throw new NotSupportedException(); } + protected override void ListFilter(object collection, IQueryExpression queryExpression, QueryParameters parameters, IList results) + { + throw new NotSupportedException(); + } + public override IList ListFilter(object collection, string filter, QueryParameters parameters) { throw new NotSupportedException(); diff --git a/src/NHibernate/Linq/DefaultQueryProvider.cs b/src/NHibernate/Linq/DefaultQueryProvider.cs index 67410194604..57bd1f18513 100644 --- a/src/NHibernate/Linq/DefaultQueryProvider.cs +++ b/src/NHibernate/Linq/DefaultQueryProvider.cs @@ -36,6 +36,14 @@ public DefaultQueryProvider(ISessionImplementor session) _session = new WeakReference(session); } + public DefaultQueryProvider(ISessionImplementor session, object collection) + : this(session) + { + Collection = collection; + } + + public object Collection { get; } + protected virtual ISessionImplementor Session { get @@ -125,7 +133,14 @@ protected virtual NhLinqExpression PrepareQuery(Expression expression, out IQuer { var nhLinqExpression = new NhLinqExpression(expression, Session.Factory); - query = Session.CreateQuery(nhLinqExpression); + if (Collection == null) + { + query = Session.CreateQuery(nhLinqExpression); + } + else + { + query = Session.CreateFilter(Collection, nhLinqExpression); + } SetParameters(query, nhLinqExpression.ParameterValuesByName); SetResultTransformerAndAdditionalCriteria(query, nhLinqExpression, nhLinqExpression.ParameterValuesByName); diff --git a/src/NHibernate/Linq/NhQueryable.cs b/src/NHibernate/Linq/NhQueryable.cs index 1c6b93711a5..74667daaeee 100644 --- a/src/NHibernate/Linq/NhQueryable.cs +++ b/src/NHibernate/Linq/NhQueryable.cs @@ -46,6 +46,12 @@ public NhQueryable(IQueryProvider provider, Expression expression, string entity EntityName = entityName; } + public NhQueryable(ISessionImplementor session, object collection) + : base(new DefaultQueryProvider(session, collection)) + { + EntityName = typeof(T).FullName; + } + public string EntityName { get; private set; } public override string ToString() @@ -53,4 +59,4 @@ public override string ToString() return "NHibernate.Linq.NhQueryable`1[" + EntityName + "]"; } } -} +} \ No newline at end of file diff --git a/src/NHibernate/Mapping/ByCode/ModelMapper.cs b/src/NHibernate/Mapping/ByCode/ModelMapper.cs index 17f460188ed..6551216b20f 100644 --- a/src/NHibernate/Mapping/ByCode/ModelMapper.cs +++ b/src/NHibernate/Mapping/ByCode/ModelMapper.cs @@ -1240,9 +1240,9 @@ private void MapIdBag(MemberInfo member, PropertyPath propertyPath, System.Type { System.Type collectionElementType = GetCollectionElementTypeOrThrow(propertiesContainerType, member, propertyType); ICollectionElementRelationMapper cert = DetermineCollectionElementRelationType(member, propertyPath, collectionElementType); - if(cert is OneToManyRelationMapper) + if (cert is OneToManyRelationMapper) { - throw new NotSupportedException("id-bag does not suppot one-to-many relation"); + throw new NotSupportedException("id-bag does not support one-to-many relation"); } propertiesContainer.IdBag(member, collectionPropertiesMapper => { From f8ba920941e74aa0472a12e8ffc48acc853c1b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 9 Sep 2017 01:02:48 +0200 Subject: [PATCH 02/10] NH-2319 - Fix plan retrieved from cache case. To be squashed. --- .../Engine/Query/FilterQueryPlan.cs | 18 ++++--- .../Engine/Query/QueryExpressionPlan.cs | 8 +-- src/NHibernate/Engine/Query/QueryPlanCache.cs | 54 ++++++++++--------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/NHibernate/Engine/Query/FilterQueryPlan.cs b/src/NHibernate/Engine/Query/FilterQueryPlan.cs index 330899f1f98..41ed7769443 100644 --- a/src/NHibernate/Engine/Query/FilterQueryPlan.cs +++ b/src/NHibernate/Engine/Query/FilterQueryPlan.cs @@ -10,17 +10,23 @@ namespace NHibernate.Engine.Query [Serializable] public class FilterQueryPlan : QueryExpressionPlan { - private readonly string collectionRole; - public FilterQueryPlan(IQueryExpression queryExpression, string collectionRole, bool shallow, IDictionary enabledFilters, ISessionFactoryImplementor factory) : base(queryExpression, collectionRole, shallow, enabledFilters, factory) { - this.collectionRole = collectionRole; + CollectionRole = collectionRole; } - public string CollectionRole + protected FilterQueryPlan(FilterQueryPlan source, IQueryExpression expression) + : base (source, expression) + { + CollectionRole = source.CollectionRole; + } + + public string CollectionRole { get; } + + public override QueryExpressionPlan Copy(IQueryExpression expression) { - get { return collectionRole; } + return new FilterQueryPlan(this, expression); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Engine/Query/QueryExpressionPlan.cs b/src/NHibernate/Engine/Query/QueryExpressionPlan.cs index 617e31e1ced..0a1184a121f 100644 --- a/src/NHibernate/Engine/Query/QueryExpressionPlan.cs +++ b/src/NHibernate/Engine/Query/QueryExpressionPlan.cs @@ -8,7 +8,7 @@ namespace NHibernate.Engine.Query [Serializable] public class QueryExpressionPlan : HQLQueryPlan, IQueryExpressionPlan { - public IQueryExpression QueryExpression { get; private set; } + public IQueryExpression QueryExpression { get; } public QueryExpressionPlan(IQueryExpression queryExpression, string collectionRole, bool shallow, IDictionary enabledFilters, ISessionFactoryImplementor factory) : this(queryExpression.Key, CreateTranslators(queryExpression, collectionRole, shallow, enabledFilters, factory)) @@ -27,7 +27,7 @@ protected QueryExpressionPlan(string key, IQueryTranslator[] translators) { } - private QueryExpressionPlan(HQLQueryPlan source, IQueryExpression expression) + protected QueryExpressionPlan(HQLQueryPlan source, IQueryExpression expression) : base(source) { QueryExpression = expression; @@ -38,9 +38,9 @@ protected static IQueryTranslator[] CreateTranslators(IQueryExpression queryExpr return factory.Settings.QueryTranslatorFactory.CreateQueryTranslators(queryExpression, collectionRole, shallow, enabledFilters, factory); } - public QueryExpressionPlan Copy(IQueryExpression expression) + public virtual QueryExpressionPlan Copy(IQueryExpression expression) { return new QueryExpressionPlan(this, expression); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Engine/Query/QueryPlanCache.cs b/src/NHibernate/Engine/Query/QueryPlanCache.cs index 8782cad0854..a7083916f45 100644 --- a/src/NHibernate/Engine/Query/QueryPlanCache.cs +++ b/src/NHibernate/Engine/Query/QueryPlanCache.cs @@ -67,23 +67,30 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo { log.Debug("located HQL query plan in cache (" + queryExpression.Key + ")"); } - var planExpression = plan.QueryExpression as NhLinqExpression; - var expression = queryExpression as NhLinqExpression; - if (planExpression != null && expression != null) - { - //NH-3413 - //Here we have to use original expression. - //In most cases NH do not translate expression in second time, but - // for cases when we have list parameters in query, like @p1.Contains(...), - // it does, and then it uses parameters from first try. - //TODO: cache only required parts of QueryExpression - - //NH-3436 - // We have to return new instance plan with it's own query expression - // because other treads can override queryexpression of current plan during execution of query if we will use cached instance of plan - expression.CopyExpressionTranslation(planExpression); - plan = plan.Copy(expression); - } + plan = CopyIfRequired(plan, queryExpression); + } + + return plan; + } + + private static QueryExpressionPlan CopyIfRequired(QueryExpressionPlan plan, IQueryExpression queryExpression) + { + var planExpression = plan.QueryExpression as NhLinqExpression; + var expression = queryExpression as NhLinqExpression; + if (planExpression != null && expression != null) + { + //NH-3413 + //Here we have to use original expression. + //In most cases NH do not translate expression in second time, but + // for cases when we have list parameters in query, like @p1.Contains(...), + // it does, and then it uses parameters from first try. + //TODO: cache only required parts of QueryExpression + + //NH-3436 + // We have to return new instance plan with it's own query expression + // because other treads can override queryexpression of current plan during execution of query if we will use cached instance of plan + expression.CopyExpressionTranslation(planExpression); + plan = plan.Copy(expression); } return plan; @@ -97,23 +104,18 @@ public IQueryExpressionPlan GetFilterQueryPlan(string filterString, string colle public IQueryExpressionPlan GetFilterQueryPlan(IQueryExpression queryExpression, string collectionRole, bool shallow, IDictionary enabledFilters) { var key = new FilterQueryPlanKey(queryExpression.Key, collectionRole, shallow, enabledFilters); - var plan = (IQueryExpressionPlan) planCache[key]; + var plan = (QueryExpressionPlan) planCache[key]; if (plan == null) { - if (log.IsDebugEnabled) - { - log.Debug(string.Format("unable to locate collection-filter query plan in cache; generating ({0} : {1})", collectionRole, queryExpression.Key)); - } + log.DebugFormat("unable to locate collection-filter query plan in cache; generating ({0} : {1})", collectionRole, queryExpression.Key); plan = new FilterQueryPlan(queryExpression, collectionRole, shallow, enabledFilters, factory); planCache.Put(key, plan); } else { - if (log.IsDebugEnabled) - { - log.Debug(string.Format("located collection-filter query plan in cache ({0} : {1})", collectionRole, queryExpression.Key)); - } + log.DebugFormat("located collection-filter query plan in cache ({0} : {1})", collectionRole, queryExpression.Key); + plan = CopyIfRequired(plan, queryExpression); } return plan; From 7f07d1aeb4be5dc62cf91d07d0f92886111c2ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 9 Sep 2017 01:37:57 +0200 Subject: [PATCH 03/10] NH-2319 - Refactoring to be squashed. --- src/NHibernate/Async/Impl/SessionImpl.cs | 142 +++++++------------- src/NHibernate/Impl/SessionImpl.cs | 135 ++++++------------- src/NHibernate/Linq/NhQueryable.cs | 6 +- src/NHibernate/Linq/QueryProviderFactory.cs | 10 +- 4 files changed, 101 insertions(+), 192 deletions(-) diff --git a/src/NHibernate/Async/Impl/SessionImpl.cs b/src/NHibernate/Async/Impl/SessionImpl.cs index 182ced5e7d5..be929645120 100644 --- a/src/NHibernate/Async/Impl/SessionImpl.cs +++ b/src/NHibernate/Async/Impl/SessionImpl.cs @@ -228,52 +228,42 @@ async Task FindAsync(string query, object[] values, IType[] types, Cancel } } - public override async Task ListAsync(IQueryExpression queryExpression, QueryParameters queryParameters, IList results, CancellationToken cancellationToken) + public override Task ListAsync(IQueryExpression queryExpression, QueryParameters queryParameters, IList results, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using (new SessionIdLoggingContext(SessionId)) + if (cancellationToken.IsCancellationRequested) { - CheckAndUpdateSessionStatus(); - queryParameters.ValidateParameters(); - var plan = GetHQLQueryPlan(queryExpression, false); - - await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false); + return Task.FromCanceled(cancellationToken); + } + return ListAsync(queryExpression, queryParameters, results, null, cancellationToken); + } - bool success = false; - using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called - { - try - { - await (plan.PerformListAsync(queryParameters, this, results, cancellationToken)).ConfigureAwait(false); - success = true; - } - catch (HibernateException) - { - // Do not call Convert on HibernateExceptions - throw; - } - catch (Exception e) - { - throw Convert(e, "Could not execute query"); - } - finally - { - await (AfterOperationAsync(success, cancellationToken)).ConfigureAwait(false); - } - } + protected override Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters queryParameters, IList results, CancellationToken cancellationToken) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); } + return ListAsync(queryExpression, queryParameters, results, collection, cancellationToken); } - protected override async Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters queryParameters, IList results, CancellationToken cancellationToken) + private async Task ListAsync(IQueryExpression queryExpression, QueryParameters queryParameters, IList results, object filterConnection, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); queryParameters.ValidateParameters(); - var plan = await (GetFilterQueryPlanAsync(collection, queryExpression, queryParameters, false, cancellationToken)).ConfigureAwait(false); - await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false); + var isFilter = filterConnection != null; + var plan = isFilter + ? await (GetFilterQueryPlanAsync(filterConnection, queryExpression, queryParameters, false, cancellationToken)).ConfigureAwait(false) + : GetHQLQueryPlan(queryExpression, false); + + // GetFilterQueryPlan has already auto flushed or fully flush. + if (!isFilter) + await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false); bool success = false; using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called @@ -441,73 +431,39 @@ public override async Task CreateFilterAsync(object collection, IQueryEx } } - private async Task GetFilterQueryPlanAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, bool shallow, CancellationToken cancellationToken) + private Task GetFilterQueryPlanAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, bool shallow, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using (new SessionIdLoggingContext(SessionId)) + if (cancellationToken.IsCancellationRequested) { - CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; - - IQueryExpressionPlan plan; - if (roleBeforeFlush == null) - { - // if it was previously unreferenced, we need to flush in order to - // get its state into the database in order to execute query - await (FlushAsync(cancellationToken)).ConfigureAwait(false); - entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; - if (roleAfterFlush == null) - { - throw new QueryException("The collection was unreferenced"); - } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); - } - else - { - // otherwise, we only need to flush if there are in-memory changes - // to the queried tables - plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleBeforeFlush.Role, shallow, EnabledFilters); - - if (await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false)) - { - // might need to run a different filter entirely after the flush - // because the collection role may have changed - entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; - if (roleBeforeFlush != roleAfterFlush) - { - if (roleAfterFlush == null) - { - throw new QueryException("The collection was dereferenced"); - } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); - } - } - } - - if (parameters != null) - { - parameters.PositionalParameterValues[0] = entry.LoadedKey; - parameters.PositionalParameterTypes[0] = entry.LoadedPersister.KeyType; - } + return Task.FromCanceled(cancellationToken); + } + return GetFilterQueryPlanAsync(collection, parameters, shallow, null, queryExpression, cancellationToken); + } - return plan; + private Task GetFilterQueryPlanAsync(object collection, string filter, QueryParameters parameters, bool shallow, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); } + return GetFilterQueryPlanAsync(collection, parameters, shallow, filter, null, cancellationToken); } - private async Task GetFilterQueryPlanAsync(object collection, string filter, QueryParameters parameters, bool shallow, CancellationToken cancellationToken) + private async Task GetFilterQueryPlanAsync(object collection, QueryParameters parameters, bool shallow, + string filter, IQueryExpression queryExpression, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (new SessionIdLoggingContext(SessionId)) { if (collection == null) - { - throw new ArgumentNullException("collection", "null collection passed to filter"); - } + throw new ArgumentNullException(nameof(collection), "null collection passed to filter"); + if (filter != null && queryExpression != null) + throw new ArgumentException($"Either {nameof(filter)} or {nameof(queryExpression)} must be specified, not both."); + if (filter == null && queryExpression == null) + throw new ArgumentException($"{nameof(filter)} and {nameof(queryExpression)} were both null."); - CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; + var entry = persistenceContext.GetCollectionEntryOrNull(collection); + var roleBeforeFlush = entry?.LoadedPersister; IQueryExpressionPlan plan; if (roleBeforeFlush == null) @@ -516,31 +472,31 @@ private async Task GetFilterQueryPlanAsync(object collecti // get its state into the database in order to execute query await (FlushAsync(cancellationToken)).ConfigureAwait(false); entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + var roleAfterFlush = entry?.LoadedPersister; if (roleAfterFlush == null) { throw new QueryException("The collection was unreferenced"); } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(filter, roleAfterFlush.Role, shallow, EnabledFilters); + plan = GetFilterQueryPlan(roleAfterFlush.Role, shallow, filter, queryExpression); } else { // otherwise, we only need to flush if there are in-memory changes // to the queried tables - plan = Factory.QueryPlanCache.GetFilterQueryPlan(filter, roleBeforeFlush.Role, shallow, EnabledFilters); + plan = GetFilterQueryPlan(roleBeforeFlush.Role, shallow, filter, queryExpression); if (await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false)) { // might need to run a different filter entirely after the flush // because the collection role may have changed entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + var roleAfterFlush = entry?.LoadedPersister; if (roleBeforeFlush != roleAfterFlush) { if (roleAfterFlush == null) { throw new QueryException("The collection was dereferenced"); } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(filter, roleAfterFlush.Role, shallow, EnabledFilters); + plan = GetFilterQueryPlan(roleAfterFlush.Role, shallow, filter, queryExpression); } } } diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index f7ab6d747b3..8ac98f96ffe 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -550,48 +550,31 @@ public override IQuery CreateQuery(IQueryExpression queryExpression) public override void List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results) { - using (new SessionIdLoggingContext(SessionId)) - { - CheckAndUpdateSessionStatus(); - queryParameters.ValidateParameters(); - var plan = GetHQLQueryPlan(queryExpression, false); - - AutoFlushIfRequired(plan.QuerySpaces); - - bool success = false; - using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called - { - try - { - plan.PerformList(queryParameters, this, results); - success = true; - } - catch (HibernateException) - { - // Do not call Convert on HibernateExceptions - throw; - } - catch (Exception e) - { - throw Convert(e, "Could not execute query"); - } - finally - { - AfterOperation(success); - } - } - } + List(queryExpression, queryParameters, results, null); } protected override void ListFilter(object collection, IQueryExpression queryExpression, QueryParameters queryParameters, IList results) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + List(queryExpression, queryParameters, results, collection); + } + + private void List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results, object filterConnection) { using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); queryParameters.ValidateParameters(); - var plan = GetFilterQueryPlan(collection, queryExpression, queryParameters, false); - AutoFlushIfRequired(plan.QuerySpaces); + var isFilter = filterConnection != null; + var plan = isFilter + ? GetFilterQueryPlan(filterConnection, queryExpression, queryParameters, false) + : GetHQLQueryPlan(queryExpression, false); + + // GetFilterQueryPlan has already auto flushed or fully flush. + if (!isFilter) + AutoFlushIfRequired(plan.QuerySpaces); bool success = false; using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called @@ -751,69 +734,28 @@ public override IQuery CreateFilter(object collection, IQueryExpression queryExp private IQueryExpressionPlan GetFilterQueryPlan(object collection, IQueryExpression queryExpression, QueryParameters parameters, bool shallow) { - using (new SessionIdLoggingContext(SessionId)) - { - CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; - - IQueryExpressionPlan plan; - if (roleBeforeFlush == null) - { - // if it was previously unreferenced, we need to flush in order to - // get its state into the database in order to execute query - Flush(); - entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; - if (roleAfterFlush == null) - { - throw new QueryException("The collection was unreferenced"); - } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); - } - else - { - // otherwise, we only need to flush if there are in-memory changes - // to the queried tables - plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleBeforeFlush.Role, shallow, EnabledFilters); - - if (AutoFlushIfRequired(plan.QuerySpaces)) - { - // might need to run a different filter entirely after the flush - // because the collection role may have changed - entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; - if (roleBeforeFlush != roleAfterFlush) - { - if (roleAfterFlush == null) - { - throw new QueryException("The collection was dereferenced"); - } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, roleAfterFlush.Role, shallow, EnabledFilters); - } - } - } - - if (parameters != null) - { - parameters.PositionalParameterValues[0] = entry.LoadedKey; - parameters.PositionalParameterTypes[0] = entry.LoadedPersister.KeyType; - } - - return plan; - } + return GetFilterQueryPlan(collection, parameters, shallow, null, queryExpression); } private IQueryExpressionPlan GetFilterQueryPlan(object collection, string filter, QueryParameters parameters, bool shallow) + { + return GetFilterQueryPlan(collection, parameters, shallow, filter, null); + } + + private IQueryExpressionPlan GetFilterQueryPlan(object collection, QueryParameters parameters, bool shallow, + string filter, IQueryExpression queryExpression) { using (new SessionIdLoggingContext(SessionId)) { if (collection == null) - { - throw new ArgumentNullException("collection", "null collection passed to filter"); - } + throw new ArgumentNullException(nameof(collection), "null collection passed to filter"); + if (filter != null && queryExpression != null) + throw new ArgumentException($"Either {nameof(filter)} or {nameof(queryExpression)} must be specified, not both."); + if (filter == null && queryExpression == null) + throw new ArgumentException($"{nameof(filter)} and {nameof(queryExpression)} were both null."); - CollectionEntry entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleBeforeFlush = (entry == null) ? null : entry.LoadedPersister; + var entry = persistenceContext.GetCollectionEntryOrNull(collection); + var roleBeforeFlush = entry?.LoadedPersister; IQueryExpressionPlan plan; if (roleBeforeFlush == null) @@ -822,31 +764,31 @@ private IQueryExpressionPlan GetFilterQueryPlan(object collection, string filter // get its state into the database in order to execute query Flush(); entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + var roleAfterFlush = entry?.LoadedPersister; if (roleAfterFlush == null) { throw new QueryException("The collection was unreferenced"); } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(filter, roleAfterFlush.Role, shallow, EnabledFilters); + plan = GetFilterQueryPlan(roleAfterFlush.Role, shallow, filter, queryExpression); } else { // otherwise, we only need to flush if there are in-memory changes // to the queried tables - plan = Factory.QueryPlanCache.GetFilterQueryPlan(filter, roleBeforeFlush.Role, shallow, EnabledFilters); + plan = GetFilterQueryPlan(roleBeforeFlush.Role, shallow, filter, queryExpression); if (AutoFlushIfRequired(plan.QuerySpaces)) { // might need to run a different filter entirely after the flush // because the collection role may have changed entry = persistenceContext.GetCollectionEntryOrNull(collection); - ICollectionPersister roleAfterFlush = (entry == null) ? null : entry.LoadedPersister; + var roleAfterFlush = entry?.LoadedPersister; if (roleBeforeFlush != roleAfterFlush) { if (roleAfterFlush == null) { throw new QueryException("The collection was dereferenced"); } - plan = Factory.QueryPlanCache.GetFilterQueryPlan(filter, roleAfterFlush.Role, shallow, EnabledFilters); + plan = GetFilterQueryPlan(roleAfterFlush.Role, shallow, filter, queryExpression); } } } @@ -861,6 +803,13 @@ private IQueryExpressionPlan GetFilterQueryPlan(object collection, string filter } } + private IQueryExpressionPlan GetFilterQueryPlan(string role, bool shallow, string filter, IQueryExpression queryExpression) + { + return filter == null + ? Factory.QueryPlanCache.GetFilterQueryPlan(queryExpression, role, shallow, EnabledFilters) + : Factory.QueryPlanCache.GetFilterQueryPlan(filter, role, shallow, EnabledFilters); + } + public override object Instantiate(string clazz, object id) { using (new SessionIdLoggingContext(SessionId)) diff --git a/src/NHibernate/Linq/NhQueryable.cs b/src/NHibernate/Linq/NhQueryable.cs index 74667daaeee..f1ef60f137a 100644 --- a/src/NHibernate/Linq/NhQueryable.cs +++ b/src/NHibernate/Linq/NhQueryable.cs @@ -27,7 +27,7 @@ public NhQueryable(ISessionImplementor session) // This constructor is called by our users, create a new IQueryExecutor. public NhQueryable(ISessionImplementor session, string entityName) - : base(QueryProviderFactory.CreateQueryProvider(session)) + : base(QueryProviderFactory.CreateQueryProvider(session, null)) { EntityName = entityName; } @@ -47,7 +47,7 @@ public NhQueryable(IQueryProvider provider, Expression expression, string entity } public NhQueryable(ISessionImplementor session, object collection) - : base(new DefaultQueryProvider(session, collection)) + : base(QueryProviderFactory.CreateQueryProvider(session, collection)) { EntityName = typeof(T).FullName; } @@ -59,4 +59,4 @@ public override string ToString() return "NHibernate.Linq.NhQueryable`1[" + EntityName + "]"; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/QueryProviderFactory.cs b/src/NHibernate/Linq/QueryProviderFactory.cs index 52c4b8119c7..43cf502b055 100644 --- a/src/NHibernate/Linq/QueryProviderFactory.cs +++ b/src/NHibernate/Linq/QueryProviderFactory.cs @@ -9,16 +9,20 @@ static class QueryProviderFactory /// Builds a new query provider. /// /// A session. + /// If the query is to be filtered as belonging to an entity collection, the collection. /// The new query provider instance. - public static INhQueryProvider CreateQueryProvider(ISessionImplementor session) + public static INhQueryProvider CreateQueryProvider(ISessionImplementor session, object collection) { if (session.Factory.Settings.LinqQueryProviderType == null) { - return new DefaultQueryProvider(session); + return new DefaultQueryProvider(session, collection); } else { - return Activator.CreateInstance(session.Factory.Settings.LinqQueryProviderType, session) as INhQueryProvider; + // For backward compatibility, prioritize using the version without collection. + return (collection == null + ? Activator.CreateInstance(session.Factory.Settings.LinqQueryProviderType, session) + : Activator.CreateInstance(session.Factory.Settings.LinqQueryProviderType, session, collection)) as INhQueryProvider; } } } From d37b6e64fa0c97b6c0a3510db452fe7bda5c2bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 9 Sep 2017 03:18:40 +0200 Subject: [PATCH 04/10] NH-2319 - documenting the feature, to be squashed. --- doc/reference/modules/query_linq.xml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/reference/modules/query_linq.xml b/doc/reference/modules/query_linq.xml index 06f4f4cf3ec..6932b6dfcb0 100644 --- a/doc/reference/modules/query_linq.xml +++ b/doc/reference/modules/query_linq.xml @@ -42,6 +42,20 @@ using NHibernate.Linq;]]> .Where(c => c.Name == "Max") .ToList();]]> + + Starting with NHibernate 5.0, queries can also be created from an entity collection, with the standard + Linq extension AsQueryable available from System.Linq namespace. + + whiteKittens = + cat.Kittens.AsQueryable() + .Where(k => k.Color == "white") + .ToList();]]> + + This will be executed as a query on that cat's kittens without loading the + entire collection. + +   + A client timeout for the query can be defined. @@ -789,4 +803,4 @@ cfg.LinqToHqlGeneratorsRegistry(); - \ No newline at end of file + From 6751fb0be35c06ab2c41b7cc42c8878c124561b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 9 Sep 2017 08:39:48 +0200 Subject: [PATCH 05/10] NH-2319 - Test case dedicated to plan cache previous bug, to be squashed --- Tools/packages.config | 2 +- .../Async/NHSpecificTest/NH2319/Fixture.cs | 90 ++++++++++++++++--- .../NHSpecificTest/NH2319/Fixture.cs | 85 +++++++++++++++--- 3 files changed, 150 insertions(+), 27 deletions(-) diff --git a/Tools/packages.config b/Tools/packages.config index e54c238eca4..2a94e5aa85e 100644 --- a/Tools/packages.config +++ b/Tools/packages.config @@ -7,5 +7,5 @@ - + diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs index 2ec6fe4fc83..56e91181f5c 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs @@ -10,39 +10,50 @@ using System; using System.Linq; +using System.Reflection; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Collection; +using NHibernate.Engine.Query; using NHibernate.Mapping.ByCode; +using NHibernate.Util; using NUnit.Framework; using NHibernate.Linq; namespace NHibernate.Test.NHSpecificTest.NH2319 { using System.Threading.Tasks; + using System.Threading; [TestFixture] public abstract class FixtureBaseAsync : TestCaseMappingByCode { - private Guid _parentId; + private Guid _parent1Id; private Guid _child1Id; + private Guid _parent2Id; + private Guid _child3Id; [Test] - public async Task ShouldBeAbleToFindChildrenByNameAsync() + public Task ShouldBeAbleToFindChildrenByNameAsync() + { + return FindChildrenByNameAsync(_parent1Id, _child1Id); + } + + private async Task FindChildrenByNameAsync(Guid parentId, Guid childId, CancellationToken cancellationToken = default(CancellationToken)) { using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = await (session.GetAsync(_parentId)); + var parent = await (session.GetAsync(parentId, cancellationToken)); Assert.That(parent, Is.Not.Null); var filtered = await (parent.Children .AsQueryable() .Where(x => x.Name == "Jack") - .ToListAsync()); + .ToListAsync(cancellationToken)); Assert.That(filtered, Has.Count.EqualTo(1)); - Assert.That(filtered[0].Id, Is.EqualTo(_child1Id)); + Assert.That(filtered[0].Id, Is.EqualTo(childId)); } } @@ -52,7 +63,7 @@ public async Task ShouldBeAbleToPerformComplexFilteringAsync() using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = await (session.GetAsync(_parentId)); + var parent = await (session.GetAsync(_parent1Id)); Assert.NotNull(parent); @@ -67,13 +78,35 @@ public async Task ShouldBeAbleToPerformComplexFilteringAsync() } } + [Test] + public async Task ShouldBeAbleToReuseQueryPlanAsync() + { + await (ShouldBeAbleToFindChildrenByNameAsync()); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + Assert.That(ShouldBeAbleToFindChildrenByNameAsync, Throws.Nothing); + AssertFilterPlanCacheHit(spy); + } + } + + [Test] + public async Task ShouldNotMixResultsAsync() + { + await (FindChildrenByNameAsync(_parent1Id, _child1Id)); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + await (FindChildrenByNameAsync(_parent2Id, _child3Id)); + AssertFilterPlanCacheHit(spy); + } + } + [Test] public async Task ShouldNotInitializeCollectionWhenPerformingQueryAsync() { using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = await (session.GetAsync(_parentId)); + var parent = await (session.GetAsync(_parent1Id)); Assert.That(parent, Is.Not.Null); var persistentCollection = (IPersistentCollection) parent.Children; @@ -94,7 +127,7 @@ public async Task ShouldPerformSqlQueryEvenIfCollectionAlreadyInitializedAsync() using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = await (session.GetAsync(_parentId)); + var parent = await (session.GetAsync(_parent1Id)); Assert.That(parent, Is.Not.Null); var loaded = parent.Children.ToList(); @@ -120,7 +153,7 @@ public async Task TestFilterAsync() using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = await (session.GetAsync(_parentId)); + var parent = await (session.GetAsync(_parent1Id)); Assert.That(parent, Is.Not.Null); var children = await ((await (session.CreateFilterAsync(parent.Children, "where this.Name = 'Jack'"))) @@ -130,6 +163,35 @@ public async Task TestFilterAsync() } } + [Test] + public async Task TestPlanCacheMissAsync() + { + var internalPlanCache = typeof(QueryPlanCache) + .GetField("planCache", BindingFlags.NonPublic | BindingFlags.Instance) + ?.GetValue(Sfi.QueryPlanCache) as SoftLimitMRUCache; + Assert.That(internalPlanCache, Is.Not.Null, + $"Unable to find the internal query plan cache for clearing it, please adapt code to current {nameof(QueryPlanCache)} implementation."); + + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + internalPlanCache.Clear(); + await (ShouldBeAbleToFindChildrenByNameAsync()); + AssertFilterPlanCacheMiss(spy); + } + } + + private const string _filterPlanCacheMissLog = "unable to locate collection-filter query plan in cache"; + + private static void AssertFilterPlanCacheHit(LogSpy spy) => + // Each query currently ask the cache two times, so asserting reuse requires to check cache has not been missed + // rather than only asserting it has been hit. + Assert.That(spy.GetWholeLog(), + Contains.Substring("located collection-filter query plan in cache (") + .And.Not.Contains(_filterPlanCacheMissLog)); + + private static void AssertFilterPlanCacheMiss(LogSpy spy) => + Assert.That(spy.GetWholeLog(), Contains.Substring(_filterPlanCacheMissLog)); + protected override void Configure(Configuration configuration) { configuration.SetProperty("show_sql", "true"); @@ -141,11 +203,11 @@ protected override void OnSetUp() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - var parent1 = new Parent {Name = "Bob"}; - _parentId = (Guid) session.Save(parent1); + var parent1 = new Parent { Name = "Bob" }; + _parent1Id = (Guid) session.Save(parent1); - var parent2 = new Parent {Name = "Martin"}; - session.Save(parent2); + var parent2 = new Parent { Name = "Martin" }; + _parent2Id = (Guid) session.Save(parent2); var child1 = new Child { @@ -185,7 +247,7 @@ protected override void OnSetUp() Parent = parent2 }; parent2.Children.Add(child3); - session.Save(child3); + _child3Id = (Guid) session.Save(child3); session.Flush(); transaction.Commit(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs index 5c3371506b5..8b50aede207 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs @@ -1,9 +1,12 @@ using System; using System.Linq; +using System.Reflection; using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Collection; +using NHibernate.Engine.Query; using NHibernate.Mapping.ByCode; +using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH2319 @@ -11,16 +14,23 @@ namespace NHibernate.Test.NHSpecificTest.NH2319 [TestFixture] public abstract class FixtureBase : TestCaseMappingByCode { - private Guid _parentId; + private Guid _parent1Id; private Guid _child1Id; + private Guid _parent2Id; + private Guid _child3Id; [Test] public void ShouldBeAbleToFindChildrenByName() + { + FindChildrenByName(_parent1Id, _child1Id); + } + + private void FindChildrenByName(Guid parentId, Guid childId) { using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = session.Get(_parentId); + var parent = session.Get(parentId); Assert.That(parent, Is.Not.Null); @@ -30,7 +40,7 @@ public void ShouldBeAbleToFindChildrenByName() .ToList(); Assert.That(filtered, Has.Count.EqualTo(1)); - Assert.That(filtered[0].Id, Is.EqualTo(_child1Id)); + Assert.That(filtered[0].Id, Is.EqualTo(childId)); } } @@ -40,7 +50,7 @@ public void ShouldBeAbleToPerformComplexFiltering() using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = session.Get(_parentId); + var parent = session.Get(_parent1Id); Assert.NotNull(parent); @@ -55,13 +65,35 @@ public void ShouldBeAbleToPerformComplexFiltering() } } + [Test] + public void ShouldBeAbleToReuseQueryPlan() + { + ShouldBeAbleToFindChildrenByName(); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + Assert.That(ShouldBeAbleToFindChildrenByName, Throws.Nothing); + AssertFilterPlanCacheHit(spy); + } + } + + [Test] + public void ShouldNotMixResults() + { + FindChildrenByName(_parent1Id, _child1Id); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + FindChildrenByName(_parent2Id, _child3Id); + AssertFilterPlanCacheHit(spy); + } + } + [Test] public void ShouldNotInitializeCollectionWhenPerformingQuery() { using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = session.Get(_parentId); + var parent = session.Get(_parent1Id); Assert.That(parent, Is.Not.Null); var persistentCollection = (IPersistentCollection) parent.Children; @@ -82,7 +114,7 @@ public void ShouldPerformSqlQueryEvenIfCollectionAlreadyInitialized() using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = session.Get(_parentId); + var parent = session.Get(_parent1Id); Assert.That(parent, Is.Not.Null); var loaded = parent.Children.ToList(); @@ -108,7 +140,7 @@ public void TestFilter() using (var session = OpenSession()) using (session.BeginTransaction()) { - var parent = session.Get(_parentId); + var parent = session.Get(_parent1Id); Assert.That(parent, Is.Not.Null); var children = session.CreateFilter(parent.Children, "where this.Name = 'Jack'") @@ -118,6 +150,35 @@ public void TestFilter() } } + [Test] + public void TestPlanCacheMiss() + { + var internalPlanCache = typeof(QueryPlanCache) + .GetField("planCache", BindingFlags.NonPublic | BindingFlags.Instance) + ?.GetValue(Sfi.QueryPlanCache) as SoftLimitMRUCache; + Assert.That(internalPlanCache, Is.Not.Null, + $"Unable to find the internal query plan cache for clearing it, please adapt code to current {nameof(QueryPlanCache)} implementation."); + + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + internalPlanCache.Clear(); + ShouldBeAbleToFindChildrenByName(); + AssertFilterPlanCacheMiss(spy); + } + } + + private const string _filterPlanCacheMissLog = "unable to locate collection-filter query plan in cache"; + + private static void AssertFilterPlanCacheHit(LogSpy spy) => + // Each query currently ask the cache two times, so asserting reuse requires to check cache has not been missed + // rather than only asserting it has been hit. + Assert.That(spy.GetWholeLog(), + Contains.Substring("located collection-filter query plan in cache (") + .And.Not.Contains(_filterPlanCacheMissLog)); + + private static void AssertFilterPlanCacheMiss(LogSpy spy) => + Assert.That(spy.GetWholeLog(), Contains.Substring(_filterPlanCacheMissLog)); + protected override void Configure(Configuration configuration) { configuration.SetProperty("show_sql", "true"); @@ -129,11 +190,11 @@ protected override void OnSetUp() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - var parent1 = new Parent {Name = "Bob"}; - _parentId = (Guid) session.Save(parent1); + var parent1 = new Parent { Name = "Bob" }; + _parent1Id = (Guid) session.Save(parent1); - var parent2 = new Parent {Name = "Martin"}; - session.Save(parent2); + var parent2 = new Parent { Name = "Martin" }; + _parent2Id = (Guid) session.Save(parent2); var child1 = new Child { @@ -173,7 +234,7 @@ protected override void OnSetUp() Parent = parent2 }; parent2.Children.Add(child3); - session.Save(child3); + _child3Id = (Guid) session.Save(child3); session.Flush(); transaction.Commit(); From 24c8cfce38b4f672f21a72b9adf995f8a11b93fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 9 Sep 2017 11:10:16 +0200 Subject: [PATCH 06/10] Cleanup log level handling. Especially one rogue change causing all subsequent test to have NHibernate.SQL logger enabled. --- src/NHibernate.Test/Async/NHSpecificTest/Logs/LogsFixture.cs | 5 ++++- src/NHibernate.Test/NHSpecificTest/Logs/LogsFixture.cs | 5 ++++- .../SystemTransactions/ResourceManagerFixture.cs | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/Logs/LogsFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/Logs/LogsFixture.cs index aa3c2db5793..10bccd2305f 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/Logs/LogsFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/Logs/LogsFixture.cs @@ -72,6 +72,7 @@ public class TextLogSpy : IDisposable private readonly TextWriterAppender appender; private readonly Logger loggerImpl; private readonly StringBuilder stringBuilder; + private readonly Level previousLevel; public TextLogSpy(string loggerName, string pattern) { @@ -84,6 +85,7 @@ public TextLogSpy(string loggerName, string pattern) }; loggerImpl = (Logger)LogManager.GetLogger(typeof(LogsFixtureAsync).Assembly, loggerName).Logger; loggerImpl.AddAppender(appender); + previousLevel = loggerImpl.Level; loggerImpl.Level = Level.All; } @@ -98,9 +100,10 @@ public string[] Events public void Dispose() { loggerImpl.RemoveAppender(appender); + loggerImpl.Level = previousLevel; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/Logs/LogsFixture.cs b/src/NHibernate.Test/NHSpecificTest/Logs/LogsFixture.cs index 5b5f1ac4b12..2ffc692763b 100644 --- a/src/NHibernate.Test/NHSpecificTest/Logs/LogsFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/Logs/LogsFixture.cs @@ -61,6 +61,7 @@ public class TextLogSpy : IDisposable private readonly TextWriterAppender appender; private readonly Logger loggerImpl; private readonly StringBuilder stringBuilder; + private readonly Level previousLevel; public TextLogSpy(string loggerName, string pattern) { @@ -73,6 +74,7 @@ public TextLogSpy(string loggerName, string pattern) }; loggerImpl = (Logger)LogManager.GetLogger(typeof(LogsFixture).Assembly, loggerName).Logger; loggerImpl.AddAppender(appender); + previousLevel = loggerImpl.Level; loggerImpl.Level = Level.All; } @@ -87,9 +89,10 @@ public string[] Events public void Dispose() { loggerImpl.RemoveAppender(appender); + loggerImpl.Level = previousLevel; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs b/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs index 4852e0315aa..bf0f8338457 100644 --- a/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs @@ -684,7 +684,6 @@ void Clone_TransactionCompleted(object sender, TransactionEventArgs e) [OneTimeSetUp] public void TestFixtureSetUp() { - ((Logger)_log.Logger).Level = log4net.Core.Level.Info; _spy = new LogSpy(_log); _spy.Appender.Layout = new PatternLayout("%d{ABSOLUTE} [%t] - %m%n"); } From e937d0f9e8742ace336ab00d98c0f8e072575958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 9 Sep 2017 17:10:13 +0200 Subject: [PATCH 07/10] NH-2319 - Testing major mapping cases, to be squashed. --- .../Async/NHSpecificTest/NH2319/Fixture.cs | 365 ++++++++++++++++-- .../NHSpecificTest/NH2319/Fixture.cs | 365 ++++++++++++++++-- .../NHSpecificTest/NH2319/Model.cs | 4 +- 3 files changed, 679 insertions(+), 55 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs index 56e91181f5c..68dc36629f3 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs @@ -215,6 +215,7 @@ protected override void OnSetUp() Parent = parent1 }; parent1.Children.Add(child1); + child1.Parents.Add(parent1); _child1Id = (Guid) session.Save(child1); var child2 = new Child @@ -223,6 +224,7 @@ protected override void OnSetUp() Parent = parent1 }; parent1.Children.Add(child2); + child2.Parents.Add(parent1); session.Save(child2); var grandChild1 = new GrandChild @@ -231,6 +233,7 @@ protected override void OnSetUp() Child = child2 }; child2.GrandChildren.Add(grandChild1); + grandChild1.ParentChidren.Add(child2); session.Save(grandChild1); var grandChild2 = new GrandChild @@ -239,6 +242,7 @@ protected override void OnSetUp() Child = child2 }; child2.GrandChildren.Add(grandChild2); + grandChild2.ParentChidren.Add(child2); session.Save(grandChild2); var child3 = new Child @@ -247,6 +251,7 @@ protected override void OnSetUp() Parent = parent2 }; parent2.Children.Add(child3); + child3.Parents.Add(parent2); _child3Id = (Guid) session.Save(child3); session.Flush(); @@ -277,9 +282,7 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.Bag(x => x.Children, - map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), - rel => rel.OneToMany()); + rc.Bag(x => x.Children, map => map.Inverse(true), rel => rel.OneToMany()); }); mapper.Class(rc => @@ -287,7 +290,13 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); rc.ManyToOne(x => x.Parent); - rc.Bag(x => x.GrandChildren, map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), rel => rel.OneToMany()); + rc.Bag(x => x.GrandChildren, + map => + { + map.Key(k => k.Column("child_id")); + map.Inverse(true); + }, + rel => rel.OneToMany()); }); mapper.Class(rc => @@ -299,7 +308,6 @@ protected override HbmMapping GetMappings() return mapper.CompileMappingForAllExplicitlyAddedEntities(); } - } [TestFixture] @@ -312,9 +320,7 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.Set(x => x.Children, - map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), - rel => rel.OneToMany()); + rc.Set(x => x.Children, map => map.Inverse(true), rel => rel.OneToMany()); }); mapper.Class(rc => @@ -323,8 +329,12 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); rc.ManyToOne(x => x.Parent); rc.Set(x => x.GrandChildren, - map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), - rel => rel.OneToMany()); + map => + { + map.Key(k => k.Column("child_id")); + map.Inverse(true); + }, + rel => rel.OneToMany()); }); mapper.Class(rc => @@ -339,7 +349,170 @@ protected override HbmMapping GetMappings() } [TestFixture] - public class ListFixtureAsync : FixtureBaseAsync + public class ManyToManyBagFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => + { + map.Key(k => k.Column("Parent")); + map.Table("ParentChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Parents, + map => + { + map.Key(k => k.Column("Child")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ParentChild"); + }, + rel => rel.ManyToMany(map => map.Column("Parent"))); + rc.Bag(x => x.GrandChildren, + map => + { + map.Key(k => k.Column("Child")); + map.Table("ChildGrandChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("GrandChild"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.ParentChidren, + map => + { + map.Key(k => k.Column("GrandChild")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ChildGrandChild"); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class ManyToManySetFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => + { + map.Key(k => k.Column("Parent")); + map.Table("ParentChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Parents, + map => + { + map.Key(k => k.Column("Child")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ParentChild"); + }, + rel => rel.ManyToMany(map => map.Column("Parent"))); + rc.Set(x => x.GrandChildren, + map => + { + map.Key(k => k.Column("Child")); + map.Table("ChildGrandChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("GrandChild"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.ParentChidren, + map => + { + map.Key(k => k.Column("GrandChild")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ChildGrandChild"); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + // No bidi list/idbag: mapping not supported unless not marking inverse the parent side with list, which is not + // normal for a bidi. This is a general limitation, not a limitation of the feature tested here. + + [TestFixture] + public class UnidiBagFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => + { + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.GrandChildren, + map => + { + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiListFixtureAsync : FixtureBaseAsync { protected override HbmMapping GetMappings() { @@ -350,10 +523,11 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); rc.List( x => x.Children, - list => + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); - list.Index(i => i.Column("i")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Key(k => k.Column("child_id")); + map.Index(i => i.Column("i")); }, rel => rel.OneToMany()); }); @@ -362,13 +536,12 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.ManyToOne(x => x.Parent); rc.List( c => c.GrandChildren, - list => + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); - list.Index(i => i.Column("i")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Index(i => i.Column("i")); }, rel => rel.OneToMany()); }); @@ -377,7 +550,6 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.ManyToOne(x => x.Child, x => x.Column("child_id")); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); @@ -385,7 +557,75 @@ protected override HbmMapping GetMappings() } [TestFixture] - public class IdBagFixtureAsync : FixtureBaseAsync + public class UnidiSetFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManyBagFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManyIdBagFixtureAsync : FixtureBaseAsync { protected override HbmMapping GetMappings() { @@ -396,9 +636,47 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); rc.IdBag( x => x.Children, - list => + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.IdBag( + c => c.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManyListFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.List( + x => x.Children, + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Key(k => k.Column("child_id")); + map.Index(i => i.Column("i")); }, rel => rel.ManyToMany()); }); @@ -407,12 +685,12 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - //rc.ManyToOne(x => x.Parent); - rc.IdBag( + rc.List( c => c.GrandChildren, - list => + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Index(i => i.Column("i")); }, rel => rel.ManyToMany()); }); @@ -421,7 +699,40 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - //rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManySetFixtureAsync : FixtureBaseAsync + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs index 8b50aede207..0f83b28e91b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs @@ -202,6 +202,7 @@ protected override void OnSetUp() Parent = parent1 }; parent1.Children.Add(child1); + child1.Parents.Add(parent1); _child1Id = (Guid) session.Save(child1); var child2 = new Child @@ -210,6 +211,7 @@ protected override void OnSetUp() Parent = parent1 }; parent1.Children.Add(child2); + child2.Parents.Add(parent1); session.Save(child2); var grandChild1 = new GrandChild @@ -218,6 +220,7 @@ protected override void OnSetUp() Child = child2 }; child2.GrandChildren.Add(grandChild1); + grandChild1.ParentChidren.Add(child2); session.Save(grandChild1); var grandChild2 = new GrandChild @@ -226,6 +229,7 @@ protected override void OnSetUp() Child = child2 }; child2.GrandChildren.Add(grandChild2); + grandChild2.ParentChidren.Add(child2); session.Save(grandChild2); var child3 = new Child @@ -234,6 +238,7 @@ protected override void OnSetUp() Parent = parent2 }; parent2.Children.Add(child3); + child3.Parents.Add(parent2); _child3Id = (Guid) session.Save(child3); session.Flush(); @@ -264,9 +269,7 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.Bag(x => x.Children, - map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), - rel => rel.OneToMany()); + rc.Bag(x => x.Children, map => map.Inverse(true), rel => rel.OneToMany()); }); mapper.Class(rc => @@ -274,7 +277,13 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); rc.ManyToOne(x => x.Parent); - rc.Bag(x => x.GrandChildren, map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), rel => rel.OneToMany()); + rc.Bag(x => x.GrandChildren, + map => + { + map.Key(k => k.Column("child_id")); + map.Inverse(true); + }, + rel => rel.OneToMany()); }); mapper.Class(rc => @@ -286,7 +295,6 @@ protected override HbmMapping GetMappings() return mapper.CompileMappingForAllExplicitlyAddedEntities(); } - } [TestFixture] @@ -299,9 +307,7 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.Set(x => x.Children, - map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), - rel => rel.OneToMany()); + rc.Set(x => x.Children, map => map.Inverse(true), rel => rel.OneToMany()); }); mapper.Class(rc => @@ -310,8 +316,12 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); rc.ManyToOne(x => x.Parent); rc.Set(x => x.GrandChildren, - map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), - rel => rel.OneToMany()); + map => + { + map.Key(k => k.Column("child_id")); + map.Inverse(true); + }, + rel => rel.OneToMany()); }); mapper.Class(rc => @@ -326,7 +336,170 @@ protected override HbmMapping GetMappings() } [TestFixture] - public class ListFixture : FixtureBase + public class ManyToManyBagFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => + { + map.Key(k => k.Column("Parent")); + map.Table("ParentChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Parents, + map => + { + map.Key(k => k.Column("Child")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ParentChild"); + }, + rel => rel.ManyToMany(map => map.Column("Parent"))); + rc.Bag(x => x.GrandChildren, + map => + { + map.Key(k => k.Column("Child")); + map.Table("ChildGrandChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("GrandChild"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.ParentChidren, + map => + { + map.Key(k => k.Column("GrandChild")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ChildGrandChild"); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class ManyToManySetFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => + { + map.Key(k => k.Column("Parent")); + map.Table("ParentChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Parents, + map => + { + map.Key(k => k.Column("Child")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ParentChild"); + }, + rel => rel.ManyToMany(map => map.Column("Parent"))); + rc.Set(x => x.GrandChildren, + map => + { + map.Key(k => k.Column("Child")); + map.Table("ChildGrandChild"); + map.Inverse(true); + }, + rel => rel.ManyToMany(map => map.Column("GrandChild"))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.ParentChidren, + map => + { + map.Key(k => k.Column("GrandChild")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Table("ChildGrandChild"); + }, + rel => rel.ManyToMany(map => map.Column("Child"))); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + // No bidi list/idbag: mapping not supported unless not marking inverse the parent side with list, which is not + // normal for a bidi. This is a general limitation, not a limitation of the feature tested here. + + [TestFixture] + public class UnidiBagFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => + { + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.GrandChildren, + map => + { + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiListFixture : FixtureBase { protected override HbmMapping GetMappings() { @@ -337,10 +510,11 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); rc.List( x => x.Children, - list => + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); - list.Index(i => i.Column("i")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Key(k => k.Column("child_id")); + map.Index(i => i.Column("i")); }, rel => rel.OneToMany()); }); @@ -349,13 +523,12 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.ManyToOne(x => x.Parent); rc.List( c => c.GrandChildren, - list => + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); - list.Index(i => i.Column("i")); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Index(i => i.Column("i")); }, rel => rel.OneToMany()); }); @@ -364,7 +537,6 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - rc.ManyToOne(x => x.Child, x => x.Column("child_id")); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); @@ -372,7 +544,75 @@ protected override HbmMapping GetMappings() } [TestFixture] - public class IdBagFixture : FixtureBase + public class UnidiSetFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManyBagFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManyIdBagFixture : FixtureBase { protected override HbmMapping GetMappings() { @@ -383,9 +623,47 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); rc.IdBag( x => x.Children, - list => + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.IdBag( + c => c.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManyListFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.List( + x => x.Children, + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Key(k => k.Column("child_id")); + map.Index(i => i.Column("i")); }, rel => rel.ManyToMany()); }); @@ -394,12 +672,12 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - //rc.ManyToOne(x => x.Parent); - rc.IdBag( + rc.List( c => c.GrandChildren, - list => + map => { - list.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + map.Index(i => i.Column("i")); }, rel => rel.ManyToMany()); }); @@ -408,7 +686,40 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); - //rc.ManyToOne(x => x.Child, x => x.Column("child_id")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } + + [TestFixture] + public class UnidiManyToManySetFixture : FixtureBase + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.Children, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.ManyToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs index fec687c6ee3..681fa2d9ccd 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs @@ -16,11 +16,13 @@ class Child public virtual Guid Id { get; set; } public virtual string Name { get; set; } public virtual ICollection GrandChildren { get; set; } = new List(); + public virtual ICollection Parents { get; set; } = new List(); } class GrandChild { public virtual Child Child { get; set; } public virtual Guid Id { get; set; } public virtual string Name { get; set; } + public virtual ICollection ParentChidren { get; set; } = new List(); } -} \ No newline at end of file +} From ac0275b434cd05536ffe8a1b2e110ad90c7d489e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 9 Sep 2017 19:14:26 +0200 Subject: [PATCH 08/10] NH-2319 - AsQueryable Map support, to be squashed. --- doc/reference/modules/query_linq.xml | 8 + .../NativeSqlCollectionLoaderFixture.cs | 18 +- .../Async/NHSpecificTest/NH2319/MapFixture.cs | 299 ++++++++++++++++++ .../NativeSqlCollectionLoaderFixture.cs | 18 +- .../NHSpecificTest/NH2319/MapFixture.cs | 286 +++++++++++++++++ .../NHSpecificTest/NH2319/Model.cs | 1 + .../Generic/PersistentGenericMap.cs | 5 +- .../Generic/PersistentGenericMap.cs | 112 ++++++- 8 files changed, 722 insertions(+), 25 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/NH2319/MapFixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH2319/MapFixture.cs diff --git a/doc/reference/modules/query_linq.xml b/doc/reference/modules/query_linq.xml index 6932b6dfcb0..3e994729866 100644 --- a/doc/reference/modules/query_linq.xml +++ b/doc/reference/modules/query_linq.xml @@ -54,6 +54,14 @@ using NHibernate.Linq;]]> This will be executed as a query on that cat's kittens without loading the entire collection. + + If the collection is a map, call AsQueryable on its Values + property. + + whiteKittens = + cat.Kittens.Values.AsQueryable() + .Where(k => k.Color == "white") + .ToList();]]>   diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs index 03286bacbc9..39732200737 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs @@ -51,8 +51,8 @@ public async Task LoadCompositeElementsWithWithSimpleHbmAliasInjectionAsync() Country country = await (LoadCountryWithNativeSQLAsync(CreateCountry(stats), "LoadAreaStatisticsWithSimpleHbmAliasInjection")); Assert.That(country, Is.Not.Null); - Assert.That((ICollection) country.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "Keys"); - Assert.That((ICollection) country.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "Elements"); + Assert.That(country.Statistics.Keys, Is.EquivalentTo(stats.Keys), "Keys"); + Assert.That(country.Statistics.Values, Is.EquivalentTo(stats.Values), "Elements"); await (CleanupWithPersonsAsync()); } @@ -63,8 +63,8 @@ public async Task LoadCompositeElementsWithWithComplexHbmAliasInjectionAsync() Country country = await (LoadCountryWithNativeSQLAsync(CreateCountry(stats), "LoadAreaStatisticsWithComplexHbmAliasInjection")); Assert.That(country, Is.Not.Null); - Assert.That((ICollection) country.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "Keys"); - Assert.That((ICollection) country.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "Elements"); + Assert.That(country.Statistics.Keys, Is.EquivalentTo(stats.Keys), "Keys"); + Assert.That(country.Statistics.Values, Is.EquivalentTo(stats.Values), "Elements"); await (CleanupWithPersonsAsync()); } @@ -75,8 +75,8 @@ public async Task LoadCompositeElementsWithWithCustomAliasesAsync() Country country = await (LoadCountryWithNativeSQLAsync(CreateCountry(stats), "LoadAreaStatisticsWithCustomAliases")); Assert.That(country, Is.Not.Null); - Assert.That((ICollection) country.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "Keys"); - Assert.That((ICollection) country.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "Elements"); + Assert.That(country.Statistics.Keys, Is.EquivalentTo(stats.Keys), "Keys"); + Assert.That(country.Statistics.Values, Is.EquivalentTo(stats.Values), "Elements"); await (CleanupWithPersonsAsync()); } @@ -201,8 +201,8 @@ public async Task LoadCompositeElementCollectionWithCustomLoaderAsync() { var a = await (session.GetAsync(country.Code)); Assert.That(a, Is.Not.Null, "area"); - Assert.That((ICollection) a.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "area.Keys"); - Assert.That((ICollection) a.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "area.Elements"); + Assert.That(a.Statistics.Keys, Is.EquivalentTo(stats.Keys), "area.Keys"); + Assert.That(a.Statistics.Values, Is.EquivalentTo(stats.Values), "area.Elements"); } await (CleanupWithPersonsAsync()); } @@ -428,4 +428,4 @@ private static IDictionary CreateStatistics() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2319/MapFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/MapFixture.cs new file mode 100644 index 00000000000..e93c7d652f1 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2319/MapFixture.cs @@ -0,0 +1,299 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Collection; +using NHibernate.Engine.Query; +using NHibernate.Mapping.ByCode; +using NHibernate.Util; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.NH2319 +{ + using System.Threading.Tasks; + using System.Threading; + [TestFixture] + public class MapFixtureAsync : TestCaseMappingByCode + { + private Guid _parent1Id; + private Guid _child1Id; + private Guid _parent2Id; + private Guid _child3Id; + + [Test] + public Task ShouldBeAbleToFindChildrenByNameAsync() + { + return FindChildrenByNameAsync(_parent1Id, _child1Id); + } + + private async Task FindChildrenByNameAsync(Guid parentId, Guid childId, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(parentId, cancellationToken)); + + Assert.That(parent, Is.Not.Null); + + var filtered = await (parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToListAsync(cancellationToken)); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(filtered[0].Id, Is.EqualTo(childId)); + } + } + + [Test] + public async Task ShouldBeAbleToPerformComplexFilteringAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parent1Id)); + + Assert.NotNull(parent); + + var filtered = await (parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Piter") + .SelectMany(x => x.GrandChildren) + .Select(x => x.Id) + .CountAsync()); + + Assert.That(filtered, Is.EqualTo(2)); + } + } + + [Test] + public async Task ShouldBeAbleToReuseQueryPlanAsync() + { + await (ShouldBeAbleToFindChildrenByNameAsync()); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + Assert.That(ShouldBeAbleToFindChildrenByNameAsync, Throws.Nothing); + AssertFilterPlanCacheHit(spy); + } + } + + [Test] + public async Task ShouldNotMixResultsAsync() + { + await (FindChildrenByNameAsync(_parent1Id, _child1Id)); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + await (FindChildrenByNameAsync(_parent2Id, _child3Id)); + AssertFilterPlanCacheHit(spy); + } + } + + [Test] + public async Task ShouldNotInitializeCollectionWhenPerformingQueryAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parent1Id)); + Assert.That(parent, Is.Not.Null); + + var persistentCollection = (IPersistentCollection) parent.ChildrenMap; + + var filtered = await (parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToListAsync()); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(persistentCollection.WasInitialized, Is.False); + } + } + + [Test] + public async Task ShouldPerformSqlQueryEvenIfCollectionAlreadyInitializedAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parent1Id)); + Assert.That(parent, Is.Not.Null); + + var loaded = parent.ChildrenMap.ToList(); + Assert.That(loaded, Has.Count.EqualTo(2)); + + var countBeforeFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + var filtered = await (parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToListAsync()); + + var countAfterFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(countAfterFiltering, Is.EqualTo(countBeforeFiltering + 1)); + } + } + + [Test] + public async Task TestFilterAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = await (session.GetAsync(_parent1Id)); + Assert.That(parent, Is.Not.Null); + + var children = await ((await (session.CreateFilterAsync(parent.ChildrenMap, "where this.Name = 'Jack'"))) + .ListAsync()); + + Assert.That(children, Has.Count.EqualTo(1)); + } + } + + [Test] + public async Task TestPlanCacheMissAsync() + { + var internalPlanCache = typeof(QueryPlanCache) + .GetField("planCache", BindingFlags.NonPublic | BindingFlags.Instance) + ?.GetValue(Sfi.QueryPlanCache) as SoftLimitMRUCache; + Assert.That(internalPlanCache, Is.Not.Null, + $"Unable to find the internal query plan cache for clearing it, please adapt code to current {nameof(QueryPlanCache)} implementation."); + + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + internalPlanCache.Clear(); + await (ShouldBeAbleToFindChildrenByNameAsync()); + AssertFilterPlanCacheMiss(spy); + } + } + + private const string _filterPlanCacheMissLog = "unable to locate collection-filter query plan in cache"; + + private static void AssertFilterPlanCacheHit(LogSpy spy) => + // Each query currently ask the cache two times, so asserting reuse requires to check cache has not been missed + // rather than only asserting it has been hit. + Assert.That(spy.GetWholeLog(), + Contains.Substring("located collection-filter query plan in cache (") + .And.Not.Contains(_filterPlanCacheMissLog)); + + private static void AssertFilterPlanCacheMiss(LogSpy spy) => + Assert.That(spy.GetWholeLog(), Contains.Substring(_filterPlanCacheMissLog)); + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("show_sql", "true"); + configuration.SetProperty("generate_statistics", "true"); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var parent1 = new Parent { Name = "Bob" }; + _parent1Id = (Guid) session.Save(parent1); + + var parent2 = new Parent { Name = "Martin" }; + _parent2Id = (Guid) session.Save(parent2); + + var child1 = new Child + { + Name = "Jack", + Parent = parent1 + }; + _child1Id = (Guid) session.Save(child1); + parent1.ChildrenMap.Add(child1.Id, child1); + + var child2 = new Child + { + Name = "Piter", + Parent = parent1 + }; + session.Save(child2); + parent1.ChildrenMap.Add(child2.Id, child2); + + var grandChild1 = new GrandChild + { + Name = "Kate", + Child = child2 + }; + session.Save(grandChild1); + child2.GrandChildren.Add(grandChild1); + + var grandChild2 = new GrandChild + { + Name = "Mary", + Child = child2 + }; + session.Save(grandChild2); + child2.GrandChildren.Add(grandChild2); + + var child3 = new Child + { + Name = "Jack", + Parent = parent2 + }; + _child3Id = (Guid) session.Save(child3); + parent2.ChildrenMap.Add(child1.Id, child3); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Map(x => x.ChildrenMap, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs index b5c6910a8ce..7c4c618284a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1612/NativeSqlCollectionLoaderFixture.cs @@ -39,8 +39,8 @@ public void LoadCompositeElementsWithWithSimpleHbmAliasInjection() Country country = LoadCountryWithNativeSQL(CreateCountry(stats), "LoadAreaStatisticsWithSimpleHbmAliasInjection"); Assert.That(country, Is.Not.Null); - Assert.That((ICollection) country.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "Keys"); - Assert.That((ICollection) country.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "Elements"); + Assert.That(country.Statistics.Keys, Is.EquivalentTo(stats.Keys), "Keys"); + Assert.That(country.Statistics.Values, Is.EquivalentTo(stats.Values), "Elements"); CleanupWithPersons(); } @@ -51,8 +51,8 @@ public void LoadCompositeElementsWithWithComplexHbmAliasInjection() Country country = LoadCountryWithNativeSQL(CreateCountry(stats), "LoadAreaStatisticsWithComplexHbmAliasInjection"); Assert.That(country, Is.Not.Null); - Assert.That((ICollection) country.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "Keys"); - Assert.That((ICollection) country.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "Elements"); + Assert.That(country.Statistics.Keys, Is.EquivalentTo(stats.Keys), "Keys"); + Assert.That(country.Statistics.Values, Is.EquivalentTo(stats.Values), "Elements"); CleanupWithPersons(); } @@ -63,8 +63,8 @@ public void LoadCompositeElementsWithWithCustomAliases() Country country = LoadCountryWithNativeSQL(CreateCountry(stats), "LoadAreaStatisticsWithCustomAliases"); Assert.That(country, Is.Not.Null); - Assert.That((ICollection) country.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "Keys"); - Assert.That((ICollection) country.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "Elements"); + Assert.That(country.Statistics.Keys, Is.EquivalentTo(stats.Keys), "Keys"); + Assert.That(country.Statistics.Values, Is.EquivalentTo(stats.Values), "Elements"); CleanupWithPersons(); } @@ -189,8 +189,8 @@ public void LoadCompositeElementCollectionWithCustomLoader() { var a = session.Get(country.Code); Assert.That(a, Is.Not.Null, "area"); - Assert.That((ICollection) a.Statistics.Keys, Is.EquivalentTo((ICollection) stats.Keys), "area.Keys"); - Assert.That((ICollection) a.Statistics.Values, Is.EquivalentTo((ICollection) stats.Values), "area.Elements"); + Assert.That(a.Statistics.Keys, Is.EquivalentTo(stats.Keys), "area.Keys"); + Assert.That(a.Statistics.Values, Is.EquivalentTo(stats.Values), "area.Elements"); } CleanupWithPersons(); } @@ -416,4 +416,4 @@ private static IDictionary CreateStatistics() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2319/MapFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2319/MapFixture.cs new file mode 100644 index 00000000000..6aa9b1c8e86 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2319/MapFixture.cs @@ -0,0 +1,286 @@ +using System; +using System.Linq; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Collection; +using NHibernate.Engine.Query; +using NHibernate.Mapping.ByCode; +using NHibernate.Util; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH2319 +{ + [TestFixture] + public class MapFixture : TestCaseMappingByCode + { + private Guid _parent1Id; + private Guid _child1Id; + private Guid _parent2Id; + private Guid _child3Id; + + [Test] + public void ShouldBeAbleToFindChildrenByName() + { + FindChildrenByName(_parent1Id, _child1Id); + } + + private void FindChildrenByName(Guid parentId, Guid childId) + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(parentId); + + Assert.That(parent, Is.Not.Null); + + var filtered = parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToList(); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(filtered[0].Id, Is.EqualTo(childId)); + } + } + + [Test] + public void ShouldBeAbleToPerformComplexFiltering() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parent1Id); + + Assert.NotNull(parent); + + var filtered = parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Piter") + .SelectMany(x => x.GrandChildren) + .Select(x => x.Id) + .Count(); + + Assert.That(filtered, Is.EqualTo(2)); + } + } + + [Test] + public void ShouldBeAbleToReuseQueryPlan() + { + ShouldBeAbleToFindChildrenByName(); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + Assert.That(ShouldBeAbleToFindChildrenByName, Throws.Nothing); + AssertFilterPlanCacheHit(spy); + } + } + + [Test] + public void ShouldNotMixResults() + { + FindChildrenByName(_parent1Id, _child1Id); + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + FindChildrenByName(_parent2Id, _child3Id); + AssertFilterPlanCacheHit(spy); + } + } + + [Test] + public void ShouldNotInitializeCollectionWhenPerformingQuery() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parent1Id); + Assert.That(parent, Is.Not.Null); + + var persistentCollection = (IPersistentCollection) parent.ChildrenMap; + + var filtered = parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToList(); + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(persistentCollection.WasInitialized, Is.False); + } + } + + [Test] + public void ShouldPerformSqlQueryEvenIfCollectionAlreadyInitialized() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parent1Id); + Assert.That(parent, Is.Not.Null); + + var loaded = parent.ChildrenMap.ToList(); + Assert.That(loaded, Has.Count.EqualTo(2)); + + var countBeforeFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + var filtered = parent.ChildrenMap.Values + .AsQueryable() + .Where(x => x.Name == "Jack") + .ToList(); + + var countAfterFiltering = session.SessionFactory.Statistics.QueryExecutionCount; + + Assert.That(filtered, Has.Count.EqualTo(1)); + Assert.That(countAfterFiltering, Is.EqualTo(countBeforeFiltering + 1)); + } + } + + [Test] + public void TestFilter() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var parent = session.Get(_parent1Id); + Assert.That(parent, Is.Not.Null); + + var children = session.CreateFilter(parent.ChildrenMap, "where this.Name = 'Jack'") + .List(); + + Assert.That(children, Has.Count.EqualTo(1)); + } + } + + [Test] + public void TestPlanCacheMiss() + { + var internalPlanCache = typeof(QueryPlanCache) + .GetField("planCache", BindingFlags.NonPublic | BindingFlags.Instance) + ?.GetValue(Sfi.QueryPlanCache) as SoftLimitMRUCache; + Assert.That(internalPlanCache, Is.Not.Null, + $"Unable to find the internal query plan cache for clearing it, please adapt code to current {nameof(QueryPlanCache)} implementation."); + + using (var spy = new LogSpy(typeof(QueryPlanCache))) + { + internalPlanCache.Clear(); + ShouldBeAbleToFindChildrenByName(); + AssertFilterPlanCacheMiss(spy); + } + } + + private const string _filterPlanCacheMissLog = "unable to locate collection-filter query plan in cache"; + + private static void AssertFilterPlanCacheHit(LogSpy spy) => + // Each query currently ask the cache two times, so asserting reuse requires to check cache has not been missed + // rather than only asserting it has been hit. + Assert.That(spy.GetWholeLog(), + Contains.Substring("located collection-filter query plan in cache (") + .And.Not.Contains(_filterPlanCacheMissLog)); + + private static void AssertFilterPlanCacheMiss(LogSpy spy) => + Assert.That(spy.GetWholeLog(), Contains.Substring(_filterPlanCacheMissLog)); + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("show_sql", "true"); + configuration.SetProperty("generate_statistics", "true"); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var parent1 = new Parent { Name = "Bob" }; + _parent1Id = (Guid) session.Save(parent1); + + var parent2 = new Parent { Name = "Martin" }; + _parent2Id = (Guid) session.Save(parent2); + + var child1 = new Child + { + Name = "Jack", + Parent = parent1 + }; + _child1Id = (Guid) session.Save(child1); + parent1.ChildrenMap.Add(child1.Id, child1); + + var child2 = new Child + { + Name = "Piter", + Parent = parent1 + }; + session.Save(child2); + parent1.ChildrenMap.Add(child2.Id, child2); + + var grandChild1 = new GrandChild + { + Name = "Kate", + Child = child2 + }; + session.Save(grandChild1); + child2.GrandChildren.Add(grandChild1); + + var grandChild2 = new GrandChild + { + Name = "Mary", + Child = child2 + }; + session.Save(grandChild2); + child2.GrandChildren.Add(grandChild2); + + var child3 = new Child + { + Name = "Jack", + Parent = parent2 + }; + _child3Id = (Guid) session.Save(child3); + parent2.ChildrenMap.Add(child1.Id, child3); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Map(x => x.ChildrenMap, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Set(x => x.GrandChildren, + map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), + rel => rel.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs index 681fa2d9ccd..ba5ef9b1368 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2319/Model.cs @@ -8,6 +8,7 @@ class Parent public virtual Guid Id { get; set; } public virtual string Name { get; set; } public virtual ICollection Children { get; set; } = new List(); + public virtual IDictionary ChildrenMap { get; set; } = new Dictionary(); } class Child diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs index bda6cc9bcfc..ad77b5d50dc 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs @@ -13,8 +13,11 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -154,4 +157,4 @@ public override async Task NeedsUpdatingAsync(object entry, int i, IType e || (!isNew && ((e.Value == null) != (snValue == null))); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs index 903f9260f17..ecadd44aa95 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; using NHibernate.DebugHelpers; using NHibernate.Engine; +using NHibernate.Linq; using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -22,15 +25,22 @@ namespace NHibernate.Collection.Generic [DebuggerTypeProxy(typeof(DictionaryProxy<,>))] public partial class PersistentGenericMap : AbstractPersistentCollection, IDictionary, ICollection { - protected IDictionary WrappedMap; + protected IDictionary WrappedMap { get; private set; } + private readonly ICollection _wrappedValues; - public PersistentGenericMap() { } + public PersistentGenericMap() + { + _wrappedValues = new ValuesWrapper(this); + } /// /// Construct an uninitialized PersistentGenericMap. /// /// The ISession the PersistentGenericMap should be a part of. - public PersistentGenericMap(ISessionImplementor session) : base(session) { } + public PersistentGenericMap(ISessionImplementor session) : base(session) + { + _wrappedValues = new ValuesWrapper(this); + } /// /// Construct an initialized PersistentGenericMap based off the values from the existing IDictionary. @@ -41,6 +51,7 @@ public PersistentGenericMap(ISessionImplementor session, IDictionary Values { get { - Read(); - return WrappedMap.Values; + return _wrappedValues; } } @@ -568,5 +578,95 @@ public void Operate() } #endregion + + [Serializable] + private class ValuesWrapper : ICollection, IQueryable + { + private readonly PersistentGenericMap _map; + + public ValuesWrapper(PersistentGenericMap map) + { + _map = map; + } + + #region IQueryable Members + + [NonSerialized] + private IQueryable _queryable; + + Expression IQueryable.Expression => InnerQueryable.Expression; + + System.Type IQueryable.ElementType => InnerQueryable.ElementType; + + IQueryProvider IQueryable.Provider => InnerQueryable.Provider; + + private IQueryable InnerQueryable => _queryable ?? (_queryable = new NhQueryable(_map.Session, _map)); + + #endregion + + #region ICollection Members + + public IEnumerator GetEnumerator() + { + _map.Read(); + return _map.WrappedMap.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + _map.Read(); + return GetEnumerator(); + } + + public void Add(TValue item) + { + _map.Read(); + _map.WrappedMap.Values.Add(item); + } + + public void Clear() + { + _map.Read(); + _map.WrappedMap.Values.Clear(); + } + + public bool Contains(TValue item) + { + _map.Read(); + return _map.WrappedMap.Values.Contains(item); + } + + public void CopyTo(TValue[] array, int arrayIndex) + { + _map.Read(); + _map.WrappedMap.Values.CopyTo(array, arrayIndex); + } + + public bool Remove(TValue item) + { + _map.Read(); + return _map.WrappedMap.Values.Remove(item); + } + + public int Count + { + get + { + _map.Read(); + return _map.WrappedMap.Values.Count; + } + } + + public bool IsReadOnly + { + get + { + _map.Read(); + return _map.WrappedMap.Values.IsReadOnly; + } + } + + #endregion + } } -} \ No newline at end of file +} From 6cdf008107af6436377f820969e75b65c69320b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Tue, 12 Sep 2017 13:58:09 +0200 Subject: [PATCH 09/10] NH-2319 - AsQueryable Map support adjustments, to be squashed. --- .../NH1136/PersistentMilestoneCollection.cs | 4 +- .../Generic/PersistentGenericMap.cs | 12 +-- .../Generic/PersistentGenericMap.cs | 100 ++++++++---------- 3 files changed, 53 insertions(+), 63 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/NH1136/PersistentMilestoneCollection.cs b/src/NHibernate.Test/NHSpecificTest/NH1136/PersistentMilestoneCollection.cs index d92cb3c9ef7..b9b5cca8398 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1136/PersistentMilestoneCollection.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1136/PersistentMilestoneCollection.cs @@ -20,9 +20,9 @@ public PersistentMilestoneCollection(ISessionImplementor session) : base(session public TValue FindValueFor(TKey key) { Read(); - return ((IMilestoneCollection) WrappedMap).FindValueFor(key); + return ((IMilestoneCollection) Entries(null)).FindValueFor(key); } #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs index ad77b5d50dc..b2e2313ca98 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs @@ -42,7 +42,7 @@ public override Task GetOrphansAsync(object snapshot, string entity try { var sn = (IDictionary) snapshot; - return GetOrphansAsync((ICollection)sn.Values, (ICollection)WrappedMap.Values, entityName, Session, cancellationToken); + return GetOrphansAsync((ICollection)sn.Values, (ICollection)_wrappedMap.Values, entityName, Session, cancellationToken); } catch (Exception ex) { @@ -55,11 +55,11 @@ public override async Task EqualsSnapshotAsync(ICollectionPersister persis cancellationToken.ThrowIfCancellationRequested(); IType elementType = persister.ElementType; var xmap = (IDictionary)GetSnapshot(); - if (xmap.Count != WrappedMap.Count) + if (xmap.Count != _wrappedMap.Count) { return false; } - foreach (KeyValuePair entry in WrappedMap) + foreach (KeyValuePair entry in _wrappedMap) { // This method is not currently called if a key has been removed/added, but better be on the safe side. if (!xmap.TryGetValue(entry.Key, out var value) || @@ -96,7 +96,7 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist BeforeInitialize(persister, size); for (int i = 0; i < size; i += 2) { - WrappedMap[(TKey)await (persister.IndexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = + _wrappedMap[(TKey)await (persister.IndexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = (TValue)await (persister.ElementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false); } } @@ -104,9 +104,9 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - object[] result = new object[WrappedMap.Count * 2]; + object[] result = new object[_wrappedMap.Count * 2]; int i = 0; - foreach (KeyValuePair e in WrappedMap) + foreach (KeyValuePair e in _wrappedMap) { result[i++] = await (persister.IndexType.DisassembleAsync(e.Key, Session, null, cancellationToken)).ConfigureAwait(false); result[i++] = await (persister.ElementType.DisassembleAsync(e.Value, Session, null, cancellationToken)).ConfigureAwait(false); diff --git a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs index ecadd44aa95..1bfec996bba 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs @@ -25,7 +25,7 @@ namespace NHibernate.Collection.Generic [DebuggerTypeProxy(typeof(DictionaryProxy<,>))] public partial class PersistentGenericMap : AbstractPersistentCollection, IDictionary, ICollection { - protected IDictionary WrappedMap { get; private set; } + private IDictionary _wrappedMap; private readonly ICollection _wrappedValues; public PersistentGenericMap() @@ -50,7 +50,7 @@ public PersistentGenericMap(ISessionImplementor session) : base(session) public PersistentGenericMap(ISessionImplementor session, IDictionary map) : base(session) { - WrappedMap = map; + _wrappedMap = map; _wrappedValues = new ValuesWrapper(this); SetInitialized(); IsDirectlyAccessible = true; @@ -58,8 +58,8 @@ public PersistentGenericMap(ISessionImplementor session, IDictionary clonedMap = new Dictionary(WrappedMap.Count); - foreach (KeyValuePair e in WrappedMap) + Dictionary clonedMap = new Dictionary(_wrappedMap.Count); + foreach (KeyValuePair e in _wrappedMap) { object copy = persister.ElementType.DeepCopy(e.Value, persister.Factory); clonedMap[e.Key] = (TValue)copy; @@ -70,18 +70,18 @@ public override object GetSnapshot(ICollectionPersister persister) public override ICollection GetOrphans(object snapshot, string entityName) { var sn = (IDictionary) snapshot; - return GetOrphans((ICollection)sn.Values, (ICollection)WrappedMap.Values, entityName, Session); + return GetOrphans((ICollection)sn.Values, (ICollection)_wrappedMap.Values, entityName, Session); } public override bool EqualsSnapshot(ICollectionPersister persister) { IType elementType = persister.ElementType; var xmap = (IDictionary)GetSnapshot(); - if (xmap.Count != WrappedMap.Count) + if (xmap.Count != _wrappedMap.Count) { return false; } - foreach (KeyValuePair entry in WrappedMap) + foreach (KeyValuePair entry in _wrappedMap) { // This method is not currently called if a key has been removed/added, but better be on the safe side. if (!xmap.TryGetValue(entry.Key, out var value) || @@ -100,23 +100,23 @@ public override bool IsSnapshotEmpty(object snapshot) public override bool IsWrapper(object collection) { - return WrappedMap == collection; + return _wrappedMap == collection; } public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize) { - WrappedMap = (IDictionary)persister.CollectionType.Instantiate(anticipatedSize); + _wrappedMap = (IDictionary)persister.CollectionType.Instantiate(anticipatedSize); } public override bool Empty { - get { return (WrappedMap.Count == 0); } + get { return (_wrappedMap.Count == 0); } } public override string ToString() { Read(); - return StringHelper.CollectionToString(WrappedMap); + return StringHelper.CollectionToString(_wrappedMap); } public override object ReadFrom(DbDataReader rs, ICollectionPersister role, ICollectionAliases descriptor, object owner) @@ -130,12 +130,12 @@ public override object ReadFrom(DbDataReader rs, ICollectionPersister role, ICol protected virtual void AddDuringInitialize(object index, object element) { - WrappedMap[(TKey)index] = (TValue)element; + _wrappedMap[(TKey)index] = (TValue)element; } public override IEnumerable Entries(ICollectionPersister persister) { - return WrappedMap; + return _wrappedMap; } /// @@ -151,16 +151,16 @@ public override void InitializeFromCache(ICollectionPersister persister, object BeforeInitialize(persister, size); for (int i = 0; i < size; i += 2) { - WrappedMap[(TKey)persister.IndexType.Assemble(array[i], Session, owner)] = + _wrappedMap[(TKey)persister.IndexType.Assemble(array[i], Session, owner)] = (TValue)persister.ElementType.Assemble(array[i + 1], Session, owner); } } public override object Disassemble(ICollectionPersister persister) { - object[] result = new object[WrappedMap.Count * 2]; + object[] result = new object[_wrappedMap.Count * 2]; int i = 0; - foreach (KeyValuePair e in WrappedMap) + foreach (KeyValuePair e in _wrappedMap) { result[i++] = persister.IndexType.Disassemble(e.Key, Session, null); result[i++] = persister.ElementType.Disassemble(e.Value, Session, null); @@ -174,7 +174,7 @@ public override IEnumerable GetDeletes(ICollectionPersister persister, bool inde var sn = (IDictionary)GetSnapshot(); foreach (var e in sn) { - if (!WrappedMap.ContainsKey(e.Key)) + if (!_wrappedMap.ContainsKey(e.Key)) { object key = e.Key; deletes.Add(indexIsFormula ? e.Value : key); @@ -224,18 +224,18 @@ public override bool Equals(object other) return false; } Read(); - return CollectionHelper.DictionaryEquals(WrappedMap, that); + return CollectionHelper.DictionaryEquals(_wrappedMap, that); } public override int GetHashCode() { Read(); - return WrappedMap.GetHashCode(); + return _wrappedMap.GetHashCode(); } public override bool EntryExists(object entry, int i) { - return WrappedMap.ContainsKey(((KeyValuePair)entry).Key); + return _wrappedMap.ContainsKey(((KeyValuePair)entry).Key); } @@ -244,7 +244,7 @@ public override bool EntryExists(object entry, int i) public bool ContainsKey(TKey key) { bool? exists = ReadIndexExistence(key); - return !exists.HasValue ? WrappedMap.ContainsKey(key) : exists.Value; + return !exists.HasValue ? _wrappedMap.ContainsKey(key) : exists.Value; } public void Add(TKey key, TValue value) @@ -263,7 +263,7 @@ public void Add(TKey key, TValue value) } } Initialize(true); - WrappedMap.Add(key, value); + _wrappedMap.Add(key, value); Dirty(); } @@ -273,7 +273,7 @@ public bool Remove(TKey key) if (old == Unknown) // queue is not enabled for 'puts', or element not found { Initialize(true); - bool contained = WrappedMap.Remove(key); + bool contained = _wrappedMap.Remove(key); if (contained) { Dirty(); @@ -291,7 +291,7 @@ public bool TryGetValue(TKey key, out TValue value) object result = ReadElementByIndex(key); if (result == Unknown) { - return WrappedMap.TryGetValue(key, out value); + return _wrappedMap.TryGetValue(key, out value); } if(result == NotFound) { @@ -309,7 +309,7 @@ public TValue this[TKey key] object result = ReadElementByIndex(key); if (result == Unknown) { - return WrappedMap[key]; + return _wrappedMap[key]; } if (result == NotFound) { @@ -331,8 +331,8 @@ public TValue this[TKey key] } Initialize(true); TValue tempObject; - WrappedMap.TryGetValue(key, out tempObject); - WrappedMap[key] = value; + _wrappedMap.TryGetValue(key, out tempObject); + _wrappedMap[key] = value; TValue old2 = tempObject; // would be better to use the element-type to determine // whether the old and the new are equal here; the problem being @@ -350,7 +350,7 @@ public ICollection Keys get { Read(); - return WrappedMap.Keys; + return _wrappedMap.Keys; } } @@ -380,10 +380,10 @@ public void Clear() else { Initialize(true); - if (WrappedMap.Count != 0) + if (_wrappedMap.Count != 0) { Dirty(); - WrappedMap.Clear(); + _wrappedMap.Clear(); } } } @@ -393,7 +393,7 @@ public bool Contains(KeyValuePair item) bool? exists = ReadIndexExistence(item.Key); if (!exists.HasValue) { - return WrappedMap.Contains(item); + return _wrappedMap.Contains(item); } if (exists.Value) @@ -441,7 +441,7 @@ public bool Remove(KeyValuePair item) public int Count { - get { return ReadSize() ? CachedSize : WrappedMap.Count; } + get { return ReadSize() ? CachedSize : _wrappedMap.Count; } } public bool IsReadOnly @@ -475,7 +475,7 @@ public bool IsSynchronized IEnumerator> IEnumerable>.GetEnumerator() { Read(); - return WrappedMap.GetEnumerator(); + return _wrappedMap.GetEnumerator(); } #endregion @@ -485,7 +485,7 @@ IEnumerator> IEnumerable>. IEnumerator IEnumerable.GetEnumerator() { Read(); - return WrappedMap.GetEnumerator(); + return _wrappedMap.GetEnumerator(); } #endregion @@ -513,7 +513,7 @@ public object Orphan public void Operate() { - _enclosingInstance.WrappedMap.Clear(); + _enclosingInstance._wrappedMap.Clear(); } } @@ -544,7 +544,7 @@ public object Orphan public void Operate() { - _enclosingInstance.WrappedMap[_index] = _value; + _enclosingInstance._wrappedMap[_index] = _value; } } @@ -573,7 +573,7 @@ public object Orphan public void Operate() { - _enclosingInstance.WrappedMap.Remove(_index); + _enclosingInstance._wrappedMap.Remove(_index); } } @@ -609,7 +609,7 @@ public ValuesWrapper(PersistentGenericMap map) public IEnumerator GetEnumerator() { _map.Read(); - return _map.WrappedMap.Values.GetEnumerator(); + return _map._wrappedMap.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -620,32 +620,29 @@ IEnumerator IEnumerable.GetEnumerator() public void Add(TValue item) { - _map.Read(); - _map.WrappedMap.Values.Add(item); + throw new NotSupportedException("Values collection is readonly"); } public void Clear() { - _map.Read(); - _map.WrappedMap.Values.Clear(); + throw new NotSupportedException("Values collection is readonly"); } public bool Contains(TValue item) { _map.Read(); - return _map.WrappedMap.Values.Contains(item); + return _map._wrappedMap.Values.Contains(item); } public void CopyTo(TValue[] array, int arrayIndex) { _map.Read(); - _map.WrappedMap.Values.CopyTo(array, arrayIndex); + _map._wrappedMap.Values.CopyTo(array, arrayIndex); } public bool Remove(TValue item) { - _map.Read(); - return _map.WrappedMap.Values.Remove(item); + throw new NotSupportedException("Values collection is readonly"); } public int Count @@ -653,18 +650,11 @@ public int Count get { _map.Read(); - return _map.WrappedMap.Values.Count; + return _map._wrappedMap.Values.Count; } } - public bool IsReadOnly - { - get - { - _map.Read(); - return _map.WrappedMap.Values.IsReadOnly; - } - } + public bool IsReadOnly => true; #endregion } From fd7fb0cdfabd8b2673c7c531f3dc89f8bbc49a7e Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Wed, 13 Sep 2017 10:49:04 +1200 Subject: [PATCH 10/10] NH-2319 - AsQueryable Map support adjustments, to be squashed. --- .../Generic/PersistentGenericMap.cs | 12 +-- .../Generic/PersistentGenericMap.cs | 82 +++++++++---------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs index b2e2313ca98..ad77b5d50dc 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs @@ -42,7 +42,7 @@ public override Task GetOrphansAsync(object snapshot, string entity try { var sn = (IDictionary) snapshot; - return GetOrphansAsync((ICollection)sn.Values, (ICollection)_wrappedMap.Values, entityName, Session, cancellationToken); + return GetOrphansAsync((ICollection)sn.Values, (ICollection)WrappedMap.Values, entityName, Session, cancellationToken); } catch (Exception ex) { @@ -55,11 +55,11 @@ public override async Task EqualsSnapshotAsync(ICollectionPersister persis cancellationToken.ThrowIfCancellationRequested(); IType elementType = persister.ElementType; var xmap = (IDictionary)GetSnapshot(); - if (xmap.Count != _wrappedMap.Count) + if (xmap.Count != WrappedMap.Count) { return false; } - foreach (KeyValuePair entry in _wrappedMap) + foreach (KeyValuePair entry in WrappedMap) { // This method is not currently called if a key has been removed/added, but better be on the safe side. if (!xmap.TryGetValue(entry.Key, out var value) || @@ -96,7 +96,7 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist BeforeInitialize(persister, size); for (int i = 0; i < size; i += 2) { - _wrappedMap[(TKey)await (persister.IndexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = + WrappedMap[(TKey)await (persister.IndexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = (TValue)await (persister.ElementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false); } } @@ -104,9 +104,9 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - object[] result = new object[_wrappedMap.Count * 2]; + object[] result = new object[WrappedMap.Count * 2]; int i = 0; - foreach (KeyValuePair e in _wrappedMap) + foreach (KeyValuePair e in WrappedMap) { result[i++] = await (persister.IndexType.DisassembleAsync(e.Key, Session, null, cancellationToken)).ConfigureAwait(false); result[i++] = await (persister.ElementType.DisassembleAsync(e.Value, Session, null, cancellationToken)).ConfigureAwait(false); diff --git a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs index 1bfec996bba..1f34cd7f06a 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs @@ -25,7 +25,7 @@ namespace NHibernate.Collection.Generic [DebuggerTypeProxy(typeof(DictionaryProxy<,>))] public partial class PersistentGenericMap : AbstractPersistentCollection, IDictionary, ICollection { - private IDictionary _wrappedMap; + protected IDictionary WrappedMap; private readonly ICollection _wrappedValues; public PersistentGenericMap() @@ -50,7 +50,7 @@ public PersistentGenericMap(ISessionImplementor session) : base(session) public PersistentGenericMap(ISessionImplementor session, IDictionary map) : base(session) { - _wrappedMap = map; + WrappedMap = map; _wrappedValues = new ValuesWrapper(this); SetInitialized(); IsDirectlyAccessible = true; @@ -58,8 +58,8 @@ public PersistentGenericMap(ISessionImplementor session, IDictionary clonedMap = new Dictionary(_wrappedMap.Count); - foreach (KeyValuePair e in _wrappedMap) + Dictionary clonedMap = new Dictionary(WrappedMap.Count); + foreach (KeyValuePair e in WrappedMap) { object copy = persister.ElementType.DeepCopy(e.Value, persister.Factory); clonedMap[e.Key] = (TValue)copy; @@ -70,18 +70,18 @@ public override object GetSnapshot(ICollectionPersister persister) public override ICollection GetOrphans(object snapshot, string entityName) { var sn = (IDictionary) snapshot; - return GetOrphans((ICollection)sn.Values, (ICollection)_wrappedMap.Values, entityName, Session); + return GetOrphans((ICollection)sn.Values, (ICollection)WrappedMap.Values, entityName, Session); } public override bool EqualsSnapshot(ICollectionPersister persister) { IType elementType = persister.ElementType; var xmap = (IDictionary)GetSnapshot(); - if (xmap.Count != _wrappedMap.Count) + if (xmap.Count != WrappedMap.Count) { return false; } - foreach (KeyValuePair entry in _wrappedMap) + foreach (KeyValuePair entry in WrappedMap) { // This method is not currently called if a key has been removed/added, but better be on the safe side. if (!xmap.TryGetValue(entry.Key, out var value) || @@ -100,23 +100,23 @@ public override bool IsSnapshotEmpty(object snapshot) public override bool IsWrapper(object collection) { - return _wrappedMap == collection; + return WrappedMap == collection; } public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize) { - _wrappedMap = (IDictionary)persister.CollectionType.Instantiate(anticipatedSize); + WrappedMap = (IDictionary)persister.CollectionType.Instantiate(anticipatedSize); } public override bool Empty { - get { return (_wrappedMap.Count == 0); } + get { return (WrappedMap.Count == 0); } } public override string ToString() { Read(); - return StringHelper.CollectionToString(_wrappedMap); + return StringHelper.CollectionToString(WrappedMap); } public override object ReadFrom(DbDataReader rs, ICollectionPersister role, ICollectionAliases descriptor, object owner) @@ -130,12 +130,12 @@ public override object ReadFrom(DbDataReader rs, ICollectionPersister role, ICol protected virtual void AddDuringInitialize(object index, object element) { - _wrappedMap[(TKey)index] = (TValue)element; + WrappedMap[(TKey)index] = (TValue)element; } public override IEnumerable Entries(ICollectionPersister persister) { - return _wrappedMap; + return WrappedMap; } /// @@ -151,16 +151,16 @@ public override void InitializeFromCache(ICollectionPersister persister, object BeforeInitialize(persister, size); for (int i = 0; i < size; i += 2) { - _wrappedMap[(TKey)persister.IndexType.Assemble(array[i], Session, owner)] = + WrappedMap[(TKey)persister.IndexType.Assemble(array[i], Session, owner)] = (TValue)persister.ElementType.Assemble(array[i + 1], Session, owner); } } public override object Disassemble(ICollectionPersister persister) { - object[] result = new object[_wrappedMap.Count * 2]; + object[] result = new object[WrappedMap.Count * 2]; int i = 0; - foreach (KeyValuePair e in _wrappedMap) + foreach (KeyValuePair e in WrappedMap) { result[i++] = persister.IndexType.Disassemble(e.Key, Session, null); result[i++] = persister.ElementType.Disassemble(e.Value, Session, null); @@ -174,7 +174,7 @@ public override IEnumerable GetDeletes(ICollectionPersister persister, bool inde var sn = (IDictionary)GetSnapshot(); foreach (var e in sn) { - if (!_wrappedMap.ContainsKey(e.Key)) + if (!WrappedMap.ContainsKey(e.Key)) { object key = e.Key; deletes.Add(indexIsFormula ? e.Value : key); @@ -224,18 +224,18 @@ public override bool Equals(object other) return false; } Read(); - return CollectionHelper.DictionaryEquals(_wrappedMap, that); + return CollectionHelper.DictionaryEquals(WrappedMap, that); } public override int GetHashCode() { Read(); - return _wrappedMap.GetHashCode(); + return WrappedMap.GetHashCode(); } public override bool EntryExists(object entry, int i) { - return _wrappedMap.ContainsKey(((KeyValuePair)entry).Key); + return WrappedMap.ContainsKey(((KeyValuePair)entry).Key); } @@ -244,7 +244,7 @@ public override bool EntryExists(object entry, int i) public bool ContainsKey(TKey key) { bool? exists = ReadIndexExistence(key); - return !exists.HasValue ? _wrappedMap.ContainsKey(key) : exists.Value; + return !exists.HasValue ? WrappedMap.ContainsKey(key) : exists.Value; } public void Add(TKey key, TValue value) @@ -263,7 +263,7 @@ public void Add(TKey key, TValue value) } } Initialize(true); - _wrappedMap.Add(key, value); + WrappedMap.Add(key, value); Dirty(); } @@ -273,7 +273,7 @@ public bool Remove(TKey key) if (old == Unknown) // queue is not enabled for 'puts', or element not found { Initialize(true); - bool contained = _wrappedMap.Remove(key); + bool contained = WrappedMap.Remove(key); if (contained) { Dirty(); @@ -291,7 +291,7 @@ public bool TryGetValue(TKey key, out TValue value) object result = ReadElementByIndex(key); if (result == Unknown) { - return _wrappedMap.TryGetValue(key, out value); + return WrappedMap.TryGetValue(key, out value); } if(result == NotFound) { @@ -309,7 +309,7 @@ public TValue this[TKey key] object result = ReadElementByIndex(key); if (result == Unknown) { - return _wrappedMap[key]; + return WrappedMap[key]; } if (result == NotFound) { @@ -331,8 +331,8 @@ public TValue this[TKey key] } Initialize(true); TValue tempObject; - _wrappedMap.TryGetValue(key, out tempObject); - _wrappedMap[key] = value; + WrappedMap.TryGetValue(key, out tempObject); + WrappedMap[key] = value; TValue old2 = tempObject; // would be better to use the element-type to determine // whether the old and the new are equal here; the problem being @@ -350,7 +350,7 @@ public ICollection Keys get { Read(); - return _wrappedMap.Keys; + return WrappedMap.Keys; } } @@ -380,10 +380,10 @@ public void Clear() else { Initialize(true); - if (_wrappedMap.Count != 0) + if (WrappedMap.Count != 0) { Dirty(); - _wrappedMap.Clear(); + WrappedMap.Clear(); } } } @@ -393,7 +393,7 @@ public bool Contains(KeyValuePair item) bool? exists = ReadIndexExistence(item.Key); if (!exists.HasValue) { - return _wrappedMap.Contains(item); + return WrappedMap.Contains(item); } if (exists.Value) @@ -441,7 +441,7 @@ public bool Remove(KeyValuePair item) public int Count { - get { return ReadSize() ? CachedSize : _wrappedMap.Count; } + get { return ReadSize() ? CachedSize : WrappedMap.Count; } } public bool IsReadOnly @@ -475,7 +475,7 @@ public bool IsSynchronized IEnumerator> IEnumerable>.GetEnumerator() { Read(); - return _wrappedMap.GetEnumerator(); + return WrappedMap.GetEnumerator(); } #endregion @@ -485,7 +485,7 @@ IEnumerator> IEnumerable>. IEnumerator IEnumerable.GetEnumerator() { Read(); - return _wrappedMap.GetEnumerator(); + return WrappedMap.GetEnumerator(); } #endregion @@ -513,7 +513,7 @@ public object Orphan public void Operate() { - _enclosingInstance._wrappedMap.Clear(); + _enclosingInstance.WrappedMap.Clear(); } } @@ -544,7 +544,7 @@ public object Orphan public void Operate() { - _enclosingInstance._wrappedMap[_index] = _value; + _enclosingInstance.WrappedMap[_index] = _value; } } @@ -573,7 +573,7 @@ public object Orphan public void Operate() { - _enclosingInstance._wrappedMap.Remove(_index); + _enclosingInstance.WrappedMap.Remove(_index); } } @@ -609,7 +609,7 @@ public ValuesWrapper(PersistentGenericMap map) public IEnumerator GetEnumerator() { _map.Read(); - return _map._wrappedMap.Values.GetEnumerator(); + return _map.WrappedMap.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -631,13 +631,13 @@ public void Clear() public bool Contains(TValue item) { _map.Read(); - return _map._wrappedMap.Values.Contains(item); + return _map.WrappedMap.Values.Contains(item); } public void CopyTo(TValue[] array, int arrayIndex) { _map.Read(); - _map._wrappedMap.Values.CopyTo(array, arrayIndex); + _map.WrappedMap.Values.CopyTo(array, arrayIndex); } public bool Remove(TValue item) @@ -650,7 +650,7 @@ public int Count get { _map.Read(); - return _map._wrappedMap.Values.Count; + return _map.WrappedMap.Values.Count; } }