From dc5c64d873ff1dad54fd58b1b0b6935943a86080 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 3 Dec 2017 21:17:21 +0530 Subject: [PATCH 01/11] Ability to select entities in Criteria API projections --- .../NHSpecificTest/GH948/FixtureByCode.cs | 398 ++++++++++++++++++ .../NHSpecificTest/GH948/Entities.cs | 56 +++ .../NHSpecificTest/GH948/FixtureByCode.cs | 387 +++++++++++++++++ .../Async/Criterion/EntityProjectionType.cs | 106 +++++ src/NHibernate/Criterion/EntityProjection.cs | 175 ++++++++ .../Criterion/EntityProjectionType.cs | 98 +++++ src/NHibernate/Criterion/Projections.cs | 42 ++ 7 files changed, 1262 insertions(+) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH948/Entities.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs create mode 100644 src/NHibernate/Async/Criterion/EntityProjectionType.cs create mode 100644 src/NHibernate/Criterion/EntityProjection.cs create mode 100644 src/NHibernate/Criterion/EntityProjectionType.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs new file mode 100644 index 00000000000..0863e05dd03 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs @@ -0,0 +1,398 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH948 +{ + using System.Threading.Tasks; + [TestFixture] + public class ByCodeFixtureAsync : TestCaseMappingByCode + { + private EntityWithCompositeId _entityWithCompositeId; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + + rc.Version(ep => ep.Version, vm => { }); + + rc.Property(x => x.Name); + + rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + + rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); + rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); + rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); + + rc.Bag(ep => ep.ChildrenList, m => + { + m.Cascade(Mapping.ByCode.Cascade.All); + m.Inverse(true); + }, a => a.OneToMany()); + + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.ComponentAsId( + e => e.Key, + ekm => + { + ekm.Property(ek => ek.Id1); + ekm.Property(ek => ek.Id2); + }); + + rc.Property(e => e.Name); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var child1 = new EntitySimpleChild + { + Name = "Child1" + }; + var child2 = new EntitySimpleChild + { + Name = "Child1" + }; + + + var parent = new EntityComplex + { + Name = "ComplexEnityParent", + Child1 = child1, + Child2 = child2, + LazyProp = "SomeBigValue", + SameTypeChild = new EntityComplex() { Name = "ComplexEntityChild" } + }; + + _entityWithCompositeId = new EntityWithCompositeId + { + Key = new CompositeKey + { + Id1 = 1, + Id2 = 2 + }, + Name = "Composite" + }; + + session.Save(child1); + session.Save(child2); + session.Save(parent.SameTypeChild); + session.Save(parent); + session.Save(_entityWithCompositeId); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public async Task RootEntityProjectionFullyInitializedAndWithUnfetchedLazyPropertiesByDefaultAsync() + { + using(var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + Sfi.Statistics.Clear(); + EntityComplex entitypRoot = await (session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity()) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.True, "Object must be initialized by default"); + Assert.That(session.IsReadOnly(entitypRoot),Is.False, "Object must not be readonly by default"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.False, "Lazy properties should not be initialized by default."); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task RootEntityProjectionLazyAsync() + { + using (var session = OpenSession()) + { + EntityComplex entitypRoot = await (session + .QueryOver() + .Select(Projections.RootEntity().SetLazy(true)) + .Take(1).SingleOrDefaultAsync()); + + + Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.False, "Object must be lazy loaded."); + } + } + + [Test] + public async Task AliasedEntityProjectionAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = await (session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .Select(Projections.Entity(() => child1)) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task MultipleLazyEntityProjectionsAsync() + { + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + EntityComplex root = null; + EntityComplex sameTypeChild = null; + EntitySimpleChild child2 = null; + + var result = await (session + .QueryOver(() => root) + .JoinAlias(ep => ep.SameTypeChild, () => sameTypeChild) + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .Select( + Projections.RootEntity().SetLazy(true), + Projections.Entity(() => child1).SetLazy(true), + Projections.Entity(() => sameTypeChild).SetLazy(true), + Projections.Entity(() => child2).SetLazy(true) + ) + .Take(1).SingleOrDefaultAsync()); + + root = (EntityComplex) result[0]; + child1 = (EntitySimpleChild) result[1]; + sameTypeChild = (EntityComplex) result[2]; + child2 = (EntitySimpleChild) result[3]; + + Assert.That(NHibernateUtil.IsInitialized(root), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "Object must be lazy loaded."); + + //make sure objects are populated from different aliases for the same types + Assert.That(root.Id , Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); + Assert.That(child1.Id , Is.Not.EqualTo(child2.Id), "Different objects are expected."); + + } + } + + [Test] + public async Task EntityProjectionWithLazyPropertiesFetchedAsync() + { + using (var session = OpenSession()) + { + EntityComplex entitypRoot = await (session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity().SetAllPropertyFetch(true)) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(entitypRoot, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entitypRoot), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + } + } + + [Test] + public async Task NullEntityProjectionAsync() + { + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = await (session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1, JoinType.LeftOuterJoin) + .Where(() => child1.Id == null) + .Select(Projections.Entity(() => child1)) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(child1, Is.Null); + } + } + + [Test] + public async Task MultipleEntitiesProjectionsAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex root = null; + EntitySimpleChild child1 = null; + EntitySimpleChild child2 = null; + EntityComplex sameAsRootChild = null; + EntitySimpleChild nullListElem = null; + var objects = await (session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) + .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) + .Select( + Projections.RootEntity(), + Projections.Entity(() => child1), + Projections.Entity(() => child2), + Projections.Entity(() => sameAsRootChild), + Projections.Entity(() => nullListElem) + ) + .Take(1).SingleOrDefaultAsync()); + + root = (EntityComplex) objects[0]; + child1 = (EntitySimpleChild) objects[1]; + child2 = (EntitySimpleChild) objects[2]; + sameAsRootChild = (EntityComplex) objects[3]; + nullListElem = (EntitySimpleChild) objects[4]; + + Assert.That(NHibernateUtil.IsInitialized(root), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(sameAsRootChild), Is.True, "Object must be initialized"); + Assert.That(nullListElem, Is.Null, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + class MultipleEntitiesResult + { + public EntityComplex Root { get; set; } + public EntitySimpleChild Child1 { get; set; } + public EntitySimpleChild Child2 { get; set; } + public EntityComplex SameAsRootChild { get; set; } + public EntitySimpleChild NullListElem { get; set; } + } + + [Test] + public async Task MultipleEntitiesProjectionsToResultTransformerAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + MultipleEntitiesResult result = new MultipleEntitiesResult(); + + EntityComplex root = null; + EntitySimpleChild child1 = null; + EntitySimpleChild child2 = null; + EntityComplex sameAsRootChild = null; + EntitySimpleChild nullListElem = null; + var r = await (session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) + .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) + .Select( + Projections.Alias(Projections.RootEntity(), nameof(result.Root)), + Projections.Entity(() => child1), + Projections.Entity(() => child2), + Projections.Entity(() => sameAsRootChild), + Projections.Entity(() => nullListElem) + ) + + .TransformUsing(Transformers.AliasToBean()) + .Take(1) + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(r.Root), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child1), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child2), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.SameAsRootChild), Is.True, "Object must be initialized"); + Assert.That(r.NullListElem, Is.Null, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + + [Test] + public async Task ReadOnlyProjectionAsync() + { + using (var session = OpenSession()) + { + EntityComplex entitypRoot = await (session + .QueryOver() + .Select(Projections.RootEntity().SetReadonly(true)) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(session.IsReadOnly(entitypRoot), Is.True, "Object must be loaded readonly."); + } + } + + [Test] + public async Task EntityProjectionForCompositeKeyInitializedAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var composite = await (session + .QueryOver() + .Select(Projections.RootEntity()) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(composite, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(composite), Is.True, "Object must be initialized"); + Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key) && Equals(x.Name, y.Name)) ? 0 : 1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + + } + + [Test] + public async Task EntityProjectionForCompositeKeyLazyAsync() + { + using (var session = OpenSession()) + { + var composite = await (session + .QueryOver() + .Select(Projections.RootEntity().SetLazy(true)) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(composite, Is.Not.Null); + Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key)) ? 0 : 1)); + Assert.That(NHibernateUtil.IsInitialized(composite), Is.False, "Object must be lazy loaded."); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH948/Entities.cs b/src/NHibernate.Test/NHSpecificTest/GH948/Entities.cs new file mode 100644 index 00000000000..20505f6e55d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH948/Entities.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH948 +{ + public class EntitySimpleChild + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + public class EntityComplex + { + public virtual Guid Id { get; set; } + + public virtual int Version { get; set; } + + public virtual string Name { get; set; } + + public virtual string LazyProp { get; set; } + + public virtual EntitySimpleChild Child1 { get; set; } + public virtual EntitySimpleChild Child2 { get; set; } + public virtual EntityComplex SameTypeChild { get; set; } + + public virtual IList ChildrenList { get; set; } + } + + public class CompositeKey + { + public int Id1 { get; set; } + public int Id2 { get; set; } + + public override bool Equals(object obj) + { + var key = obj as CompositeKey; + return key != null + && Id1 == key.Id1 + && Id2 == key.Id2; + } + + public override int GetHashCode() + { + var hashCode = -1596524975; + hashCode = hashCode * -1521134295 + Id1.GetHashCode(); + hashCode = hashCode * -1521134295 + Id2.GetHashCode(); + return hashCode; + } + } + + public class EntityWithCompositeId + { + public virtual CompositeKey Key { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs new file mode 100644 index 00000000000..4697332373c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs @@ -0,0 +1,387 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH948 +{ + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + private EntityWithCompositeId _entityWithCompositeId; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + + rc.Version(ep => ep.Version, vm => { }); + + rc.Property(x => x.Name); + + rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + + rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); + rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); + rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); + + rc.Bag(ep => ep.ChildrenList, m => + { + m.Cascade(Mapping.ByCode.Cascade.All); + m.Inverse(true); + }, a => a.OneToMany()); + + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.ComponentAsId( + e => e.Key, + ekm => + { + ekm.Property(ek => ek.Id1); + ekm.Property(ek => ek.Id2); + }); + + rc.Property(e => e.Name); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var child1 = new EntitySimpleChild + { + Name = "Child1" + }; + var child2 = new EntitySimpleChild + { + Name = "Child1" + }; + + + var parent = new EntityComplex + { + Name = "ComplexEnityParent", + Child1 = child1, + Child2 = child2, + LazyProp = "SomeBigValue", + SameTypeChild = new EntityComplex() { Name = "ComplexEntityChild" } + }; + + _entityWithCompositeId = new EntityWithCompositeId + { + Key = new CompositeKey + { + Id1 = 1, + Id2 = 2 + }, + Name = "Composite" + }; + + session.Save(child1); + session.Save(child2); + session.Save(parent.SameTypeChild); + session.Save(parent); + session.Save(_entityWithCompositeId); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public void RootEntityProjectionFullyInitializedAndWithUnfetchedLazyPropertiesByDefault() + { + using(var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + Sfi.Statistics.Clear(); + EntityComplex entitypRoot = session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity()) + .Take(1).SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.True, "Object must be initialized by default"); + Assert.That(session.IsReadOnly(entitypRoot),Is.False, "Object must not be readonly by default"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.False, "Lazy properties should not be initialized by default."); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void RootEntityProjectionLazy() + { + using (var session = OpenSession()) + { + EntityComplex entitypRoot = session + .QueryOver() + .Select(Projections.RootEntity().SetLazy(true)) + .Take(1).SingleOrDefault(); + + + Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.False, "Object must be lazy loaded."); + } + } + + [Test] + public void AliasedEntityProjection() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .Select(Projections.Entity(() => child1)) + .Take(1).SingleOrDefault(); + + Assert.That(child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void MultipleLazyEntityProjections() + { + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + EntityComplex root = null; + EntityComplex sameTypeChild = null; + EntitySimpleChild child2 = null; + + var result = session + .QueryOver(() => root) + .JoinAlias(ep => ep.SameTypeChild, () => sameTypeChild) + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .Select( + Projections.RootEntity().SetLazy(true), + Projections.Entity(() => child1).SetLazy(true), + Projections.Entity(() => sameTypeChild).SetLazy(true), + Projections.Entity(() => child2).SetLazy(true) + ) + .Take(1).SingleOrDefault(); + + root = (EntityComplex) result[0]; + child1 = (EntitySimpleChild) result[1]; + sameTypeChild = (EntityComplex) result[2]; + child2 = (EntitySimpleChild) result[3]; + + Assert.That(NHibernateUtil.IsInitialized(root), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "Object must be lazy loaded."); + + //make sure objects are populated from different aliases for the same types + Assert.That(root.Id , Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); + Assert.That(child1.Id , Is.Not.EqualTo(child2.Id), "Different objects are expected."); + + } + } + + [Test] + public void EntityProjectionWithLazyPropertiesFetched() + { + using (var session = OpenSession()) + { + EntityComplex entitypRoot = session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity().SetAllPropertyFetch(true)) + .Take(1).SingleOrDefault(); + + Assert.That(entitypRoot, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entitypRoot), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + } + } + + [Test] + public void NullEntityProjection() + { + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1, JoinType.LeftOuterJoin) + .Where(() => child1.Id == null) + .Select(Projections.Entity(() => child1)) + .Take(1).SingleOrDefault(); + + Assert.That(child1, Is.Null); + } + } + + [Test] + public void MultipleEntitiesProjections() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex root = null; + EntitySimpleChild child1 = null; + EntitySimpleChild child2 = null; + EntityComplex sameAsRootChild = null; + EntitySimpleChild nullListElem = null; + var objects = session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) + .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) + .Select( + Projections.RootEntity(), + Projections.Entity(() => child1), + Projections.Entity(() => child2), + Projections.Entity(() => sameAsRootChild), + Projections.Entity(() => nullListElem) + ) + .Take(1).SingleOrDefault(); + + root = (EntityComplex) objects[0]; + child1 = (EntitySimpleChild) objects[1]; + child2 = (EntitySimpleChild) objects[2]; + sameAsRootChild = (EntityComplex) objects[3]; + nullListElem = (EntitySimpleChild) objects[4]; + + Assert.That(NHibernateUtil.IsInitialized(root), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(sameAsRootChild), Is.True, "Object must be initialized"); + Assert.That(nullListElem, Is.Null, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + class MultipleEntitiesResult + { + public EntityComplex Root { get; set; } + public EntitySimpleChild Child1 { get; set; } + public EntitySimpleChild Child2 { get; set; } + public EntityComplex SameAsRootChild { get; set; } + public EntitySimpleChild NullListElem { get; set; } + } + + [Test] + public void MultipleEntitiesProjectionsToResultTransformer() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + MultipleEntitiesResult result = new MultipleEntitiesResult(); + + EntityComplex root = null; + EntitySimpleChild child1 = null; + EntitySimpleChild child2 = null; + EntityComplex sameAsRootChild = null; + EntitySimpleChild nullListElem = null; + var r = session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) + .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) + .Select( + Projections.Alias(Projections.RootEntity(), nameof(result.Root)), + Projections.Entity(() => child1), + Projections.Entity(() => child2), + Projections.Entity(() => sameAsRootChild), + Projections.Entity(() => nullListElem) + ) + + .TransformUsing(Transformers.AliasToBean()) + .Take(1) + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(r.Root), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child1), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child2), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.SameAsRootChild), Is.True, "Object must be initialized"); + Assert.That(r.NullListElem, Is.Null, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + + [Test] + public void ReadOnlyProjection() + { + using (var session = OpenSession()) + { + EntityComplex entitypRoot = session + .QueryOver() + .Select(Projections.RootEntity().SetReadonly(true)) + .Take(1).SingleOrDefault(); + + Assert.That(session.IsReadOnly(entitypRoot), Is.True, "Object must be loaded readonly."); + } + } + + [Test] + public void EntityProjectionForCompositeKeyInitialized() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var composite = session + .QueryOver() + .Select(Projections.RootEntity()) + .Take(1).SingleOrDefault(); + + Assert.That(composite, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(composite), Is.True, "Object must be initialized"); + Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key) && Equals(x.Name, y.Name)) ? 0 : 1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + + } + + [Test] + public void EntityProjectionForCompositeKeyLazy() + { + using (var session = OpenSession()) + { + var composite = session + .QueryOver() + .Select(Projections.RootEntity().SetLazy(true)) + .Take(1).SingleOrDefault(); + + Assert.That(composite, Is.Not.Null); + Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key)) ? 0 : 1)); + Assert.That(NHibernateUtil.IsInitialized(composite), Is.False, "Object must be lazy loaded."); + } + } + } +} diff --git a/src/NHibernate/Async/Criterion/EntityProjectionType.cs b/src/NHibernate/Async/Criterion/EntityProjectionType.cs new file mode 100644 index 00000000000..e2f97aa270c --- /dev/null +++ b/src/NHibernate/Async/Criterion/EntityProjectionType.cs @@ -0,0 +1,106 @@ +//------------------------------------------------------------------------------ +// +// 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.Data.Common; +using NHibernate.Engine; +using NHibernate.Event; +using NHibernate.Persister.Entity; +using NHibernate.Type; + +namespace NHibernate.Criterion +{ + using System.Threading.Tasks; + using System.Threading; + internal partial class EntityProjectionType : ManyToOneType, IType + { + + Task IType.NullSafeGetAsync(DbDataReader rs, string[] names, ISessionImplementor session, object owner, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + //names parameter is ignored (taken from projection) + return NullSafeGetAsync(rs, string.Empty, session, owner, cancellationToken); + } + + public override async Task NullSafeGetAsync(DbDataReader rs, string name, ISessionImplementor session, object owner, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var identifier = await (_projection.Persister.IdentifierType.NullSafeGetAsync(rs, _projection.IdentifierColumnAliases, session, null, cancellationToken)).ConfigureAwait(false); + + if (identifier == null) + { + return null; + } + + return _projection.Lazy + ? await (ResolveIdentifierAsync(identifier, session, cancellationToken)).ConfigureAwait(false) + : await (GetInitializedEntityFromProjectionAsync(rs, session, identifier, cancellationToken)).ConfigureAwait(false); + } + + private async Task GetInitializedEntityFromProjectionAsync(DbDataReader rs, ISessionImplementor session, object identifier, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var entity = await (CreateInitializedEntityAsync( + rs, + session, + _projection.Persister, + identifier, + _projection.PropertyColumnAliases, + LockMode.None, + _projection.FetchLazyProperties, + _projection.IsReadOnly, cancellationToken)).ConfigureAwait(false); + + return entity; + } + + private static async Task CreateInitializedEntityAsync(DbDataReader rs, ISessionImplementor session, IQueryable persister, object identifier, string[][] propertyAliases, LockMode lockMode, bool fetchLazyProperties, bool readOnly, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var eventSource = session as IEventSource; + PostLoadEvent postLoadEvent = null; + PreLoadEvent preLoadEvent = null; + object entity; + if (eventSource != null) + { + preLoadEvent = new PreLoadEvent(eventSource); + postLoadEvent = new PostLoadEvent(eventSource); + entity = eventSource.Instantiate(persister, identifier); + } + else + { + entity = session.Instantiate(persister.EntityName, identifier); + } + + TwoPhaseLoad.AddUninitializedEntity( + session.GenerateEntityKey(identifier, persister), + entity, + persister, + lockMode, + !fetchLazyProperties, + session); + + var hydrated = await (persister.HydrateAsync( + rs, + identifier, + entity, + persister, + propertyAliases, + fetchLazyProperties, + session, cancellationToken)).ConfigureAwait(false); + + TwoPhaseLoad.PostHydrate(persister, identifier, hydrated, null, entity, lockMode, !fetchLazyProperties, session); + await (TwoPhaseLoad.InitializeEntityAsync(entity, readOnly, session, preLoadEvent, postLoadEvent, cancellationToken)).ConfigureAwait(false); + return entity; + } + } +} diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs new file mode 100644 index 00000000000..9a566d7a6fa --- /dev/null +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -0,0 +1,175 @@ +using System; +using System.Linq; +using NHibernate.Engine; +using NHibernate.SqlCommand; +using NHibernate.Type; +using IQueryable = NHibernate.Persister.Entity.IQueryable; + +namespace NHibernate.Criterion +{ + using TThis = EntityProjection; + + [Serializable] + public class EntityProjection : IProjection + { + private string _entityAlias; + private string _columnAliasSuffix; + private string _tableAlias; + private IType[] _types; + private string[] _columnAliases; + + /// + /// Root entity projection + /// + public EntityProjection() : this(null, null) + { + } + + public EntityProjection(System.Type rootEntity, string entityAlias) + { + RootEntity = rootEntity; + _entityAlias = entityAlias; + } + + public bool FetchLazyProperties { get; set; } + public bool IsReadOnly { get; set; } + public bool Lazy { get; set; } + + internal string[] IdentifierColumnAliases { get; private set; } + internal string[][] PropertyColumnAliases { get; private set; } + internal IQueryable Persister { get; private set; } + internal System.Type RootEntity { get; private set; } + + #region Configuration methods + + public TThis SetLazy(bool lazy = true) + { + Lazy = lazy; + return this; + } + + public TThis SetAllPropertyFetch(bool fetchLazyProperties = true) + { + FetchLazyProperties = fetchLazyProperties; + return this; + } + + public TThis SetReadonly(bool isReadOnly = true) + { + IsReadOnly = isReadOnly; + return this; + } + + #endregion Configuration methods + + #region IProjection implementation + + /// + /// Entity alias + /// + public string[] Aliases + { + get { return new[] {_entityAlias}; } + } + + bool IProjection.IsAggregate + { + get { return false; } + } + + bool IProjection.IsGrouped + { + get { return false; } + } + + IType[] IProjection.GetTypes(string alias, ICriteria criteria, ICriteriaQuery criteriaQuery) + { + return new[] {criteriaQuery.GetType(criteria, alias)}; + } + + IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + SetFields(criteria, criteriaQuery); + + return _types; + } + + public string[] GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + { + SetFields(criteria, criteriaQuery); + + return _columnAliases; + } + + public string[] GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + { + SetFields(criteria, criteriaQuery); + + return _columnAliases; + } + + SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) + { + SetFields(criteria, criteriaQuery); + + string identifierSelectFragment = Persister.IdentifierSelectFragment(_tableAlias, _columnAliasSuffix); + return new SqlString( + Lazy + ? identifierSelectFragment + : string.Concat( + identifierSelectFragment, + Persister.PropertySelectFragment(_tableAlias, _columnAliasSuffix, FetchLazyProperties))); + } + + SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + throw new NotImplementedException(); + } + + TypedValue[] IProjection.GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + throw new NotImplementedException(); + } + + #endregion IProjection implementation + + private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + //Persister is required, so let's use it as "initialized marker" + if (Persister != null) + return; + + if (RootEntity == null) + { + RootEntity = criteria.GetRootEntityTypeIfAvailable(); + } + + if (_entityAlias == null) + { + _entityAlias = criteria.Alias; + } + + Persister = (IQueryable) criteriaQuery.Factory.GetEntityPersister(RootEntity.FullName); + + ICriteria subcriteria = criteria.GetCriteriaByAlias(_entityAlias); + if (subcriteria == null) + throw new HibernateException($"Criteria\\QueryOver alias '{_entityAlias}' for entity projection is not found."); + + _tableAlias = criteriaQuery.GetSQLAlias( + subcriteria, + Persister.IdentifierPropertyName ?? string.Empty); + + _columnAliasSuffix = criteriaQuery.GetIndexForAlias().ToString(); + + IdentifierColumnAliases = Persister.GetIdentifierAliases(_columnAliasSuffix); + + PropertyColumnAliases = Lazy + ? new string[][] { } + : Enumerable.Range(0, Persister.PropertyNames.Length).Select(i => Persister.GetPropertyAliases(_columnAliasSuffix, i)).ToArray(); + + _columnAliases = IdentifierColumnAliases.Concat(PropertyColumnAliases.SelectMany(ca => ca)).ToArray(); + + _types = new IType[] {new EntityProjectionType(this)}; + } + } +} diff --git a/src/NHibernate/Criterion/EntityProjectionType.cs b/src/NHibernate/Criterion/EntityProjectionType.cs new file mode 100644 index 00000000000..43c43ca4b39 --- /dev/null +++ b/src/NHibernate/Criterion/EntityProjectionType.cs @@ -0,0 +1,98 @@ +using System; +using System.Data.Common; +using NHibernate.Engine; +using NHibernate.Event; +using NHibernate.Persister.Entity; +using NHibernate.Type; + +namespace NHibernate.Criterion +{ + /// + /// Specialized type for retrieving entity from Criteria/QueryOver API projection. + /// Intended to be used only inside + /// + [Serializable] + internal partial class EntityProjectionType : ManyToOneType, IType + { + private readonly EntityProjection _projection; + + public EntityProjectionType(EntityProjection projection) : base(projection.RootEntity.FullName, projection.Lazy) + { + _projection = projection; + } + + object IType.NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner) + { + //names parameter is ignored (taken from projection) + return NullSafeGet(rs, string.Empty, session, owner); + } + + public override object NullSafeGet(DbDataReader rs, string name, ISessionImplementor session, object owner) + { + var identifier = _projection.Persister.IdentifierType.NullSafeGet(rs, _projection.IdentifierColumnAliases, session, null); + + if (identifier == null) + { + return null; + } + + return _projection.Lazy + ? ResolveIdentifier(identifier, session) + : GetInitializedEntityFromProjection(rs, session, identifier); + } + + private object GetInitializedEntityFromProjection(DbDataReader rs, ISessionImplementor session, object identifier) + { + var entity = CreateInitializedEntity( + rs, + session, + _projection.Persister, + identifier, + _projection.PropertyColumnAliases, + LockMode.None, + _projection.FetchLazyProperties, + _projection.IsReadOnly); + + return entity; + } + + private static object CreateInitializedEntity(DbDataReader rs, ISessionImplementor session, IQueryable persister, object identifier, string[][] propertyAliases, LockMode lockMode, bool fetchLazyProperties, bool readOnly) + { + var eventSource = session as IEventSource; + PostLoadEvent postLoadEvent = null; + PreLoadEvent preLoadEvent = null; + object entity; + if (eventSource != null) + { + preLoadEvent = new PreLoadEvent(eventSource); + postLoadEvent = new PostLoadEvent(eventSource); + entity = eventSource.Instantiate(persister, identifier); + } + else + { + entity = session.Instantiate(persister.EntityName, identifier); + } + + TwoPhaseLoad.AddUninitializedEntity( + session.GenerateEntityKey(identifier, persister), + entity, + persister, + lockMode, + !fetchLazyProperties, + session); + + var hydrated = persister.Hydrate( + rs, + identifier, + entity, + persister, + propertyAliases, + fetchLazyProperties, + session); + + TwoPhaseLoad.PostHydrate(persister, identifier, hydrated, null, entity, lockMode, !fetchLazyProperties, session); + TwoPhaseLoad.InitializeEntity(entity, readOnly, session, preLoadEvent, postLoadEvent); + return entity; + } + } +} diff --git a/src/NHibernate/Criterion/Projections.cs b/src/NHibernate/Criterion/Projections.cs index 88f750de722..ec3be880ead 100644 --- a/src/NHibernate/Criterion/Projections.cs +++ b/src/NHibernate/Criterion/Projections.cs @@ -16,6 +16,48 @@ namespace NHibernate.Criterion /// public static class Projections { + /// + /// Projection for root entity. + /// + /// + public static EntityProjection RootEntity() + { + return new EntityProjection(); + } + + /// + /// Projection for entity with given alias. + /// + /// The type of the entity. + /// The alias of the entity. + /// + public static EntityProjection Entity(System.Type type, string alias) + { + return new EntityProjection(type, alias); + } + + /// + /// Projection for entity with given alias. + /// + /// /// The type of the entity. + /// The alias of the entity. + /// + public static EntityProjection Entity(string alias) + { + return Entity(typeof(T), alias); + } + + /// + /// Returns an aliased entity. + /// + /// /// The type of the entity. + /// The alias of the entity. + /// A projection of the entity. + public static EntityProjection Entity(Expression> alias) + { + return Entity(typeof(T), ExpressionProcessor.FindMemberExpression(alias.Body)); + } + /// /// Create a distinct projection from a projection /// From 1c9cfec64448251c402718934a18cc78626b239e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 4 Dec 2017 17:28:05 +0530 Subject: [PATCH 02/11] Fixed test warnings --- .../Async/NHSpecificTest/GH948/FixtureByCode.cs | 9 ++++----- .../NHSpecificTest/GH948/FixtureByCode.cs | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs index 0863e05dd03..c3d5182135e 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs @@ -311,27 +311,26 @@ public async Task MultipleEntitiesProjectionsToResultTransformerAsync() using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - MultipleEntitiesResult result = new MultipleEntitiesResult(); + MultipleEntitiesResult r = null; - EntityComplex root = null; EntitySimpleChild child1 = null; EntitySimpleChild child2 = null; EntityComplex sameAsRootChild = null; EntitySimpleChild nullListElem = null; - var r = await (session + + r = await (session .QueryOver() .JoinAlias(ep => ep.Child1, () => child1) .JoinAlias(ep => ep.Child2, () => child2) .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) .Select( - Projections.Alias(Projections.RootEntity(), nameof(result.Root)), + Projections.Alias(Projections.RootEntity(), nameof(r.Root)), Projections.Entity(() => child1), Projections.Entity(() => child2), Projections.Entity(() => sameAsRootChild), Projections.Entity(() => nullListElem) ) - .TransformUsing(Transformers.AliasToBean()) .Take(1) .SingleOrDefaultAsync()); diff --git a/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs index 4697332373c..25bea3be260 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs @@ -300,27 +300,26 @@ public void MultipleEntitiesProjectionsToResultTransformer() using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - MultipleEntitiesResult result = new MultipleEntitiesResult(); + MultipleEntitiesResult r = null; - EntityComplex root = null; EntitySimpleChild child1 = null; EntitySimpleChild child2 = null; EntityComplex sameAsRootChild = null; EntitySimpleChild nullListElem = null; - var r = session + + r = session .QueryOver() .JoinAlias(ep => ep.Child1, () => child1) .JoinAlias(ep => ep.Child2, () => child2) .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) .Select( - Projections.Alias(Projections.RootEntity(), nameof(result.Root)), + Projections.Alias(Projections.RootEntity(), nameof(r.Root)), Projections.Entity(() => child1), Projections.Entity(() => child2), Projections.Entity(() => sameAsRootChild), Projections.Entity(() => nullListElem) ) - .TransformUsing(Transformers.AliasToBean()) .Take(1) .SingleOrDefault(); From 43e4fd67870fd53e693644df2a33b23c9ce44a62 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 6 Dec 2017 09:24:32 +0530 Subject: [PATCH 03/11] Address requested changes --- .../EntityProjectionsTest.cs} | 126 ++++++++++-------- .../EntityProjectionsEntities.cs} | 4 +- .../EntityProjectionsTest.cs} | 126 ++++++++++-------- src/NHibernate/Criterion/EntityProjection.cs | 50 +++++-- 4 files changed, 180 insertions(+), 126 deletions(-) rename src/NHibernate.Test/Async/{NHSpecificTest/GH948/FixtureByCode.cs => Criteria/EntityProjectionsTest.cs} (79%) rename src/NHibernate.Test/{NHSpecificTest/GH948/Entities.cs => Criteria/EntityProjectionsEntities.cs} (96%) rename src/NHibernate.Test/{NHSpecificTest/GH948/FixtureByCode.cs => Criteria/EntityProjectionsTest.cs} (78%) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs similarity index 79% rename from src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs rename to src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs index c3d5182135e..17ad24ad6a1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH948/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs @@ -15,44 +15,52 @@ using NHibernate.Transform; using NUnit.Framework; -namespace NHibernate.Test.NHSpecificTest.GH948 +namespace NHibernate.Test.Criteria { using System.Threading.Tasks; + /// + /// GH948 + /// [TestFixture] - public class ByCodeFixtureAsync : TestCaseMappingByCode + public class EntityProjectionsTestAsync : TestCaseMappingByCode { private EntityWithCompositeId _entityWithCompositeId; protected override HbmMapping GetMappings() { var mapper = new ModelMapper(); - mapper.Class(rc => - { - rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); - rc.Version(ep => ep.Version, vm => { }); + rc.Version(ep => ep.Version, vm => { }); - rc.Property(x => x.Name); + rc.Property(x => x.Name); - rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); - rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); - rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); - rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); + rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); + rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); + rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); - rc.Bag(ep => ep.ChildrenList, m => - { - m.Cascade(Mapping.ByCode.Cascade.All); - m.Inverse(true); - }, a => a.OneToMany()); - - }); + rc.Bag( + ep => ep.ChildrenList, + m => + { + m.Cascade(Mapping.ByCode.Cascade.All); + m.Inverse(true); + }, + a => a.OneToMany()); - mapper.Class(rc => - { - rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); - rc.Property(x => x.Name); - }); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); mapper.Class( rc => @@ -96,14 +104,16 @@ protected override void OnSetUp() Name = "Child1" }; - var parent = new EntityComplex { Name = "ComplexEnityParent", Child1 = child1, Child2 = child2, LazyProp = "SomeBigValue", - SameTypeChild = new EntityComplex() { Name = "ComplexEntityChild" } + SameTypeChild = new EntityComplex() + { + Name = "ComplexEntityChild" + } }; _entityWithCompositeId = new EntityWithCompositeId @@ -130,19 +140,19 @@ protected override void OnSetUp() [Test] public async Task RootEntityProjectionFullyInitializedAndWithUnfetchedLazyPropertiesByDefaultAsync() { - using(var sqlLog = new SqlLogSpy()) + using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { Sfi.Statistics.Clear(); - EntityComplex entitypRoot = await (session + EntityComplex entityRoot = await (session .QueryOver() .Where(ec => ec.LazyProp != null) .Select(Projections.RootEntity()) .Take(1).SingleOrDefaultAsync()); - - Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.True, "Object must be initialized by default"); - Assert.That(session.IsReadOnly(entitypRoot),Is.False, "Object must not be readonly by default"); - Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.False, "Lazy properties should not be initialized by default."); + + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized by default"); + Assert.That(session.IsReadOnly(entityRoot), Is.False, "Object must not be readonly by default"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.False, "Lazy properties should not be initialized by default."); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } } @@ -152,13 +162,13 @@ public async Task RootEntityProjectionLazyAsync() { using (var session = OpenSession()) { - EntityComplex entitypRoot = await (session + EntityComplex entityRoot = await (session .QueryOver() .Select(Projections.RootEntity().SetLazy(true)) .Take(1).SingleOrDefaultAsync()); - - - Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.False, "Object must be lazy loaded."); + + + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.False, "Object must be lazy loaded."); } } @@ -213,10 +223,10 @@ public async Task MultipleLazyEntityProjectionsAsync() Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "Object must be lazy loaded."); Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "Object must be lazy loaded."); Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "Object must be lazy loaded."); - + //make sure objects are populated from different aliases for the same types - Assert.That(root.Id , Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); - Assert.That(child1.Id , Is.Not.EqualTo(child2.Id), "Different objects are expected."); + Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); + Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected."); } } @@ -226,15 +236,15 @@ public async Task EntityProjectionWithLazyPropertiesFetchedAsync() { using (var session = OpenSession()) { - EntityComplex entitypRoot = await (session + EntityComplex entityRoot = await (session .QueryOver() .Where(ec => ec.LazyProp != null) - .Select(Projections.RootEntity().SetAllPropertyFetch(true)) + .Select(Projections.RootEntity().SetFetchLazyProperties(true)) .Take(1).SingleOrDefaultAsync()); - Assert.That(entitypRoot, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(entitypRoot), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + Assert.That(entityRoot, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized"); } } @@ -319,18 +329,18 @@ public async Task MultipleEntitiesProjectionsToResultTransformerAsync() EntitySimpleChild nullListElem = null; r = await (session - .QueryOver() - .JoinAlias(ep => ep.Child1, () => child1) - .JoinAlias(ep => ep.Child2, () => child2) - .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) - .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) - .Select( - Projections.Alias(Projections.RootEntity(), nameof(r.Root)), - Projections.Entity(() => child1), - Projections.Entity(() => child2), - Projections.Entity(() => sameAsRootChild), - Projections.Entity(() => nullListElem) - ) + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) + .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) + .Select( + Projections.Alias(Projections.RootEntity(), nameof(r.Root)), + Projections.Entity(() => child1), + Projections.Entity(() => child2), + Projections.Entity(() => sameAsRootChild), + Projections.Entity(() => nullListElem) + ) .TransformUsing(Transformers.AliasToBean()) .Take(1) .SingleOrDefaultAsync()); @@ -350,12 +360,12 @@ public async Task ReadOnlyProjectionAsync() { using (var session = OpenSession()) { - EntityComplex entitypRoot = await (session + EntityComplex entityRoot = await (session .QueryOver() .Select(Projections.RootEntity().SetReadonly(true)) .Take(1).SingleOrDefaultAsync()); - Assert.That(session.IsReadOnly(entitypRoot), Is.True, "Object must be loaded readonly."); + Assert.That(session.IsReadOnly(entityRoot), Is.True, "Object must be loaded readonly."); } } @@ -375,7 +385,7 @@ public async Task EntityProjectionForCompositeKeyInitializedAsync() Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key) && Equals(x.Name, y.Name)) ? 0 : 1)); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } - + } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/GH948/Entities.cs b/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs similarity index 96% rename from src/NHibernate.Test/NHSpecificTest/GH948/Entities.cs rename to src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs index 20505f6e55d..d4d5edcd415 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH948/Entities.cs +++ b/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace NHibernate.Test.NHSpecificTest.GH948 +namespace NHibernate.Test.Criteria { public class EntitySimpleChild { @@ -14,7 +14,7 @@ public class EntityComplex public virtual Guid Id { get; set; } public virtual int Version { get; set; } - + public virtual string Name { get; set; } public virtual string LazyProp { get; set; } diff --git a/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs similarity index 78% rename from src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs rename to src/NHibernate.Test/Criteria/EntityProjectionsTest.cs index 25bea3be260..b3a06c9c669 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH948/FixtureByCode.cs +++ b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs @@ -5,43 +5,51 @@ using NHibernate.Transform; using NUnit.Framework; -namespace NHibernate.Test.NHSpecificTest.GH948 +namespace NHibernate.Test.Criteria { + /// + /// GH948 + /// [TestFixture] - public class ByCodeFixture : TestCaseMappingByCode + public class EntityProjectionsTest : TestCaseMappingByCode { private EntityWithCompositeId _entityWithCompositeId; protected override HbmMapping GetMappings() { var mapper = new ModelMapper(); - mapper.Class(rc => - { - rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); - rc.Version(ep => ep.Version, vm => { }); + rc.Version(ep => ep.Version, vm => { }); - rc.Property(x => x.Name); + rc.Property(x => x.Name); - rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); - rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); - rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); - rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); + rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); + rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); + rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); - rc.Bag(ep => ep.ChildrenList, m => - { - m.Cascade(Mapping.ByCode.Cascade.All); - m.Inverse(true); - }, a => a.OneToMany()); - - }); + rc.Bag( + ep => ep.ChildrenList, + m => + { + m.Cascade(Mapping.ByCode.Cascade.All); + m.Inverse(true); + }, + a => a.OneToMany()); - mapper.Class(rc => - { - rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); - rc.Property(x => x.Name); - }); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); mapper.Class( rc => @@ -85,14 +93,16 @@ protected override void OnSetUp() Name = "Child1" }; - var parent = new EntityComplex { Name = "ComplexEnityParent", Child1 = child1, Child2 = child2, LazyProp = "SomeBigValue", - SameTypeChild = new EntityComplex() { Name = "ComplexEntityChild" } + SameTypeChild = new EntityComplex() + { + Name = "ComplexEntityChild" + } }; _entityWithCompositeId = new EntityWithCompositeId @@ -119,19 +129,19 @@ protected override void OnSetUp() [Test] public void RootEntityProjectionFullyInitializedAndWithUnfetchedLazyPropertiesByDefault() { - using(var sqlLog = new SqlLogSpy()) + using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { Sfi.Statistics.Clear(); - EntityComplex entitypRoot = session + EntityComplex entityRoot = session .QueryOver() .Where(ec => ec.LazyProp != null) .Select(Projections.RootEntity()) .Take(1).SingleOrDefault(); - - Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.True, "Object must be initialized by default"); - Assert.That(session.IsReadOnly(entitypRoot),Is.False, "Object must not be readonly by default"); - Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.False, "Lazy properties should not be initialized by default."); + + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized by default"); + Assert.That(session.IsReadOnly(entityRoot), Is.False, "Object must not be readonly by default"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.False, "Lazy properties should not be initialized by default."); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } } @@ -141,13 +151,13 @@ public void RootEntityProjectionLazy() { using (var session = OpenSession()) { - EntityComplex entitypRoot = session + EntityComplex entityRoot = session .QueryOver() .Select(Projections.RootEntity().SetLazy(true)) .Take(1).SingleOrDefault(); - - - Assert.That(NHibernateUtil.IsInitialized(entitypRoot),Is.False, "Object must be lazy loaded."); + + + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.False, "Object must be lazy loaded."); } } @@ -202,10 +212,10 @@ public void MultipleLazyEntityProjections() Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "Object must be lazy loaded."); Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "Object must be lazy loaded."); Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "Object must be lazy loaded."); - + //make sure objects are populated from different aliases for the same types - Assert.That(root.Id , Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); - Assert.That(child1.Id , Is.Not.EqualTo(child2.Id), "Different objects are expected."); + Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); + Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected."); } } @@ -215,15 +225,15 @@ public void EntityProjectionWithLazyPropertiesFetched() { using (var session = OpenSession()) { - EntityComplex entitypRoot = session + EntityComplex entityRoot = session .QueryOver() .Where(ec => ec.LazyProp != null) - .Select(Projections.RootEntity().SetAllPropertyFetch(true)) + .Select(Projections.RootEntity().SetFetchLazyProperties(true)) .Take(1).SingleOrDefault(); - Assert.That(entitypRoot, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(entitypRoot), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsPropertyInitialized(entitypRoot, nameof(entitypRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + Assert.That(entityRoot, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized"); } } @@ -308,18 +318,18 @@ public void MultipleEntitiesProjectionsToResultTransformer() EntitySimpleChild nullListElem = null; r = session - .QueryOver() - .JoinAlias(ep => ep.Child1, () => child1) - .JoinAlias(ep => ep.Child2, () => child2) - .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) - .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) - .Select( - Projections.Alias(Projections.RootEntity(), nameof(r.Root)), - Projections.Entity(() => child1), - Projections.Entity(() => child2), - Projections.Entity(() => sameAsRootChild), - Projections.Entity(() => nullListElem) - ) + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .JoinAlias(ep => ep.Child2, () => child2) + .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) + .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) + .Select( + Projections.Alias(Projections.RootEntity(), nameof(r.Root)), + Projections.Entity(() => child1), + Projections.Entity(() => child2), + Projections.Entity(() => sameAsRootChild), + Projections.Entity(() => nullListElem) + ) .TransformUsing(Transformers.AliasToBean()) .Take(1) .SingleOrDefault(); @@ -339,12 +349,12 @@ public void ReadOnlyProjection() { using (var session = OpenSession()) { - EntityComplex entitypRoot = session + EntityComplex entityRoot = session .QueryOver() .Select(Projections.RootEntity().SetReadonly(true)) .Take(1).SingleOrDefault(); - Assert.That(session.IsReadOnly(entitypRoot), Is.True, "Object must be loaded readonly."); + Assert.That(session.IsReadOnly(entityRoot), Is.True, "Object must be loaded readonly."); } } @@ -364,7 +374,7 @@ public void EntityProjectionForCompositeKeyInitialized() Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key) && Equals(x.Name, y.Name)) ? 0 : 1)); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } - + } [Test] diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index 9a566d7a6fa..f73198ca856 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -7,8 +7,9 @@ namespace NHibernate.Criterion { - using TThis = EntityProjection; - + /// + /// Entity projection + /// [Serializable] public class EntityProjection : IProjection { @@ -25,14 +26,30 @@ public EntityProjection() : this(null, null) { } + /// + /// Entity projection for given type and alias + /// + /// Type of entity + /// Entity alias public EntityProjection(System.Type rootEntity, string entityAlias) { RootEntity = rootEntity; _entityAlias = entityAlias; } + /// + /// Fetch lazy properties + /// public bool FetchLazyProperties { get; set; } + + /// + /// Read-only entity + /// public bool IsReadOnly { get; set; } + + /// + /// Lazy load entity + /// public bool Lazy { get; set; } internal string[] IdentifierColumnAliases { get; private set; } @@ -42,19 +59,34 @@ public EntityProjection(System.Type rootEntity, string entityAlias) #region Configuration methods - public TThis SetLazy(bool lazy = true) + /// + /// Lazy load entity + /// + /// + /// + public EntityProjection SetLazy(bool lazy = true) { Lazy = lazy; return this; } - public TThis SetAllPropertyFetch(bool fetchLazyProperties = true) + /// + /// Fetch lazy properties + /// + /// + /// + public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) { FetchLazyProperties = fetchLazyProperties; return this; } - public TThis SetReadonly(bool isReadOnly = true) + /// + /// Set the read-only mode for entity + /// + /// + /// + public EntityProjection SetReadonly(bool isReadOnly = true) { IsReadOnly = isReadOnly; return this; @@ -94,14 +126,14 @@ IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) return _types; } - public string[] GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { SetFields(criteria, criteriaQuery); return _columnAliases; } - public string[] GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { SetFields(criteria, criteriaQuery); @@ -149,7 +181,9 @@ private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery) _entityAlias = criteria.Alias; } - Persister = (IQueryable) criteriaQuery.Factory.GetEntityPersister(RootEntity.FullName); + Persister = criteriaQuery.Factory.GetEntityPersister(RootEntity.FullName) as IQueryable; + if (Persister == null) + throw new HibernateException($"Projecting to entities requires a '{typeof(IQueryable).FullName}' persister, '{RootEntity.FullName}' does not have one."); ICriteria subcriteria = criteria.GetCriteriaByAlias(_entityAlias); if (subcriteria == null) From a21fb4a18c12a11340e28a9f7868192f0a4e1118 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 8 Dec 2017 13:37:04 +0530 Subject: [PATCH 04/11] Inject into CriteriaLoader info about projected entities --- .../Async/Criteria/EntityProjectionsTest.cs | 56 ++++++++- .../Criteria/EntityProjectionsTest.cs | 56 ++++++++- .../Async/Criterion/EntityProjectionType.cs | 106 ------------------ src/NHibernate/Criterion/EntityProjection.cs | 75 ++++++------- .../Criterion/EntityProjectionType.cs | 98 ---------------- .../Criterion/ProjectionsExtensions.cs | 26 +++++ src/NHibernate/Impl/ExpressionProcessor.cs | 1 + .../Loader/AbstractEntityJoinWalker.cs | 64 +++++++++-- src/NHibernate/Loader/BasicLoader.cs | 9 +- .../Loader/Criteria/CriteriaJoinWalker.cs | 7 +- .../Criteria/CriteriaQueryTranslator.cs | 18 ++- .../ISupportEntityProjectionCriteriaQuery.cs | 9 ++ src/NHibernate/Loader/JoinWalker.cs | 2 + src/NHibernate/Loader/OuterJoinLoader.cs | 9 +- 14 files changed, 266 insertions(+), 270 deletions(-) delete mode 100644 src/NHibernate/Async/Criterion/EntityProjectionType.cs delete mode 100644 src/NHibernate/Criterion/EntityProjectionType.cs create mode 100644 src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs diff --git a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs index 17ad24ad6a1..70bb59eb222 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs @@ -191,6 +191,54 @@ public async Task AliasedEntityProjectionAsync() } } + [Test] + public async Task EntityProjectionAsSelectExpressionAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = await (session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .Select(ec => child1.AsEntity()) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task EntityProjectionLockModeAsync() + { + var upgradeHint = Dialect.ForUpdateString; + if(string.IsNullOrEmpty(upgradeHint)) + upgradeHint = this.Dialect.AppendLockHint(LockMode.Upgrade, string.Empty); + if (string.IsNullOrEmpty(upgradeHint)) + { + Assert.Ignore($"Upgrade hint is not supported by dialect {Dialect.GetType().Name}"); + } + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = await (session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .Lock(() => child1).Upgrade + .Select(Projections.Entity(() => child1)) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(sqlLog.Appender.GetEvents()[0].RenderedMessage, Does.Contain(upgradeHint)); + } + } + [Test] public async Task MultipleLazyEntityProjectionsAsync() { @@ -313,6 +361,7 @@ class MultipleEntitiesResult public EntitySimpleChild Child2 { get; set; } public EntityComplex SameAsRootChild { get; set; } public EntitySimpleChild NullListElem { get; set; } + public string Name { get; set; } } [Test] @@ -335,9 +384,11 @@ public async Task MultipleEntitiesProjectionsToResultTransformerAsync() .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) .Select( - Projections.Alias(Projections.RootEntity(), nameof(r.Root)), + Projections.RootEntity().WithAlias(nameof(r.Root)), Projections.Entity(() => child1), + Projections.Property(() => child2.Name).As(nameof(r.Name)), Projections.Entity(() => child2), + Projections.Property(() => child1.Id), Projections.Entity(() => sameAsRootChild), Projections.Entity(() => nullListElem) ) @@ -362,7 +413,8 @@ public async Task ReadOnlyProjectionAsync() { EntityComplex entityRoot = await (session .QueryOver() - .Select(Projections.RootEntity().SetReadonly(true)) + .Select(Projections.RootEntity()) + .ReadOnly() .Take(1).SingleOrDefaultAsync()); Assert.That(session.IsReadOnly(entityRoot), Is.True, "Object must be loaded readonly."); diff --git a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs index b3a06c9c669..eb3570e6fd6 100644 --- a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs @@ -180,6 +180,54 @@ public void AliasedEntityProjection() } } + [Test] + public void EntityProjectionAsSelectExpression() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .Select(ec => child1.AsEntity()) + .Take(1).SingleOrDefault(); + + Assert.That(child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void EntityProjectionLockMode() + { + var upgradeHint = Dialect.ForUpdateString; + if(string.IsNullOrEmpty(upgradeHint)) + upgradeHint = this.Dialect.AppendLockHint(LockMode.Upgrade, string.Empty); + if (string.IsNullOrEmpty(upgradeHint)) + { + Assert.Ignore($"Upgrade hint is not supported by dialect {Dialect.GetType().Name}"); + } + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild child1 = null; + child1 = session + .QueryOver() + .JoinAlias(ep => ep.Child1, () => child1) + .Lock(() => child1).Upgrade + .Select(Projections.Entity(() => child1)) + .Take(1).SingleOrDefault(); + + Assert.That(child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(sqlLog.Appender.GetEvents()[0].RenderedMessage, Does.Contain(upgradeHint)); + } + } + [Test] public void MultipleLazyEntityProjections() { @@ -302,6 +350,7 @@ class MultipleEntitiesResult public EntitySimpleChild Child2 { get; set; } public EntityComplex SameAsRootChild { get; set; } public EntitySimpleChild NullListElem { get; set; } + public string Name { get; set; } } [Test] @@ -324,9 +373,11 @@ public void MultipleEntitiesProjectionsToResultTransformer() .JoinAlias(ep => ep.SameTypeChild, () => sameAsRootChild) .JoinAlias(ep => ep.ChildrenList, () => nullListElem, JoinType.LeftOuterJoin) .Select( - Projections.Alias(Projections.RootEntity(), nameof(r.Root)), + Projections.RootEntity().WithAlias(nameof(r.Root)), Projections.Entity(() => child1), + Projections.Property(() => child2.Name).As(nameof(r.Name)), Projections.Entity(() => child2), + Projections.Property(() => child1.Id), Projections.Entity(() => sameAsRootChild), Projections.Entity(() => nullListElem) ) @@ -351,7 +402,8 @@ public void ReadOnlyProjection() { EntityComplex entityRoot = session .QueryOver() - .Select(Projections.RootEntity().SetReadonly(true)) + .Select(Projections.RootEntity()) + .ReadOnly() .Take(1).SingleOrDefault(); Assert.That(session.IsReadOnly(entityRoot), Is.True, "Object must be loaded readonly."); diff --git a/src/NHibernate/Async/Criterion/EntityProjectionType.cs b/src/NHibernate/Async/Criterion/EntityProjectionType.cs deleted file mode 100644 index e2f97aa270c..00000000000 --- a/src/NHibernate/Async/Criterion/EntityProjectionType.cs +++ /dev/null @@ -1,106 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 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.Data.Common; -using NHibernate.Engine; -using NHibernate.Event; -using NHibernate.Persister.Entity; -using NHibernate.Type; - -namespace NHibernate.Criterion -{ - using System.Threading.Tasks; - using System.Threading; - internal partial class EntityProjectionType : ManyToOneType, IType - { - - Task IType.NullSafeGetAsync(DbDataReader rs, string[] names, ISessionImplementor session, object owner, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - //names parameter is ignored (taken from projection) - return NullSafeGetAsync(rs, string.Empty, session, owner, cancellationToken); - } - - public override async Task NullSafeGetAsync(DbDataReader rs, string name, ISessionImplementor session, object owner, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var identifier = await (_projection.Persister.IdentifierType.NullSafeGetAsync(rs, _projection.IdentifierColumnAliases, session, null, cancellationToken)).ConfigureAwait(false); - - if (identifier == null) - { - return null; - } - - return _projection.Lazy - ? await (ResolveIdentifierAsync(identifier, session, cancellationToken)).ConfigureAwait(false) - : await (GetInitializedEntityFromProjectionAsync(rs, session, identifier, cancellationToken)).ConfigureAwait(false); - } - - private async Task GetInitializedEntityFromProjectionAsync(DbDataReader rs, ISessionImplementor session, object identifier, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var entity = await (CreateInitializedEntityAsync( - rs, - session, - _projection.Persister, - identifier, - _projection.PropertyColumnAliases, - LockMode.None, - _projection.FetchLazyProperties, - _projection.IsReadOnly, cancellationToken)).ConfigureAwait(false); - - return entity; - } - - private static async Task CreateInitializedEntityAsync(DbDataReader rs, ISessionImplementor session, IQueryable persister, object identifier, string[][] propertyAliases, LockMode lockMode, bool fetchLazyProperties, bool readOnly, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var eventSource = session as IEventSource; - PostLoadEvent postLoadEvent = null; - PreLoadEvent preLoadEvent = null; - object entity; - if (eventSource != null) - { - preLoadEvent = new PreLoadEvent(eventSource); - postLoadEvent = new PostLoadEvent(eventSource); - entity = eventSource.Instantiate(persister, identifier); - } - else - { - entity = session.Instantiate(persister.EntityName, identifier); - } - - TwoPhaseLoad.AddUninitializedEntity( - session.GenerateEntityKey(identifier, persister), - entity, - persister, - lockMode, - !fetchLazyProperties, - session); - - var hydrated = await (persister.HydrateAsync( - rs, - identifier, - entity, - persister, - propertyAliases, - fetchLazyProperties, - session, cancellationToken)).ConfigureAwait(false); - - TwoPhaseLoad.PostHydrate(persister, identifier, hydrated, null, entity, lockMode, !fetchLazyProperties, session); - await (TwoPhaseLoad.InitializeEntityAsync(entity, readOnly, session, preLoadEvent, postLoadEvent, cancellationToken)).ConfigureAwait(false); - return entity; - } - } -} diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index f73198ca856..429e332f75b 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -1,6 +1,7 @@ using System; -using System.Linq; using NHibernate.Engine; +using NHibernate.Loader; +using NHibernate.Loader.Criteria; using NHibernate.SqlCommand; using NHibernate.Type; using IQueryable = NHibernate.Persister.Entity.IQueryable; @@ -14,10 +15,8 @@ namespace NHibernate.Criterion public class EntityProjection : IProjection { private string _entityAlias; - private string _columnAliasSuffix; - private string _tableAlias; + private System.Type _rootEntity; private IType[] _types; - private string[] _columnAliases; /// /// Root entity projection @@ -33,7 +32,7 @@ public EntityProjection() : this(null, null) /// Entity alias public EntityProjection(System.Type rootEntity, string entityAlias) { - RootEntity = rootEntity; + _rootEntity = rootEntity; _entityAlias = entityAlias; } @@ -42,20 +41,17 @@ public EntityProjection(System.Type rootEntity, string entityAlias) /// public bool FetchLazyProperties { get; set; } - /// - /// Read-only entity - /// - public bool IsReadOnly { get; set; } - /// /// Lazy load entity /// public bool Lazy { get; set; } - internal string[] IdentifierColumnAliases { get; private set; } - internal string[][] PropertyColumnAliases { get; private set; } + internal string[] IdentifierColumnAliases { get; set; } internal IQueryable Persister { get; private set; } - internal System.Type RootEntity { get; private set; } + + + public string ColumnAliasSuffix { get; private set; } + public string TableAlias { get; private set; } #region Configuration methods @@ -81,17 +77,6 @@ public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) return this; } - /// - /// Set the read-only mode for entity - /// - /// - /// - public EntityProjection SetReadonly(bool isReadOnly = true) - { - IsReadOnly = isReadOnly; - return this; - } - #endregion Configuration methods #region IProjection implementation @@ -130,27 +115,28 @@ string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteri { SetFields(criteria, criteriaQuery); - return _columnAliases; + return IdentifierColumnAliases; } string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { SetFields(criteria, criteriaQuery); - return _columnAliases; + return IdentifierColumnAliases; } SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) { + //return new SqlString(string.Empty); SetFields(criteria, criteriaQuery); - string identifierSelectFragment = Persister.IdentifierSelectFragment(_tableAlias, _columnAliasSuffix); + string identifierSelectFragment = Persister.IdentifierSelectFragment(TableAlias, ColumnAliasSuffix); return new SqlString( Lazy ? identifierSelectFragment : string.Concat( identifierSelectFragment, - Persister.PropertySelectFragment(_tableAlias, _columnAliasSuffix, FetchLazyProperties))); + Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties))); } SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) @@ -171,9 +157,20 @@ private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery) if (Persister != null) return; - if (RootEntity == null) + if (Lazy == false) + { + var entityProjectionQuery = criteriaQuery as ISupportEntityProjectionCriteriaQuery; + if (entityProjectionQuery == null) + { + throw new HibernateException($"Projecting to entities requires a '{criteriaQuery.GetType().FullName}' type to implement {nameof(ISupportEntityProjectionCriteriaQuery)} interface."); + } + + entityProjectionQuery.RegisterEntityProjection(this); + } + + if (_rootEntity == null) { - RootEntity = criteria.GetRootEntityTypeIfAvailable(); + _rootEntity = criteria.GetRootEntityTypeIfAvailable(); } if (_entityAlias == null) @@ -181,29 +178,23 @@ private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery) _entityAlias = criteria.Alias; } - Persister = criteriaQuery.Factory.GetEntityPersister(RootEntity.FullName) as IQueryable; + Persister = criteriaQuery.Factory.GetEntityPersister(_rootEntity.FullName) as IQueryable; if (Persister == null) - throw new HibernateException($"Projecting to entities requires a '{typeof(IQueryable).FullName}' persister, '{RootEntity.FullName}' does not have one."); + throw new HibernateException($"Projecting to entities requires a '{typeof(IQueryable).FullName}' persister, '{_rootEntity.FullName}' does not have one."); ICriteria subcriteria = criteria.GetCriteriaByAlias(_entityAlias); if (subcriteria == null) throw new HibernateException($"Criteria\\QueryOver alias '{_entityAlias}' for entity projection is not found."); - _tableAlias = criteriaQuery.GetSQLAlias( + TableAlias = criteriaQuery.GetSQLAlias( subcriteria, Persister.IdentifierPropertyName ?? string.Empty); - _columnAliasSuffix = criteriaQuery.GetIndexForAlias().ToString(); - - IdentifierColumnAliases = Persister.GetIdentifierAliases(_columnAliasSuffix); - - PropertyColumnAliases = Lazy - ? new string[][] { } - : Enumerable.Range(0, Persister.PropertyNames.Length).Select(i => Persister.GetPropertyAliases(_columnAliasSuffix, i)).ToArray(); + ColumnAliasSuffix = BasicLoader.GenerateSuffix(criteriaQuery.GetIndexForAlias()); - _columnAliases = IdentifierColumnAliases.Concat(PropertyColumnAliases.SelectMany(ca => ca)).ToArray(); + IdentifierColumnAliases = Persister.GetIdentifierAliases(ColumnAliasSuffix); - _types = new IType[] {new EntityProjectionType(this)}; + _types = new IType[] {TypeFactory.ManyToOne(Persister.EntityName, true),}; } } } diff --git a/src/NHibernate/Criterion/EntityProjectionType.cs b/src/NHibernate/Criterion/EntityProjectionType.cs deleted file mode 100644 index 43c43ca4b39..00000000000 --- a/src/NHibernate/Criterion/EntityProjectionType.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Data.Common; -using NHibernate.Engine; -using NHibernate.Event; -using NHibernate.Persister.Entity; -using NHibernate.Type; - -namespace NHibernate.Criterion -{ - /// - /// Specialized type for retrieving entity from Criteria/QueryOver API projection. - /// Intended to be used only inside - /// - [Serializable] - internal partial class EntityProjectionType : ManyToOneType, IType - { - private readonly EntityProjection _projection; - - public EntityProjectionType(EntityProjection projection) : base(projection.RootEntity.FullName, projection.Lazy) - { - _projection = projection; - } - - object IType.NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner) - { - //names parameter is ignored (taken from projection) - return NullSafeGet(rs, string.Empty, session, owner); - } - - public override object NullSafeGet(DbDataReader rs, string name, ISessionImplementor session, object owner) - { - var identifier = _projection.Persister.IdentifierType.NullSafeGet(rs, _projection.IdentifierColumnAliases, session, null); - - if (identifier == null) - { - return null; - } - - return _projection.Lazy - ? ResolveIdentifier(identifier, session) - : GetInitializedEntityFromProjection(rs, session, identifier); - } - - private object GetInitializedEntityFromProjection(DbDataReader rs, ISessionImplementor session, object identifier) - { - var entity = CreateInitializedEntity( - rs, - session, - _projection.Persister, - identifier, - _projection.PropertyColumnAliases, - LockMode.None, - _projection.FetchLazyProperties, - _projection.IsReadOnly); - - return entity; - } - - private static object CreateInitializedEntity(DbDataReader rs, ISessionImplementor session, IQueryable persister, object identifier, string[][] propertyAliases, LockMode lockMode, bool fetchLazyProperties, bool readOnly) - { - var eventSource = session as IEventSource; - PostLoadEvent postLoadEvent = null; - PreLoadEvent preLoadEvent = null; - object entity; - if (eventSource != null) - { - preLoadEvent = new PreLoadEvent(eventSource); - postLoadEvent = new PostLoadEvent(eventSource); - entity = eventSource.Instantiate(persister, identifier); - } - else - { - entity = session.Instantiate(persister.EntityName, identifier); - } - - TwoPhaseLoad.AddUninitializedEntity( - session.GenerateEntityKey(identifier, persister), - entity, - persister, - lockMode, - !fetchLazyProperties, - session); - - var hydrated = persister.Hydrate( - rs, - identifier, - entity, - persister, - propertyAliases, - fetchLazyProperties, - session); - - TwoPhaseLoad.PostHydrate(persister, identifier, hydrated, null, entity, lockMode, !fetchLazyProperties, session); - TwoPhaseLoad.InitializeEntity(entity, readOnly, session, preLoadEvent, postLoadEvent); - return entity; - } - } -} diff --git a/src/NHibernate/Criterion/ProjectionsExtensions.cs b/src/NHibernate/Criterion/ProjectionsExtensions.cs index b98b3b0e722..066082d622a 100644 --- a/src/NHibernate/Criterion/ProjectionsExtensions.cs +++ b/src/NHibernate/Criterion/ProjectionsExtensions.cs @@ -21,6 +21,17 @@ public static IProjection WithAlias(this IProjection projection, return Projections.Alias(projection, aliasContainer); } + /// + /// Create an alias for a projection + /// + /// the projection instance + /// LambdaExpression returning an alias + /// return NHibernate.Criterion.IProjection + public static IProjection WithAlias(this IProjection projection, string alias) + { + return Projections.Alias(projection, alias); + } + internal static IProjection ProcessYear(System.Linq.Expressions.Expression expression) { IProjection property = ExpressionProcessor.FindMemberProjection(expression).AsProjection(); @@ -323,5 +334,20 @@ internal static IProjection ProcessMod(MethodCallExpression methodCallExpression object divisor = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]); return Projections.SqlFunction("mod", NHibernateUtil.Int32, property, Projections.Constant(divisor)); } + + /// + /// Project Entity + /// + public static T AsEntity(this T alias) where T:class + { + throw new Exception("Not to be used directly - use inside QueryOver expression"); + } + + internal static IProjection ProcessAsEntity(MethodCallExpression methodCallExpression) + { + var expression = methodCallExpression.Arguments[0]; + var aliasName = ExpressionProcessor.FindMemberExpression(expression); + return new EntityProjection(expression.Type, aliasName); + } } } diff --git a/src/NHibernate/Impl/ExpressionProcessor.cs b/src/NHibernate/Impl/ExpressionProcessor.cs index 8fdb12af7a3..00217b3e850 100644 --- a/src/NHibernate/Impl/ExpressionProcessor.cs +++ b/src/NHibernate/Impl/ExpressionProcessor.cs @@ -198,6 +198,7 @@ static ExpressionProcessor() RegisterCustomProjection(() => Math.Round(default(decimal)), ProjectionsExtensions.ProcessRound); RegisterCustomProjection(() => Math.Round(default(double), default(int)), ProjectionsExtensions.ProcessRound); RegisterCustomProjection(() => Math.Round(default(decimal), default(int)), ProjectionsExtensions.ProcessRound); + RegisterCustomProjection(() => ProjectionsExtensions.AsEntity(default(object)), ProjectionsExtensions.ProcessAsEntity); } private static ICriterion Eq(ProjectionInfo property, object value) diff --git a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs index 1ad5ce5cc19..475388ed01e 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using NHibernate.Criterion; using NHibernate.Engine; using NHibernate.Loader.Criteria; using NHibernate.Persister.Entity; @@ -32,19 +34,55 @@ protected virtual void InitAll(SqlString whereString, SqlString orderByString, L { WalkEntityTree(persister, Alias); IList allAssociations = new List(associations); - allAssociations.Add( - new OuterJoinableAssociation(persister.EntityType, null, null, alias, JoinType.LeftOuterJoin, null, Factory, - CollectionHelper.EmptyDictionary())); + allAssociations.Add(CreateAssociation(persister.EntityType, alias)); InitPersisters(allAssociations, lockMode); InitStatementString(whereString, orderByString, lockMode); } - protected void InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary enabledFilters, LockMode lockMode) + protected void InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary enabledFilters, LockMode lockMode, IList entityProjections = null) { WalkEntityTree(persister, Alias); - Persisters = Array.Empty(); + if (entityProjections?.Count > 0) + { + var list = new List(); + var eagerProps = new bool[entityProjections.Count]; + for (var i = 0; i < entityProjections.Count; i++) + { + var e = entityProjections[i]; + list.Add( + CreateAssociation(e.Persister.EntityMetamodel.EntityType, e.TableAlias)); + if (e.FetchLazyProperties) + { + eagerProps[i] = true; + } + } + + InitPersisters(list, lockMode); + Suffixes = entityProjections.Select(ep => ep.ColumnAliasSuffix).ToArray(); + EagerPropertyFetches = eagerProps; + } + else + { + Persisters = Array.Empty(); + Suffixes = Array.Empty(); + } + InitStatementString(projectionString, whereString, orderByString, groupByString, havingString, lockMode); + + } + + private OuterJoinableAssociation CreateAssociation(EntityType entityType, string tableAlias) + { + return new OuterJoinableAssociation( + entityType, + null, + null, + tableAlias, + JoinType.LeftOuterJoin, + null, + Factory, + CollectionHelper.EmptyDictionary()); } private void InitStatementString(SqlString condition, SqlString orderBy, LockMode lockMode) @@ -52,15 +90,19 @@ private void InitStatementString(SqlString condition, SqlString orderBy, LockMod InitStatementString(null, condition, orderBy, null, null, lockMode); } - private void InitStatementString(SqlString projection,SqlString condition, SqlString orderBy, SqlString groupBy, SqlString having, LockMode lockMode) + private void InitStatementString(SqlString projection, SqlString condition, SqlString orderBy, SqlString groupBy, SqlString having, LockMode lockMode) { - int joins = CountEntityPersisters(associations); - Suffixes = BasicLoader.GenerateSuffixes(joins + 1); + SqlString selectClause = projection; + if (selectClause == null) + { + int joins = CountEntityPersisters(associations); + + Suffixes = BasicLoader.GenerateSuffixes(joins + 1); + selectClause = new SqlString(persister.SelectFragment(alias, Suffixes[joins]) + SelectString(associations)); + } + JoinFragment ojf = MergeOuterJoins(associations); - SqlString selectClause = - projection ?? new SqlString(persister.SelectFragment(alias, Suffixes[joins]) + SelectString(associations)); - SqlSelectBuilder select = new SqlSelectBuilder(Factory) .SetLockMode(lockMode) .SetSelectClause(selectClause) diff --git a/src/NHibernate/Loader/BasicLoader.cs b/src/NHibernate/Loader/BasicLoader.cs index a9958c88666..1e2055329cc 100644 --- a/src/NHibernate/Loader/BasicLoader.cs +++ b/src/NHibernate/Loader/BasicLoader.cs @@ -93,10 +93,15 @@ public static string[] GenerateSuffixes(int seed, int length) for (int i = 0; i < length; i++) { - suffixes[i] = (i + seed).ToString() + StringHelper.Underscore; + suffixes[i] = GenerateSuffix(i + seed); } return suffixes; } + + public static string GenerateSuffix(int index) + { + return index.ToString() + StringHelper.Underscore; + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs index 65ecef6b164..2394fef4f19 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs @@ -51,8 +51,9 @@ public CriteriaJoinWalker(IOuterJoinLoadable persister, CriteriaQueryTranslator translator.GetOrderBy(), translator.GetGroupBy(), translator.GetHavingCondition(), - enabledFilters, - LockMode.None); + enabledFilters, + LockMode.None, + translator.GetEntityProjections()); resultTypes = translator.ProjectedTypes; userAliases = translator.ProjectedAliases; @@ -231,4 +232,4 @@ protected override SqlString GetWithClause(string path) return translator.GetWithClause(path); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index 731727a4e1a..fdc02627f5d 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -14,7 +14,7 @@ namespace NHibernate.Loader.Criteria { - public class CriteriaQueryTranslator : ICriteriaQuery + public class CriteriaQueryTranslator : ICriteriaQuery, ISupportEntityProjectionCriteriaQuery { public static readonly string RootSqlAlias = CriteriaSpecification.RootAlias + '_'; private static readonly INHibernateLogger logger = NHibernateLogger.For(typeof(CriteriaQueryTranslator)); @@ -26,6 +26,7 @@ public class CriteriaQueryTranslator : ICriteriaQuery private readonly string rootEntityName; private readonly string rootSQLAlias; private int indexForAlias = 0; + private IList _entityProjections = new List(); private readonly IDictionary criteriaInfoMap = new Dictionary(); @@ -45,6 +46,7 @@ public class CriteriaQueryTranslator : ICriteriaQuery private readonly ICollection collectedParameterSpecifications; private readonly ICollection namedParameters; private readonly ISet subQuerySpaces = new HashSet(); + public IList entityProjections = new List(); @@ -191,6 +193,16 @@ public string[] ProjectedAliases get { return rootCriteria.Projection.Aliases; } } + public IList GetEntityProjections() + { + return _entityProjections; + } + + public void RegisterEntityProjection(EntityProjection projection) + { + _entityProjections.Add(projection); + } + public SqlString GetWhereCondition() { SqlStringBuilder condition = new SqlStringBuilder(30); @@ -866,6 +878,6 @@ private void CreateSubQuerySpaces() subQuerySpaces.UnionWith(translator.GetQuerySpaces()); } - } + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs b/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs new file mode 100644 index 00000000000..bf1cd3910a3 --- /dev/null +++ b/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs @@ -0,0 +1,9 @@ +using NHibernate.Criterion; + +namespace NHibernate.Loader.Criteria +{ + public interface ISupportEntityProjectionCriteriaQuery + { + void RegisterEntityProjection(EntityProjection projection); + } +} \ No newline at end of file diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 7dfc36c36cd..5450e09c84e 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -55,6 +55,8 @@ public string[] Aliases set { aliases = value; } } + public bool[] EagerPropertyFetches { get; set; } + public int[] CollectionOwners { get { return collectionOwners; } diff --git a/src/NHibernate/Loader/OuterJoinLoader.cs b/src/NHibernate/Loader/OuterJoinLoader.cs index f64f097de26..eb5d515b114 100644 --- a/src/NHibernate/Loader/OuterJoinLoader.cs +++ b/src/NHibernate/Loader/OuterJoinLoader.cs @@ -29,6 +29,7 @@ public abstract class OuterJoinLoader : BasicLoader private SqlString sql; private string[] suffixes; private string[] collectionSuffixes; + private bool[] entityEagerPropertyFetches; private readonly IDictionary enabledFilters; @@ -98,6 +99,11 @@ protected override ICollectionPersister[] CollectionPersisters get { return collectionPersisters; } } + protected override bool[] EntityEagerPropertyFetches + { + get { return entityEagerPropertyFetches; } + } + protected void InitFromWalker(JoinWalker walker) { persisters = walker.Persisters; @@ -110,6 +116,7 @@ protected void InitFromWalker(JoinWalker walker) collectionOwners = walker.CollectionOwners; sql = walker.SqlString; aliases = walker.Aliases; + entityEagerPropertyFetches = walker.EagerPropertyFetches; } } -} \ No newline at end of file +} From 2475764fac4398e6868c68edcd0d1bf5e23b5c0a Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 20 Dec 2017 14:57:39 +0530 Subject: [PATCH 05/11] Clean up --- src/NHibernate/Criterion/EntityProjection.cs | 45 ++++++------------- .../Criterion/ProjectionsExtensions.cs | 2 +- .../Loader/AbstractEntityJoinWalker.cs | 31 ++++++++----- .../Criteria/CriteriaQueryTranslator.cs | 11 +++-- .../ISupportEntityProjectionCriteriaQuery.cs | 3 +- 5 files changed, 41 insertions(+), 51 deletions(-) diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index 429e332f75b..38c2adee7f7 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -17,6 +17,7 @@ public class EntityProjection : IProjection private string _entityAlias; private System.Type _rootEntity; private IType[] _types; + private string[] _identifierColumnAliases; /// /// Root entity projection @@ -46,20 +47,15 @@ public EntityProjection(System.Type rootEntity, string entityAlias) /// public bool Lazy { get; set; } - internal string[] IdentifierColumnAliases { get; set; } internal IQueryable Persister { get; private set; } - - - public string ColumnAliasSuffix { get; private set; } - public string TableAlias { get; private set; } + internal string ColumnAliasSuffix { get; private set; } + internal string TableAlias { get; private set; } #region Configuration methods /// /// Lazy load entity /// - /// - /// public EntityProjection SetLazy(bool lazy = true) { Lazy = lazy; @@ -69,8 +65,6 @@ public EntityProjection SetLazy(bool lazy = true) /// /// Fetch lazy properties /// - /// - /// public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) { FetchLazyProperties = fetchLazyProperties; @@ -81,27 +75,15 @@ public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) #region IProjection implementation - /// - /// Entity alias - /// - public string[] Aliases - { - get { return new[] {_entityAlias}; } - } + string[] IProjection.Aliases => new[] { _entityAlias }; - bool IProjection.IsAggregate - { - get { return false; } - } + bool IProjection.IsAggregate => false; - bool IProjection.IsGrouped - { - get { return false; } - } + bool IProjection.IsGrouped => false; IType[] IProjection.GetTypes(string alias, ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new[] {criteriaQuery.GetType(criteria, alias)}; + return new[] { criteriaQuery.GetType(criteria, alias) }; } IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) @@ -115,19 +97,18 @@ string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteri { SetFields(criteria, criteriaQuery); - return IdentifierColumnAliases; + return _identifierColumnAliases; } string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { SetFields(criteria, criteriaQuery); - return IdentifierColumnAliases; + return _identifierColumnAliases; } SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) { - //return new SqlString(string.Empty); SetFields(criteria, criteriaQuery); string identifierSelectFragment = Persister.IdentifierSelectFragment(TableAlias, ColumnAliasSuffix); @@ -141,12 +122,12 @@ SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQue SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - throw new NotImplementedException(); + throw new InvalidOperationException("not a grouping projection"); } TypedValue[] IProjection.GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) { - throw new NotImplementedException(); + return Array.Empty(); } #endregion IProjection implementation @@ -192,9 +173,9 @@ private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery) ColumnAliasSuffix = BasicLoader.GenerateSuffix(criteriaQuery.GetIndexForAlias()); - IdentifierColumnAliases = Persister.GetIdentifierAliases(ColumnAliasSuffix); + _identifierColumnAliases = Persister.GetIdentifierAliases(ColumnAliasSuffix); - _types = new IType[] {TypeFactory.ManyToOne(Persister.EntityName, true),}; + _types = new IType[] { TypeFactory.ManyToOne(Persister.EntityName, true), }; } } } diff --git a/src/NHibernate/Criterion/ProjectionsExtensions.cs b/src/NHibernate/Criterion/ProjectionsExtensions.cs index 066082d622a..e0c09363479 100644 --- a/src/NHibernate/Criterion/ProjectionsExtensions.cs +++ b/src/NHibernate/Criterion/ProjectionsExtensions.cs @@ -25,7 +25,7 @@ public static IProjection WithAlias(this IProjection projection, /// Create an alias for a projection /// /// the projection instance - /// LambdaExpression returning an alias + /// alias /// return NHibernate.Criterion.IProjection public static IProjection WithAlias(this IProjection projection, string alias) { diff --git a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs index 475388ed01e..30fbeca0eae 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using NHibernate.Criterion; using NHibernate.Engine; using NHibernate.Loader.Criteria; @@ -40,26 +39,37 @@ protected virtual void InitAll(SqlString whereString, SqlString orderByString, L InitStatementString(whereString, orderByString, lockMode); } - protected void InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary enabledFilters, LockMode lockMode, IList entityProjections = null) + //Since v5.1 + [Obsolete("Please use InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary enabledFilters, LockMode lockMode, IList entityProjections) instead")] + protected void InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary enabledFilters, LockMode lockMode) + { + InitProjection(projectionString, whereString, orderByString, groupByString, havingString, enabledFilters, lockMode, Array.Empty()); + } + + protected void InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary enabledFilters, LockMode lockMode, IList entityProjections) { WalkEntityTree(persister, Alias); - if (entityProjections?.Count > 0) + + int countEntities = entityProjections.Count; + if (countEntities > 0) { - var list = new List(); - var eagerProps = new bool[entityProjections.Count]; - for (var i = 0; i < entityProjections.Count; i++) + var associations = new OuterJoinableAssociation[countEntities]; + var eagerProps = new bool[countEntities]; + var suffixes = new string[countEntities]; + for (var i = 0; i < countEntities; i++) { var e = entityProjections[i]; - list.Add( - CreateAssociation(e.Persister.EntityMetamodel.EntityType, e.TableAlias)); + associations[i] = CreateAssociation(e.Persister.EntityMetamodel.EntityType, e.TableAlias); if (e.FetchLazyProperties) { eagerProps[i] = true; } + suffixes[i] = e.ColumnAliasSuffix; } - InitPersisters(list, lockMode); - Suffixes = entityProjections.Select(ep => ep.ColumnAliasSuffix).ToArray(); + InitPersisters(associations, lockMode); + + Suffixes = suffixes; EagerPropertyFetches = eagerProps; } else @@ -69,7 +79,6 @@ protected void InitProjection(SqlString projectionString, SqlString whereString, } InitStatementString(projectionString, whereString, orderByString, groupByString, havingString, lockMode); - } private OuterJoinableAssociation CreateAssociation(EntityType entityType, string tableAlias) diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index fdc02627f5d..d5f4d49de43 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -26,7 +26,7 @@ public class CriteriaQueryTranslator : ICriteriaQuery, ISupportEntityProjectionC private readonly string rootEntityName; private readonly string rootSQLAlias; private int indexForAlias = 0; - private IList _entityProjections = new List(); + private readonly List entityProjections = new List(); private readonly IDictionary criteriaInfoMap = new Dictionary(); @@ -46,7 +46,6 @@ public class CriteriaQueryTranslator : ICriteriaQuery, ISupportEntityProjectionC private readonly ICollection collectedParameterSpecifications; private readonly ICollection namedParameters; private readonly ISet subQuerySpaces = new HashSet(); - public IList entityProjections = new List(); @@ -195,12 +194,12 @@ public string[] ProjectedAliases public IList GetEntityProjections() { - return _entityProjections; + return entityProjections; } public void RegisterEntityProjection(EntityProjection projection) { - _entityProjections.Add(projection); + entityProjections.Add(projection); } public SqlString GetWhereCondition() @@ -878,6 +877,6 @@ private void CreateSubQuerySpaces() subQuerySpaces.UnionWith(translator.GetQuerySpaces()); } - } + } } -} +} \ No newline at end of file diff --git a/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs b/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs index bf1cd3910a3..da17ee21a2f 100644 --- a/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs +++ b/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs @@ -2,8 +2,9 @@ namespace NHibernate.Loader.Criteria { + // 6.0 TODO: merge into 'ICriteriaQuery'. public interface ISupportEntityProjectionCriteriaQuery { void RegisterEntityProjection(EntityProjection projection); } -} \ No newline at end of file +} From dae9cb12aa179a1780729300f97b49212fd5fe78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Wed, 20 Dec 2017 12:57:54 +0100 Subject: [PATCH 06/11] Adjustment to entity projection initialization. To be squashed. --- src/NHibernate/Criterion/EntityProjection.cs | 30 +++++++++++-------- .../Criteria/CriteriaQueryTranslator.cs | 4 ++- .../ISupportEntityProjectionCriteriaQuery.cs | 2 ++ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index 38c2adee7f7..45784c17ba9 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -83,33 +83,36 @@ public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) IType[] IProjection.GetTypes(string alias, ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new[] { criteriaQuery.GetType(criteria, alias) }; + SetFields(criteriaQuery); + return _entityAlias == alias + ? _types + : null; } IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) { - SetFields(criteria, criteriaQuery); + SetFields(criteriaQuery); return _types; } string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { - SetFields(criteria, criteriaQuery); + SetFields(criteriaQuery); return _identifierColumnAliases; } string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { - SetFields(criteria, criteriaQuery); + SetFields(criteriaQuery); return _identifierColumnAliases; } SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) { - SetFields(criteria, criteriaQuery); + SetFields(criteriaQuery); string identifierSelectFragment = Persister.IdentifierSelectFragment(TableAlias, ColumnAliasSuffix); return new SqlString( @@ -132,20 +135,21 @@ TypedValue[] IProjection.GetTypedValues(ICriteria criteria, ICriteriaQuery crite #endregion IProjection implementation - private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery) + private void SetFields(ICriteriaQuery criteriaQuery) { //Persister is required, so let's use it as "initialized marker" if (Persister != null) return; - if (Lazy == false) + if (!(criteriaQuery is ISupportEntityProjectionCriteriaQuery entityProjectionQuery)) { - var entityProjectionQuery = criteriaQuery as ISupportEntityProjectionCriteriaQuery; - if (entityProjectionQuery == null) - { - throw new HibernateException($"Projecting to entities requires a '{criteriaQuery.GetType().FullName}' type to implement {nameof(ISupportEntityProjectionCriteriaQuery)} interface."); - } + throw new HibernateException($"Projecting to entities requires a '{criteriaQuery.GetType().FullName}' type to implement {nameof(ISupportEntityProjectionCriteriaQuery)} interface."); + } + + var criteria = entityProjectionQuery.RootCriteria; + if (!Lazy) + { entityProjectionQuery.RegisterEntityProjection(this); } @@ -175,7 +179,7 @@ private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery) _identifierColumnAliases = Persister.GetIdentifierAliases(ColumnAliasSuffix); - _types = new IType[] { TypeFactory.ManyToOne(Persister.EntityName, true), }; + _types = new IType[] { TypeFactory.ManyToOne(Persister.EntityName, true) }; } } } diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index d5f4d49de43..2c6b864de89 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -113,6 +113,8 @@ public CriteriaImpl RootCriteria get { return rootCriteria; } } + ICriteria ISupportEntityProjectionCriteriaQuery.RootCriteria => rootCriteria; + public QueryParameters GetQueryParameters() { RowSelection selection = new RowSelection(); @@ -879,4 +881,4 @@ private void CreateSubQuerySpaces() } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs b/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs index da17ee21a2f..71e146a5785 100644 --- a/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs +++ b/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs @@ -6,5 +6,7 @@ namespace NHibernate.Loader.Criteria public interface ISupportEntityProjectionCriteriaQuery { void RegisterEntityProjection(EntityProjection projection); + + ICriteria RootCriteria { get; } } } From 2c1d77d3d7450015fa5cf75096efa9d90b26a5da Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 20 Dec 2017 22:21:42 +0530 Subject: [PATCH 07/11] Do not interfere with alias resolution --- src/NHibernate/Criterion/EntityProjection.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index 45784c17ba9..0bea9f3db8d 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -83,27 +83,22 @@ public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) IType[] IProjection.GetTypes(string alias, ICriteria criteria, ICriteriaQuery criteriaQuery) { - SetFields(criteriaQuery); - return _entityAlias == alias - ? _types - : null; + return null; } - IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) + string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { - SetFields(criteriaQuery); - - return _types; + return null; } - string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) { SetFields(criteriaQuery); - return _identifierColumnAliases; + return _types; } - string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { SetFields(criteriaQuery); From 36e8ff268d5abdc1c4338e2be72db1e7de61de1d Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 21 Dec 2017 11:18:57 +0530 Subject: [PATCH 08/11] Skip LockMode test for Oracle due to NH-3902 --- src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs | 4 ++++ src/NHibernate.Test/Criteria/EntityProjectionsTest.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs index 70bb59eb222..f41fa2f6491 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs @@ -10,6 +10,7 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Criterion; +using NHibernate.Dialect; using NHibernate.Mapping.ByCode; using NHibernate.SqlCommand; using NHibernate.Transform; @@ -213,6 +214,9 @@ public async Task EntityProjectionAsSelectExpressionAsync() [Test] public async Task EntityProjectionLockModeAsync() { + if (Dialect is Oracle8iDialect) + Assert.Ignore("Oracle is not supported due to #1352 bug (NH-3902)"); + var upgradeHint = Dialect.ForUpdateString; if(string.IsNullOrEmpty(upgradeHint)) upgradeHint = this.Dialect.AppendLockHint(LockMode.Upgrade, string.Empty); diff --git a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs index eb3570e6fd6..3d5eaf3d403 100644 --- a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs @@ -1,5 +1,6 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Criterion; +using NHibernate.Dialect; using NHibernate.Mapping.ByCode; using NHibernate.SqlCommand; using NHibernate.Transform; @@ -202,6 +203,9 @@ public void EntityProjectionAsSelectExpression() [Test] public void EntityProjectionLockMode() { + if (Dialect is Oracle8iDialect) + Assert.Ignore("Oracle is not supported due to #1352 bug (NH-3902)"); + var upgradeHint = Dialect.ForUpdateString; if(string.IsNullOrEmpty(upgradeHint)) upgradeHint = this.Dialect.AppendLockHint(LockMode.Upgrade, string.Empty); From 9ece50d6c9f1388750d4d450c2852ce614691b75 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 21 Dec 2017 15:28:56 +0530 Subject: [PATCH 09/11] small code refactoring --- src/NHibernate/Criterion/EntityProjection.cs | 16 ++++++++-------- src/NHibernate/Criterion/Projections.cs | 2 +- .../Criterion/ProjectionsExtensions.cs | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index 0bea9f3db8d..c7c083f3b2f 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -15,7 +15,7 @@ namespace NHibernate.Criterion public class EntityProjection : IProjection { private string _entityAlias; - private System.Type _rootEntity; + private System.Type _entityType; private IType[] _types; private string[] _identifierColumnAliases; @@ -29,11 +29,11 @@ public EntityProjection() : this(null, null) /// /// Entity projection for given type and alias /// - /// Type of entity + /// Type of entity /// Entity alias - public EntityProjection(System.Type rootEntity, string entityAlias) + public EntityProjection(System.Type entityType, string entityAlias) { - _rootEntity = rootEntity; + _entityType = entityType; _entityAlias = entityAlias; } @@ -148,9 +148,9 @@ private void SetFields(ICriteriaQuery criteriaQuery) entityProjectionQuery.RegisterEntityProjection(this); } - if (_rootEntity == null) + if (_entityType == null) { - _rootEntity = criteria.GetRootEntityTypeIfAvailable(); + _entityType = criteria.GetRootEntityTypeIfAvailable(); } if (_entityAlias == null) @@ -158,9 +158,9 @@ private void SetFields(ICriteriaQuery criteriaQuery) _entityAlias = criteria.Alias; } - Persister = criteriaQuery.Factory.GetEntityPersister(_rootEntity.FullName) as IQueryable; + Persister = criteriaQuery.Factory.GetEntityPersister(_entityType.FullName) as IQueryable; if (Persister == null) - throw new HibernateException($"Projecting to entities requires a '{typeof(IQueryable).FullName}' persister, '{_rootEntity.FullName}' does not have one."); + throw new HibernateException($"Projecting to entities requires a '{typeof(IQueryable).FullName}' persister, '{_entityType.FullName}' does not have one."); ICriteria subcriteria = criteria.GetCriteriaByAlias(_entityAlias); if (subcriteria == null) diff --git a/src/NHibernate/Criterion/Projections.cs b/src/NHibernate/Criterion/Projections.cs index ec3be880ead..db71d394606 100644 --- a/src/NHibernate/Criterion/Projections.cs +++ b/src/NHibernate/Criterion/Projections.cs @@ -48,7 +48,7 @@ public static EntityProjection Entity(string alias) } /// - /// Returns an aliased entity. + /// Projection for entity with given alias. /// /// /// The type of the entity. /// The alias of the entity. diff --git a/src/NHibernate/Criterion/ProjectionsExtensions.cs b/src/NHibernate/Criterion/ProjectionsExtensions.cs index e0c09363479..7ff946b069f 100644 --- a/src/NHibernate/Criterion/ProjectionsExtensions.cs +++ b/src/NHibernate/Criterion/ProjectionsExtensions.cs @@ -347,7 +347,7 @@ internal static IProjection ProcessAsEntity(MethodCallExpression methodCallExpre { var expression = methodCallExpression.Arguments[0]; var aliasName = ExpressionProcessor.FindMemberExpression(expression); - return new EntityProjection(expression.Type, aliasName); + return Projections.Entity(expression.Type, aliasName); } } } From bb0cc4584e54b0c23db7d7ff3ea2200ac471ff59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Thu, 21 Dec 2017 16:17:25 +0100 Subject: [PATCH 10/11] Adjustments to assert messages. --- .../Async/Criteria/EntityProjectionsTest.cs | 32 +++++++++---------- .../Criteria/EntityProjectionsTest.cs | 32 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs index f41fa2f6491..1b3a894bc4e 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs @@ -271,14 +271,14 @@ public async Task MultipleLazyEntityProjectionsAsync() sameTypeChild = (EntityComplex) result[2]; child2 = (EntitySimpleChild) result[3]; - Assert.That(NHibernateUtil.IsInitialized(root), Is.False, "Object must be lazy loaded."); - Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "Object must be lazy loaded."); - Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "Object must be lazy loaded."); - Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(root), Is.False, "root must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "sameTypeChild must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "child1 must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "child2 must be lazy loaded."); //make sure objects are populated from different aliases for the same types - Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); - Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected."); + Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected for root and sameTypeChild."); + Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected for child1 and child2."); } } @@ -349,11 +349,11 @@ public async Task MultipleEntitiesProjectionsAsync() sameAsRootChild = (EntityComplex) objects[3]; nullListElem = (EntitySimpleChild) objects[4]; - Assert.That(NHibernateUtil.IsInitialized(root), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(child2), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(sameAsRootChild), Is.True, "Object must be initialized"); - Assert.That(nullListElem, Is.Null, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True, "root must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "child1 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.True, "child2 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(sameAsRootChild), Is.True, "sameAsRootChild must be initialized"); + Assert.That(nullListElem, Is.Null, "nullListElem must be null"); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } } @@ -400,11 +400,11 @@ public async Task MultipleEntitiesProjectionsToResultTransformerAsync() .Take(1) .SingleOrDefaultAsync()); - Assert.That(NHibernateUtil.IsInitialized(r.Root), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(r.Child1), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(r.Child2), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(r.SameAsRootChild), Is.True, "Object must be initialized"); - Assert.That(r.NullListElem, Is.Null, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Root), Is.True, "Root must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child1), Is.True, "Child1 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child2), Is.True, "Child2 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.SameAsRootChild), Is.True, "SameAsRootChild must be initialized"); + Assert.That(r.NullListElem, Is.Null, "NullListElem must be null"); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } } diff --git a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs index 3d5eaf3d403..7c75db331c2 100644 --- a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs @@ -260,14 +260,14 @@ public void MultipleLazyEntityProjections() sameTypeChild = (EntityComplex) result[2]; child2 = (EntitySimpleChild) result[3]; - Assert.That(NHibernateUtil.IsInitialized(root), Is.False, "Object must be lazy loaded."); - Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "Object must be lazy loaded."); - Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "Object must be lazy loaded."); - Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "Object must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(root), Is.False, "root must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(sameTypeChild), Is.False, "sameTypeChild must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.False, "child1 must be lazy loaded."); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.False, "child2 must be lazy loaded."); //make sure objects are populated from different aliases for the same types - Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected."); - Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected."); + Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected for root and sameTypeChild."); + Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected for child1 and child2."); } } @@ -338,11 +338,11 @@ public void MultipleEntitiesProjections() sameAsRootChild = (EntityComplex) objects[3]; nullListElem = (EntitySimpleChild) objects[4]; - Assert.That(NHibernateUtil.IsInitialized(root), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(child2), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(sameAsRootChild), Is.True, "Object must be initialized"); - Assert.That(nullListElem, Is.Null, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True, "root must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "child1 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(child2), Is.True, "child2 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(sameAsRootChild), Is.True, "sameAsRootChild must be initialized"); + Assert.That(nullListElem, Is.Null, "nullListElem must be null"); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } } @@ -389,11 +389,11 @@ public void MultipleEntitiesProjectionsToResultTransformer() .Take(1) .SingleOrDefault(); - Assert.That(NHibernateUtil.IsInitialized(r.Root), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(r.Child1), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(r.Child2), Is.True, "Object must be initialized"); - Assert.That(NHibernateUtil.IsInitialized(r.SameAsRootChild), Is.True, "Object must be initialized"); - Assert.That(r.NullListElem, Is.Null, "Object must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Root), Is.True, "Root must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child1), Is.True, "Child1 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.Child2), Is.True, "Child2 must be initialized"); + Assert.That(NHibernateUtil.IsInitialized(r.SameAsRootChild), Is.True, "SameAsRootChild must be initialized"); + Assert.That(r.NullListElem, Is.Null, "NullListElem must be null"); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } } From fe3aa9d23b3896e9cddb8851619986e3c5fdfc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 23 Dec 2017 13:12:45 +0100 Subject: [PATCH 11/11] Adjust exceptions, to be squashed. --- src/NHibernate/Criterion/EntityProjection.cs | 5 ++++- src/NHibernate/Criterion/ProjectionsExtensions.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index c7c083f3b2f..6f1cb79949e 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -138,7 +138,10 @@ private void SetFields(ICriteriaQuery criteriaQuery) if (!(criteriaQuery is ISupportEntityProjectionCriteriaQuery entityProjectionQuery)) { - throw new HibernateException($"Projecting to entities requires a '{criteriaQuery.GetType().FullName}' type to implement {nameof(ISupportEntityProjectionCriteriaQuery)} interface."); + throw new ArgumentException( + $"Projecting to entities requires a '{criteriaQuery.GetType().FullName}' type to implement " + + $"{nameof(ISupportEntityProjectionCriteriaQuery)} interface.", + nameof(criteriaQuery)); } var criteria = entityProjectionQuery.RootCriteria; diff --git a/src/NHibernate/Criterion/ProjectionsExtensions.cs b/src/NHibernate/Criterion/ProjectionsExtensions.cs index d614f9b9aeb..268494c9359 100644 --- a/src/NHibernate/Criterion/ProjectionsExtensions.cs +++ b/src/NHibernate/Criterion/ProjectionsExtensions.cs @@ -340,7 +340,7 @@ internal static IProjection ProcessMod(MethodCallExpression methodCallExpression /// public static T AsEntity(this T alias) where T:class { - throw new Exception("Not to be used directly - use inside QueryOver expression"); + throw QueryOver.GetDirectUsageException(); } internal static IProjection ProcessAsEntity(MethodCallExpression methodCallExpression)