diff --git a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs new file mode 100644 index 00000000000..1b3a894bc4e --- /dev/null +++ b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs @@ -0,0 +1,463 @@ +//------------------------------------------------------------------------------ +// +// 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.Dialect; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria +{ + using System.Threading.Tasks; + /// + /// GH948 + /// + [TestFixture] + 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)); + + 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 entityRoot = await (session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity()) + .Take(1).SingleOrDefaultAsync()); + + 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"); + } + } + + [Test] + public async Task RootEntityProjectionLazyAsync() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot = await (session + .QueryOver() + .Select(Projections.RootEntity().SetLazy(true)) + .Take(1).SingleOrDefaultAsync()); + + + Assert.That(NHibernateUtil.IsInitialized(entityRoot), 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 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() + { + 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); + 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() + { + 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, "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 for root and sameTypeChild."); + Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected for child1 and child2."); + + } + } + + [Test] + public async Task EntityProjectionWithLazyPropertiesFetchedAsync() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot = await (session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity().SetFetchLazyProperties(true)) + .Take(1).SingleOrDefaultAsync()); + + 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"); + } + } + + [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, "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"); + } + } + + 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; } + public string Name { get; set; } + } + + [Test] + public async Task MultipleEntitiesProjectionsToResultTransformerAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + MultipleEntitiesResult r = null; + + EntitySimpleChild child1 = null; + EntitySimpleChild child2 = null; + EntityComplex sameAsRootChild = null; + 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.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) + ) + .TransformUsing(Transformers.AliasToBean()) + .Take(1) + .SingleOrDefaultAsync()); + + 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"); + } + } + + + [Test] + public async Task ReadOnlyProjectionAsync() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot = await (session + .QueryOver() + .Select(Projections.RootEntity()) + .ReadOnly() + .Take(1).SingleOrDefaultAsync()); + + Assert.That(session.IsReadOnly(entityRoot), 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/Criteria/EntityProjectionsEntities.cs b/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs new file mode 100644 index 00000000000..d4d5edcd415 --- /dev/null +++ b/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.Criteria +{ + 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/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs new file mode 100644 index 00000000000..7c75db331c2 --- /dev/null +++ b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs @@ -0,0 +1,452 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria +{ + /// + /// GH948 + /// + [TestFixture] + 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)); + + 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 entityRoot = session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity()) + .Take(1).SingleOrDefault(); + + 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"); + } + } + + [Test] + public void RootEntityProjectionLazy() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot = session + .QueryOver() + .Select(Projections.RootEntity().SetLazy(true)) + .Take(1).SingleOrDefault(); + + + Assert.That(NHibernateUtil.IsInitialized(entityRoot), 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 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() + { + 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); + 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() + { + 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, "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 for root and sameTypeChild."); + Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected for child1 and child2."); + + } + } + + [Test] + public void EntityProjectionWithLazyPropertiesFetched() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot = session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity().SetFetchLazyProperties(true)) + .Take(1).SingleOrDefault(); + + 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"); + } + } + + [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, "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"); + } + } + + 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; } + public string Name { get; set; } + } + + [Test] + public void MultipleEntitiesProjectionsToResultTransformer() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + MultipleEntitiesResult r = null; + + EntitySimpleChild child1 = null; + EntitySimpleChild child2 = null; + EntityComplex sameAsRootChild = null; + 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.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) + ) + .TransformUsing(Transformers.AliasToBean()) + .Take(1) + .SingleOrDefault(); + + 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"); + } + } + + + [Test] + public void ReadOnlyProjection() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot = session + .QueryOver() + .Select(Projections.RootEntity()) + .ReadOnly() + .Take(1).SingleOrDefault(); + + Assert.That(session.IsReadOnly(entityRoot), 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/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs new file mode 100644 index 00000000000..6f1cb79949e --- /dev/null +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -0,0 +1,183 @@ +using System; +using NHibernate.Engine; +using NHibernate.Loader; +using NHibernate.Loader.Criteria; +using NHibernate.SqlCommand; +using NHibernate.Type; +using IQueryable = NHibernate.Persister.Entity.IQueryable; + +namespace NHibernate.Criterion +{ + /// + /// Entity projection + /// + [Serializable] + public class EntityProjection : IProjection + { + private string _entityAlias; + private System.Type _entityType; + private IType[] _types; + private string[] _identifierColumnAliases; + + /// + /// Root entity projection + /// + public EntityProjection() : this(null, null) + { + } + + /// + /// Entity projection for given type and alias + /// + /// Type of entity + /// Entity alias + public EntityProjection(System.Type entityType, string entityAlias) + { + _entityType = entityType; + _entityAlias = entityAlias; + } + + /// + /// Fetch lazy properties + /// + public bool FetchLazyProperties { get; set; } + + /// + /// Lazy load entity + /// + public bool Lazy { get; set; } + + internal IQueryable Persister { 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; + return this; + } + + /// + /// Fetch lazy properties + /// + public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) + { + FetchLazyProperties = fetchLazyProperties; + return this; + } + + #endregion Configuration methods + + #region IProjection implementation + + string[] IProjection.Aliases => new[] { _entityAlias }; + + bool IProjection.IsAggregate => false; + + bool IProjection.IsGrouped => false; + + IType[] IProjection.GetTypes(string alias, ICriteria criteria, ICriteriaQuery criteriaQuery) + { + return null; + } + + string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + { + return null; + } + + IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + SetFields(criteriaQuery); + + return _types; + } + + string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery) + { + SetFields(criteriaQuery); + + return _identifierColumnAliases; + } + + SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) + { + SetFields(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 InvalidOperationException("not a grouping projection"); + } + + TypedValue[] IProjection.GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + return Array.Empty(); + } + + #endregion IProjection implementation + + private void SetFields(ICriteriaQuery criteriaQuery) + { + //Persister is required, so let's use it as "initialized marker" + if (Persister != null) + return; + + if (!(criteriaQuery is ISupportEntityProjectionCriteriaQuery entityProjectionQuery)) + { + throw new ArgumentException( + $"Projecting to entities requires a '{criteriaQuery.GetType().FullName}' type to implement " + + $"{nameof(ISupportEntityProjectionCriteriaQuery)} interface.", + nameof(criteriaQuery)); + } + + var criteria = entityProjectionQuery.RootCriteria; + + if (!Lazy) + { + entityProjectionQuery.RegisterEntityProjection(this); + } + + if (_entityType == null) + { + _entityType = criteria.GetRootEntityTypeIfAvailable(); + } + + if (_entityAlias == null) + { + _entityAlias = criteria.Alias; + } + + Persister = criteriaQuery.Factory.GetEntityPersister(_entityType.FullName) as IQueryable; + if (Persister == null) + 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) + throw new HibernateException($"Criteria\\QueryOver alias '{_entityAlias}' for entity projection is not found."); + + TableAlias = criteriaQuery.GetSQLAlias( + subcriteria, + Persister.IdentifierPropertyName ?? string.Empty); + + ColumnAliasSuffix = BasicLoader.GenerateSuffix(criteriaQuery.GetIndexForAlias()); + + _identifierColumnAliases = Persister.GetIdentifierAliases(ColumnAliasSuffix); + + _types = new IType[] { TypeFactory.ManyToOne(Persister.EntityName, true) }; + } + } +} diff --git a/src/NHibernate/Criterion/Projections.cs b/src/NHibernate/Criterion/Projections.cs index 0c4ccafc380..5cb6b7b3d6e 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); + } + + /// + /// Projection for entity with given alias. + /// + /// /// 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 /// diff --git a/src/NHibernate/Criterion/ProjectionsExtensions.cs b/src/NHibernate/Criterion/ProjectionsExtensions.cs index 4e091a28588..268494c9359 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 + /// 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 QueryOver.GetDirectUsageException(); + } + + internal static IProjection ProcessAsEntity(MethodCallExpression methodCallExpression) + { + var expression = methodCallExpression.Arguments[0]; + var aliasName = ExpressionProcessor.FindMemberExpression(expression); + return Projections.Entity(expression.Type, aliasName); + } } } diff --git a/src/NHibernate/Impl/ExpressionProcessor.cs b/src/NHibernate/Impl/ExpressionProcessor.cs index 7e36c1988ae..cb4a87f1ab1 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..30fbeca0eae 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NHibernate.Criterion; using NHibernate.Engine; using NHibernate.Loader.Criteria; using NHibernate.Persister.Entity; @@ -32,35 +33,85 @@ 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); } + //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); - Persisters = Array.Empty(); + + int countEntities = entityProjections.Count; + if (countEntities > 0) + { + 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]; + associations[i] = CreateAssociation(e.Persister.EntityMetamodel.EntityType, e.TableAlias); + if (e.FetchLazyProperties) + { + eagerProps[i] = true; + } + suffixes[i] = e.ColumnAliasSuffix; + } + + InitPersisters(associations, lockMode); + + Suffixes = suffixes; + 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) { 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..2c6b864de89 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 readonly List entityProjections = new List(); private readonly IDictionary criteriaInfoMap = new Dictionary(); @@ -112,6 +113,8 @@ public CriteriaImpl RootCriteria get { return rootCriteria; } } + ICriteria ISupportEntityProjectionCriteriaQuery.RootCriteria => rootCriteria; + public QueryParameters GetQueryParameters() { RowSelection selection = new RowSelection(); @@ -191,6 +194,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); @@ -868,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 new file mode 100644 index 00000000000..71e146a5785 --- /dev/null +++ b/src/NHibernate/Loader/Criteria/ISupportEntityProjectionCriteriaQuery.cs @@ -0,0 +1,12 @@ +using NHibernate.Criterion; + +namespace NHibernate.Loader.Criteria +{ + // 6.0 TODO: merge into 'ICriteriaQuery'. + public interface ISupportEntityProjectionCriteriaQuery + { + void RegisterEntityProjection(EntityProjection projection); + + ICriteria RootCriteria { get; } + } +} 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 +}