From a49c7392f68ea710bb2476c2cf63bac3849686cd Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 20 Jan 2018 16:35:14 +0530 Subject: [PATCH 1/8] Support to join not associated entities in Criteria API (aka Entity Join) --- .../Async/Criteria/EntityJoinCriteriaTest.cs | 431 +++++++++++++++++ .../Criteria/EntityJoinCriteriaTest.cs | 433 ++++++++++++++++++ src/NHibernate/Async/Impl/CriteriaImpl.cs | 2 +- .../Criterion/ISupportEntityJoinQueryOver.cs | 17 + src/NHibernate/Criterion/QueryOver.cs | 28 +- src/NHibernate/EntityJoinExtensions.cs | 44 ++ src/NHibernate/Impl/CriteriaImpl.cs | 23 +- .../Impl/ISupportEntityJoinCriteria.cs | 11 + .../Loader/AbstractEntityJoinWalker.cs | 9 +- .../Loader/Criteria/CriteriaJoinWalker.cs | 46 +- .../Criteria/CriteriaQueryTranslator.cs | 106 +++-- src/NHibernate/Loader/JoinWalker.cs | 19 + .../Loader/OuterJoinableAssociation.cs | 9 +- src/NHibernate/SqlCommand/ANSIJoinFragment.cs | 9 +- src/NHibernate/SqlCommand/JoinFragment.cs | 4 +- 15 files changed, 1140 insertions(+), 51 deletions(-) create mode 100644 src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs create mode 100644 src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs create mode 100644 src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs create mode 100644 src/NHibernate/EntityJoinExtensions.cs create mode 100644 src/NHibernate/Impl/ISupportEntityJoinCriteria.cs diff --git a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs new file mode 100644 index 00000000000..a7941c5a175 --- /dev/null +++ b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs @@ -0,0 +1,431 @@ +//------------------------------------------------------------------------------ +// +// 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 NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria +{ + using System.Threading.Tasks; + /// + /// Tests for explicit entity joins (not associated entities) + /// + [TestFixture] + public class EntityJoinCriteriaTestAsync : TestCaseMappingByCode + { + private const string customEntityName = "CustomEntityName"; + private EntityWithCompositeId _entityWithCompositeId; + private EntityWithNoAssociation _noAssociation; + private EntityCustomEntityName _entityWithCustomEntityName; + + //check JoinEntityAlias - JoinAlias analog for entity join + [Test] + public async Task CanJoinNotAssociatedEntityAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + .JoinEntityAlias(() => entityComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == entityComplex.Id)).Take(1) + .SingleOrDefaultAsync()); + entityComplex = await (session.LoadAsync(root.Complex1Id)); + + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + //check JoinEntityQueryOver - JoinQueryOver analog for entity join + [Test] + public async Task CanJoinEntityQueryOverAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejQueryOver = null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + .JoinEntityQueryOver( ()=> ejQueryOver, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id)) + .Take(1) + .SingleOrDefaultAsync()); + ejQueryOver = await (session.LoadAsync(root.Complex1Id)); + + Assert.That(NHibernateUtil.IsInitialized(ejQueryOver), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + //can join not associated entity and join associated entities for it via JoinQueryOver + [Test] + public async Task CanQueryOverForAssociationInNotAssociatedEntityAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejComplex = null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + .JoinEntityQueryOver( ()=> ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .JoinQueryOver(ej => ej.Child1) + .Take(1) + .SingleOrDefaultAsync()); + + ejComplex = await (session.LoadAsync(root.Complex1Id)); + + Assert.That(NHibernateUtil.IsInitialized(ejComplex), Is.True); + Assert.That(NHibernateUtil.IsInitialized(ejComplex.Child1), Is.True); + Assert.That(NHibernateUtil.IsInitialized(ejComplex.Child2), Is.False); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task SimpleProjectionForEntityJoinAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejComplex = null; + EntityWithNoAssociation root = null; + var name = await (session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .Select((e) => ejComplex.Name) + .Take(1) + .SingleOrDefaultAsync()); + + Assert.That(name, Is.Not.Empty); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task EntityProjectionForEntityJoinAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild ejChild1 = null; + + EntityComplex root = null; + EntityComplex st = null; + var r = await (session + .QueryOver(() => root) + .JoinAlias(c => c.SameTypeChild, () => st) + .JoinEntityAlias(() => ejChild1, JoinType.InnerJoin, Restrictions.Where(() => ejChild1.Id == root.Child1.Id)) + .Select( + Projections.RootEntity(), + Projections.Entity(() => st), + Projections.Entity(() => ejChild1) + ) + .SingleOrDefaultAsync()); + var rootObj = (EntityComplex) r[0]; + var mappedObj = (EntityComplex) r[1]; + var entityJoinObj = (EntitySimpleChild) r[2]; + + Assert.That(rootObj, Is.Not.Null); + Assert.That(mappedObj, Is.Not.Null); + Assert.That(entityJoinObj, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(rootObj), Is.True); + Assert.That(NHibernateUtil.IsInitialized(mappedObj), Is.True); + Assert.That(NHibernateUtil.IsInitialized(entityJoinObj), Is.True); + } + } + + //just check that it can be executed without error + [Test] + public async Task MixOfJoinsForAssociatedAndNotAssociatedEntitiesAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + + EntityComplex root = null; + EntityComplex ejLevel1 = null; + EntitySimpleChild customChildForEjLevel1 = null; + EntityComplex entityComplexForEjLevel1 = null; + EntitySimpleChild ejLevel2OnEntityComplexForEjLevel1 = null; + var obj = await (session + .QueryOver(() => root) + .JoinEntityAlias(() => ejLevel1, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null)) + .JoinAlias(() => ejLevel1.Child1, () => customChildForEjLevel1, JoinType.InnerJoin) + .JoinAlias(() => ejLevel1.SameTypeChild, () => entityComplexForEjLevel1, JoinType.LeftOuterJoin) + .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, JoinType.InnerJoin, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id)) + .Where(() => customChildForEjLevel1.Id != null && ejLevel2OnEntityComplexForEjLevel1.Id != null) + .Take(1) + .SingleOrDefaultAsync()); + } + } + + [Test] + public async Task EntityJoinForCompositeKeyAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityWithCompositeId ejComposite = null; + EntityWithNoAssociation root = null; + root = await (session + .QueryOver(() => root) + .JoinEntityAlias(() => ejComposite, JoinType.InnerJoin, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2 )) + .Take(1).SingleOrDefaultAsync()); + var composite = await (session.LoadAsync(_entityWithCompositeId.Key)); + + 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 NullLeftEntityJoinAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejLeftNull = null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + //add some non existent + .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .Take(1) + .SingleOrDefaultAsync()); + + Assert.That(root, Is.Not.Null, "root should not be null (looks like left join didn't work)"); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task NullLeftEntityJoinWithEntityProjectionAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejLeftNull = null; + EntityWithNoAssociation root = null; + var objs = await (session.QueryOver(() => root) + //add some non existent + .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .Select((e) => root.AsEntity(), e => ejLeftNull.AsEntity()) + .Take(1) + .SingleOrDefaultAsync()); + root = (EntityWithNoAssociation) objs[0]; + ejLeftNull = (EntityComplex) objs[1]; + + Assert.That(root, Is.Not.Null, "root should not be null (looks like left join didn't work)"); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(ejLeftNull, Is.Null, "Entity join should be null"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task EntityJoinForCustomEntityNameAsync() + { + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityCustomEntityName ejCustomEntity= null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + .JoinEntityAlias(() => ejCustomEntity, JoinType.InnerJoin, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), customEntityName) + .Take(1) + .SingleOrDefaultAsync()); + + ejCustomEntity = await (session.LoadAsync(root.CustomEntityNameId)); + + Assert.That(NHibernateUtil.IsInitialized(ejCustomEntity), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + #region Test Setup + + 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); + }); + + mapper.Class( + rc => + { + rc.ComponentAsId( + e => e.Key, + ekm => + { + ekm.Property(ek => ek.Id1); + ekm.Property(ek => ek.Id2); + }); + + rc.Property(e => e.Name); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + + rc.Property(e => e.Complex1Id); + rc.Property(e => e.Complex2Id); + rc.Property(e => e.Simple1Id); + rc.Property(e => e.Simple2Id); + rc.Property(e => e.Composite1Key1); + rc.Property(e => e.Composite1Key2); + rc.Property(e => e.CustomEntityNameId); + + }); + + mapper.Class( + rc => + { + rc.EntityName(customEntityName); + + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + 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); + + _entityWithCustomEntityName = new EntityCustomEntityName() + { + Name = "EntityCustomEntityName" + }; + + session.Save(customEntityName, _entityWithCustomEntityName); + + _noAssociation = new EntityWithNoAssociation() + { + Complex1Id = parent.Id, + Complex2Id = parent.SameTypeChild.Id, + Composite1Key1 = _entityWithCompositeId.Key.Id1, + Composite1Key2 = _entityWithCompositeId.Key.Id2, + Simple1Id = child1.Id, + Simple2Id = child2.Id, + CustomEntityNameId = _entityWithCustomEntityName.Id + }; + + session.Save(_noAssociation); + + session.Flush(); + transaction.Commit(); + } + } + + #endregion Test Setup + } +} diff --git a/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs new file mode 100644 index 00000000000..26ee137a931 --- /dev/null +++ b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs @@ -0,0 +1,433 @@ +using System; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria +{ + /// + /// Tests for explicit entity joins (not associated entities) + /// + [TestFixture] + public class EntityJoinCriteriaTest : TestCaseMappingByCode + { + private const string customEntityName = "CustomEntityName"; + private EntityWithCompositeId _entityWithCompositeId; + private EntityWithNoAssociation _noAssociation; + private EntityCustomEntityName _entityWithCustomEntityName; + + //check JoinEntityAlias - JoinAlias analog for entity join + [Test] + public void CanJoinNotAssociatedEntity() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + .JoinEntityAlias(() => entityComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == entityComplex.Id)).Take(1) + .SingleOrDefault(); + entityComplex = session.Load(root.Complex1Id); + + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + //check JoinEntityQueryOver - JoinQueryOver analog for entity join + [Test] + public void CanJoinEntityQueryOver() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejQueryOver = null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + .JoinEntityQueryOver( ()=> ejQueryOver, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id)) + .Take(1) + .SingleOrDefault(); + ejQueryOver = session.Load(root.Complex1Id); + + Assert.That(NHibernateUtil.IsInitialized(ejQueryOver), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + //can join not associated entity and join associated entities for it via JoinQueryOver + [Test] + public void CanQueryOverForAssociationInNotAssociatedEntity() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejComplex = null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + .JoinEntityQueryOver( ()=> ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .JoinQueryOver(ej => ej.Child1) + .Take(1) + .SingleOrDefault(); + + ejComplex = session.Load(root.Complex1Id); + + Assert.That(NHibernateUtil.IsInitialized(ejComplex), Is.True); + Assert.That(NHibernateUtil.IsInitialized(ejComplex.Child1), Is.True); + Assert.That(NHibernateUtil.IsInitialized(ejComplex.Child2), Is.False); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void SimpleProjectionForEntityJoin() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejComplex = null; + EntityWithNoAssociation root = null; + var name = session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .Select((e) => ejComplex.Name) + .Take(1) + .SingleOrDefault(); + + Assert.That(name, Is.Not.Empty); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void EntityProjectionForEntityJoin() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntitySimpleChild ejChild1 = null; + + EntityComplex root = null; + EntityComplex st = null; + var r = session + .QueryOver(() => root) + .JoinAlias(c => c.SameTypeChild, () => st) + .JoinEntityAlias(() => ejChild1, JoinType.InnerJoin, Restrictions.Where(() => ejChild1.Id == root.Child1.Id)) + .Select( + Projections.RootEntity(), + Projections.Entity(() => st), + Projections.Entity(() => ejChild1) + ) + .SingleOrDefault(); + var rootObj = (EntityComplex) r[0]; + var mappedObj = (EntityComplex) r[1]; + var entityJoinObj = (EntitySimpleChild) r[2]; + + Assert.That(rootObj, Is.Not.Null); + Assert.That(mappedObj, Is.Not.Null); + Assert.That(entityJoinObj, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(rootObj), Is.True); + Assert.That(NHibernateUtil.IsInitialized(mappedObj), Is.True); + Assert.That(NHibernateUtil.IsInitialized(entityJoinObj), Is.True); + } + } + + //just check that it can be executed without error + [Test] + public void MixOfJoinsForAssociatedAndNotAssociatedEntities() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + + EntityComplex root = null; + EntityComplex ejLevel1 = null; + EntitySimpleChild customChildForEjLevel1 = null; + EntityComplex entityComplexForEjLevel1 = null; + EntitySimpleChild ejLevel2OnEntityComplexForEjLevel1 = null; + var obj = session + .QueryOver(() => root) + .JoinEntityAlias(() => ejLevel1, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null)) + .JoinAlias(() => ejLevel1.Child1, () => customChildForEjLevel1, JoinType.InnerJoin) + .JoinAlias(() => ejLevel1.SameTypeChild, () => entityComplexForEjLevel1, JoinType.LeftOuterJoin) + .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, JoinType.InnerJoin, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id)) + .Where(() => customChildForEjLevel1.Id != null && ejLevel2OnEntityComplexForEjLevel1.Id != null) + .Take(1) + .SingleOrDefault(); + } + } + + [Test] + public void EntityJoinForCompositeKey() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityWithCompositeId ejComposite = null; + EntityWithNoAssociation root = null; + root = session + .QueryOver(() => root) + .JoinEntityAlias(() => ejComposite, JoinType.InnerJoin, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2 )) + .Take(1).SingleOrDefault(); + var composite = session.Load(_entityWithCompositeId.Key); + + 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 NullLeftEntityJoin() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejLeftNull = null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + //add some non existent + .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .Take(1) + .SingleOrDefault(); + + Assert.That(root, Is.Not.Null, "root should not be null (looks like left join didn't work)"); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void NullLeftEntityJoinWithEntityProjection() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejLeftNull = null; + EntityWithNoAssociation root = null; + var objs = session.QueryOver(() => root) + //add some non existent + .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .Select((e) => root.AsEntity(), e => ejLeftNull.AsEntity()) + .Take(1) + .SingleOrDefault(); + root = (EntityWithNoAssociation) objs[0]; + ejLeftNull = (EntityComplex) objs[1]; + + Assert.That(root, Is.Not.Null, "root should not be null (looks like left join didn't work)"); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(ejLeftNull, Is.Null, "Entity join should be null"); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void EntityJoinForCustomEntityName() + { + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityCustomEntityName ejCustomEntity= null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + .JoinEntityAlias(() => ejCustomEntity, JoinType.InnerJoin, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), customEntityName) + .Take(1) + .SingleOrDefault(); + + ejCustomEntity = session.Load(root.CustomEntityNameId); + + Assert.That(NHibernateUtil.IsInitialized(ejCustomEntity), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + #region Test Setup + + 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); + }); + + mapper.Class( + rc => + { + rc.ComponentAsId( + e => e.Key, + ekm => + { + ekm.Property(ek => ek.Id1); + ekm.Property(ek => ek.Id2); + }); + + rc.Property(e => e.Name); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + + rc.Property(e => e.Complex1Id); + rc.Property(e => e.Complex2Id); + rc.Property(e => e.Simple1Id); + rc.Property(e => e.Simple2Id); + rc.Property(e => e.Composite1Key1); + rc.Property(e => e.Composite1Key2); + rc.Property(e => e.CustomEntityNameId); + + }); + + mapper.Class( + rc => + { + rc.EntityName(customEntityName); + + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + 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); + + _entityWithCustomEntityName = new EntityCustomEntityName() + { + Name = "EntityCustomEntityName" + }; + + session.Save(customEntityName, _entityWithCustomEntityName); + + _noAssociation = new EntityWithNoAssociation() + { + Complex1Id = parent.Id, + Complex2Id = parent.SameTypeChild.Id, + Composite1Key1 = _entityWithCompositeId.Key.Id1, + Composite1Key2 = _entityWithCompositeId.Key.Id2, + Simple1Id = child1.Id, + Simple2Id = child2.Id, + CustomEntityNameId = _entityWithCustomEntityName.Id + }; + + session.Save(_noAssociation); + + session.Flush(); + transaction.Commit(); + } + } + + #endregion Test Setup + } + + public class EntityWithNoAssociation + { + public virtual Guid Id { get; set; } + public virtual Guid Complex1Id { get; set; } + public virtual Guid Complex2Id { get; set; } + public virtual Guid Simple1Id { get; set; } + public virtual Guid Simple2Id { get; set; } + public virtual int Composite1Key1 { get; set; } + public virtual int Composite1Key2 { get; set; } + public virtual Guid CustomEntityNameId { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate/Async/Impl/CriteriaImpl.cs b/src/NHibernate/Async/Impl/CriteriaImpl.cs index a40bc06bbd5..4bf96fbbebf 100644 --- a/src/NHibernate/Async/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Async/Impl/CriteriaImpl.cs @@ -22,7 +22,7 @@ namespace NHibernate.Impl { using System.Threading.Tasks; - public partial class CriteriaImpl : ICriteria + public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria { public async Task ListAsync(CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs b/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs new file mode 100644 index 00000000000..56d6892e44c --- /dev/null +++ b/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs @@ -0,0 +1,17 @@ +using System; +using System.Linq.Expressions; +using NHibernate.SqlCommand; + +namespace NHibernate.Criterion +{ + //TODO: Make interface more flexible for changes (maybe it should take only 2 params alias + EntityJoinConfig) + public interface ISupportEntityJoinQueryOver + { + void JoinEntityAlias(Expression> alias, JoinType joinType, ICriterion withClause, string entityName); + } + + public interface ISupportEntityJoinQueryOver: ISupportEntityJoinQueryOver + { + IQueryOver JoinEntityQueryOver(Expression> alias, JoinType joinType, ICriterion withClause, string entityName); + } +} diff --git a/src/NHibernate/Criterion/QueryOver.cs b/src/NHibernate/Criterion/QueryOver.cs index e84f406cc02..cd17f845e8b 100644 --- a/src/NHibernate/Criterion/QueryOver.cs +++ b/src/NHibernate/Criterion/QueryOver.cs @@ -65,6 +65,10 @@ internal static Exception GetDirectUsageException() [Serializable] public abstract partial class QueryOver : QueryOver, IQueryOver { + protected internal QueryOver Create(ICriteria criteria) + { + return new QueryOver(impl, criteria); + } private IList List() { @@ -280,7 +284,8 @@ IQueryOver IQueryOver.ReadOnly() /// Implementation of the interface /// [Serializable] - public class QueryOver : QueryOver, IQueryOver + public class QueryOver : QueryOver, IQueryOver, + ISupportEntityJoinQueryOver { protected internal QueryOver() @@ -658,6 +663,11 @@ public QueryOver JoinQueryOver(Expression>> path joinType)); } + public QueryOver JoinEntityQueryOver(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) + { + return Create(CreateEntityCriteria(alias, joinType, withClause, entityName)); + } + public QueryOver JoinAlias(Expression> path, Expression> alias) { return AddAlias( @@ -758,6 +768,11 @@ private QueryOver AddAlias(string path, string alias, JoinType j return this; } + private ICriteria CreateEntityCriteria(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) + { + return criteria.CreateEntityCriteria(ExpressionProcessor.FindMemberExpression(alias.Body), joinType, withClause, entityName ?? typeof(U).FullName); + } + private QueryOver Add(Expression> expression) { criteria.Add(ExpressionProcessor.ProcessExpression(expression)); @@ -974,6 +989,17 @@ IQueryOver IQueryOver.JoinAlias(Expression IQueryOver.JoinAlias(Expression>> path, Expression> alias, JoinType joinType, ICriterion withClause) { return JoinAlias(path, alias, joinType, withClause); } + IQueryOver ISupportEntityJoinQueryOver.JoinEntityQueryOver(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) + { + return JoinEntityQueryOver(alias, joinType, withClause, entityName); + } + + //6.0 TODO: to remove and merge with extension EntityJoinExtensions.JoinEntityAlias + void ISupportEntityJoinQueryOver.JoinEntityAlias(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) + { + CreateEntityCriteria(alias, joinType, withClause, entityName); + } + IQueryOverJoinBuilder IQueryOver.Inner { get { return new IQueryOverJoinBuilder(this, JoinType.InnerJoin); } } diff --git a/src/NHibernate/EntityJoinExtensions.cs b/src/NHibernate/EntityJoinExtensions.cs new file mode 100644 index 00000000000..3cd7ddf3f19 --- /dev/null +++ b/src/NHibernate/EntityJoinExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq.Expressions; +using NHibernate.Criterion; +using NHibernate.Impl; +using NHibernate.SqlCommand; + +namespace NHibernate +{ + public static class EntityJoinExtensions + { + // 6.0 TODO: merge into 'IQueryOver + public static TThis JoinEntityAlias(this TThis queryOver, Expression> alias, JoinType joinType, ICriterion withClause, string entityName = null) where TThis : IQueryOver + { + var q = CastOrThrow(queryOver); + q.JoinEntityAlias(alias, joinType, withClause, entityName); + return queryOver; + } + + public static IQueryOver JoinEntityQueryOver(this IQueryOver queryOver, Expression> alias, JoinType joinType, ICriterion withClause, string entityName = null) + { + var q = CastOrThrow>(queryOver); + return q.JoinEntityQueryOver(alias, joinType, withClause, entityName); + } + + // 6.0 TODO: merge into 'ICriteria' + public static ICriteria CreateEntityAlias(this ICriteria criteria, string alias, JoinType joinType, ICriterion withClause, string entityName) + { + CreateEntityCriteria(criteria, alias, joinType, withClause, entityName); + return criteria; + } + + public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, JoinType joinType, ICriterion withClause, string entityName) + { + var c = CastOrThrow(criteria); + return c.CreateEntityCriteria(alias, joinType, withClause, entityName); + } + + private static T CastOrThrow(object obj) where T: class + { + return obj as T + ?? throw new ArgumentException($"{obj.GetType().FullName} requires to implement {nameof(T)} interface to support explicit entity joins."); + } + } +} diff --git a/src/NHibernate/Impl/CriteriaImpl.cs b/src/NHibernate/Impl/CriteriaImpl.cs index 5e3bbb60d88..0ed853dd345 100644 --- a/src/NHibernate/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Impl/CriteriaImpl.cs @@ -15,7 +15,7 @@ namespace NHibernate.Impl /// Implementation of the interface /// [Serializable] - public partial class CriteriaImpl : ICriteria + public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria { private readonly System.Type persistentClass; private readonly List criteria = new List(); @@ -42,6 +42,7 @@ public partial class CriteriaImpl : ICriteria private readonly Dictionary subcriteriaByPath = new Dictionary(); private readonly Dictionary subcriteriaByAlias = new Dictionary(); + private readonly string entityOrClassName; // Projection Fields @@ -71,7 +72,7 @@ public CriteriaImpl(string entityOrClassName, string alias, ISessionImplementor rootAlias = alias; subcriteriaByAlias[alias] = this; } - + public ISessionImplementor Session { get { return session; } @@ -371,6 +372,11 @@ public ICriteria CreateAlias(string associationPath, string alias, JoinType join return this; } + public ICriteria CreateEntityCriteria(string alias, JoinType joinType, ICriterion withClause, string entityName) + { + return new Subcriteria(this, this, alias, alias, joinType, withClause, entityName); + } + public ICriteria Add(ICriteria criteriaInst, ICriterion expression) { criteria.Add(new CriterionEntry(expression, criteriaInst)); @@ -647,7 +653,7 @@ public sealed partial class Subcriteria : ICriteria private readonly JoinType joinType; private ICriterion withClause; - internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string alias, JoinType joinType, ICriterion withClause) + internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string alias, JoinType joinType, ICriterion withClause, string joinEntityName = null) { this.root = root; this.parent = parent; @@ -655,6 +661,7 @@ internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string al this.path = path; this.joinType = joinType; this.withClause = withClause; + JoinEntityName = joinEntityName; root.subcriteriaList.Add(this); @@ -668,6 +675,16 @@ internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string al internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, JoinType joinType) : this(root, parent, path, null, joinType) { } + /// + /// Entity name for "Entity Join" - join for enitity with not mapped association + /// + public string JoinEntityName { get; } + + /// + /// Is this an Entity join for not mapped association + /// + public bool IsEntityJoin => JoinEntityName != null; + public ICriterion WithClause { get { return withClause; } diff --git a/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs b/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs new file mode 100644 index 00000000000..088436af2d9 --- /dev/null +++ b/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs @@ -0,0 +1,11 @@ +using NHibernate.Criterion; +using NHibernate.SqlCommand; + +namespace NHibernate.Impl +{ + public interface ISupportEntityJoinCriteria + { + //TODO: Make interface more flexible for changes (maybe it should be as simple as CreateEntityCriteria(EntityJoinConfig join) + ICriteria CreateEntityCriteria(string alias, JoinType joinType, ICriterion withClause, string entityName); + } +} diff --git a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs index 30fbeca0eae..a789cbaeb90 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -31,7 +31,7 @@ public AbstractEntityJoinWalker(string rootSqlAlias, IOuterJoinLoadable persiste protected virtual void InitAll(SqlString whereString, SqlString orderByString, LockMode lockMode) { - WalkEntityTree(persister, Alias); + AddAssociations(); IList allAssociations = new List(associations); allAssociations.Add(CreateAssociation(persister.EntityType, alias)); @@ -48,7 +48,7 @@ protected void InitProjection(SqlString projectionString, SqlString whereString, protected void InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary enabledFilters, LockMode lockMode, IList entityProjections) { - WalkEntityTree(persister, Alias); + AddAssociations(); int countEntities = entityProjections.Count; if (countEntities > 0) @@ -81,6 +81,11 @@ protected void InitProjection(SqlString projectionString, SqlString whereString, InitStatementString(projectionString, whereString, orderByString, groupByString, havingString, lockMode); } + protected virtual void AddAssociations() + { + WalkEntityTree(persister, Alias); + } + private OuterJoinableAssociation CreateAssociation(EntityType entityType, string tableAlias) { return new OuterJoinableAssociation( diff --git a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs index 2394fef4f19..08f81ec4174 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs @@ -76,6 +76,22 @@ public CriteriaJoinWalker(IOuterJoinLoadable persister, CriteriaQueryTranslator } } + protected override void AddAssociations() + { + base.AddAssociations(); + foreach (var entityJoinInfo in translator.GetEntityJoins().Values) + { + var tableAlias = translator.GetSQLAlias(entityJoinInfo.Criteria); + var criteriaPath = entityJoinInfo.Criteria.Alias; //path for entity join is equal to alias + var persister + = entityJoinInfo.Persister as IOuterJoinLoadable; + AddExplicitEntityJoinAssociation(persister, tableAlias, translator.GetJoinType(criteriaPath), GetWithClause(criteriaPath)); + IncludeInResultIfNeeded(persister, entityJoinInfo.Criteria, tableAlias); + //collect mapped associations for entity join + WalkEntityTree(persister, tableAlias, criteriaPath, 1); + } + } + protected override void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) { // NH different behavior (NH-1476, NH-1760, NH-1785) @@ -199,19 +215,7 @@ protected override string GenerateTableAlias(int n, string path, IJoinable joina ICriteria subcriteria = translator.GetCriteria(path); sqlAlias = subcriteria == null ? null : translator.GetSQLAlias(subcriteria); - if (joinable.ConsumesEntityAlias() && !translator.HasProjection) - { - includeInResultRowList.Add(subcriteria != null && subcriteria.Alias != null); - - if (sqlAlias != null) - { - if (subcriteria.Alias != null) - { - userAliasList.Add(subcriteria.Alias); //alias may be null - resultTypeList.Add(translator.ResultType(subcriteria)); - } - } - } + IncludeInResultIfNeeded(joinable, subcriteria, sqlAlias); } if (sqlAlias == null) @@ -220,6 +224,22 @@ protected override string GenerateTableAlias(int n, string path, IJoinable joina return sqlAlias; } + private void IncludeInResultIfNeeded(IJoinable joinable, ICriteria subcriteria, string sqlAlias) + { + if (joinable.ConsumesEntityAlias() && !translator.HasProjection) + { + includeInResultRowList.Add(subcriteria != null && subcriteria.Alias != null); + + if (sqlAlias != null) + { + if (subcriteria.Alias != null) + { + userAliasList.Add(subcriteria.Alias); //alias may be null + resultTypeList.Add(translator.ResultType(subcriteria)); + } + } + } + } protected override string GenerateRootAlias(string tableName) { diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index d1e1b0da801..323f3b7a949 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -7,15 +7,23 @@ using NHibernate.Impl; using NHibernate.Param; using NHibernate.Persister.Collection; +using NHibernate.Persister.Entity; using NHibernate_Persister_Entity = NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; using NHibernate.Util; +using IQueryable = NHibernate.Persister.Entity.IQueryable; namespace NHibernate.Loader.Criteria { public class CriteriaQueryTranslator : ICriteriaQuery, ISupportEntityProjectionCriteriaQuery { + public class EntityJoinInfo + { + public ICriteria Criteria; + public IQueryable Persister; + } + public static readonly string RootSqlAlias = CriteriaSpecification.RootAlias + '_'; private static readonly INHibernateLogger logger = NHibernateLogger.For(typeof(CriteriaQueryTranslator)); @@ -23,7 +31,6 @@ public class CriteriaQueryTranslator : ICriteriaQuery, ISupportEntityProjectionC private readonly ICriteriaQuery outerQueryTranslator; private readonly CriteriaImpl rootCriteria; - private readonly string rootEntityName; private readonly string rootSQLAlias; private int indexForAlias = 0; private readonly List entityProjections = new List(); @@ -47,7 +54,8 @@ public class CriteriaQueryTranslator : ICriteriaQuery, ISupportEntityProjectionC private readonly ICollection namedParameters; private readonly ISet subQuerySpaces = new HashSet(); - + private Dictionary entityJoins = new Dictionary(); + private readonly IQueryable rootPersister; public CriteriaQueryTranslator(ISessionFactoryImplementor factory, CriteriaImpl criteria, string rootEntityName, string rootSQLAlias, ICriteriaQuery outerQuery) @@ -62,8 +70,9 @@ public CriteriaQueryTranslator(ISessionFactoryImplementor factory, CriteriaImpl string rootSQLAlias) { rootCriteria = criteria; - this.rootEntityName = rootEntityName; + sessionFactory = factory; + rootPersister = GetQueryablePersister(rootEntityName); this.rootSQLAlias = rootSQLAlias; helper = new SessionFactoryHelper(factory); @@ -72,6 +81,7 @@ public CriteriaQueryTranslator(ISessionFactoryImplementor factory, CriteriaImpl CreateAliasCriteriaMap(); CreateAssociationPathCriteriaMap(); + CreateEntityJoinMap(); CreateCriteriaEntityNameMap(); CreateCriteriaCollectionPersisters(); CreateCriteriaSQLAliasMap(); @@ -115,6 +125,11 @@ public CriteriaImpl RootCriteria ICriteria ISupportEntityProjectionCriteriaQuery.RootCriteria => rootCriteria; + internal IReadOnlyDictionary GetEntityJoins() + { + return entityJoins; + } + public QueryParameters GetQueryParameters() { RowSelection selection = new RowSelection(); @@ -380,19 +395,35 @@ private string GetWholeAssociationPath(CriteriaImpl.Subcriteria subcriteria) private void CreateCriteriaEntityNameMap() { // initialize the rootProvider first - ICriteriaInfoProvider rootProvider = new EntityCriteriaInfoProvider((NHibernate_Persister_Entity.IQueryable)sessionFactory.GetEntityPersister(rootEntityName)); + ICriteriaInfoProvider rootProvider = new EntityCriteriaInfoProvider(rootPersister); criteriaInfoMap.Add(rootCriteria, rootProvider); nameCriteriaInfoMap.Add(rootProvider.Name, rootProvider); foreach (KeyValuePair me in associationPathCriteriaMap) { - ICriteriaInfoProvider info = GetPathInfo(me.Key); + ICriteriaInfoProvider info = GetPathInfo(me.Key, rootProvider); criteriaInfoMap.Add(me.Value, info); nameCriteriaInfoMap[info.Name] = info; } } + //explicit joins with not associated entities + private void CreateEntityJoinMap() + { + foreach (var criteria in rootCriteria.IterateSubcriteria()) + { + if (criteria.IsEntityJoin) + { + var entityJoinPersister = GetQueryablePersister(criteria.JoinEntityName); + entityJoins[criteria.Alias] = new EntityJoinInfo + { + Persister = entityJoinPersister, + Criteria = criteria, + }; + } + } + } private void CreateCriteriaCollectionPersisters() { @@ -408,15 +439,26 @@ private void CreateCriteriaCollectionPersisters() private Persister.Entity.IJoinable GetPathJoinable(string path) { - NHibernate_Persister_Entity.IJoinable last = (NHibernate_Persister_Entity.IJoinable)Factory.GetEntityPersister(rootEntityName); - NHibernate_Persister_Entity.IPropertyMapping lastEntity = (NHibernate_Persister_Entity.IPropertyMapping)last; - - string componentPath = ""; + // start with the root + IJoinable last = rootPersister; + + var tokens = path.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries); + if (tokens.Length == 0) + return last; + + IPropertyMapping lastEntity = rootPersister; + int i = 0; + if (entityJoins.TryGetValue(tokens[0], out var entityJoinInfo)) + { + last = entityJoinInfo.Persister; + lastEntity = (IPropertyMapping) last; + ++i; + } - StringTokenizer tokens = new StringTokenizer(path, ".", false); - foreach (string token in tokens) + string componentPath = string.Empty; + for (; i < tokens.Length; i++) { - componentPath += token; + componentPath += tokens[i]; IType type = lastEntity.ToType(componentPath); if (type.IsAssociationType) { @@ -446,20 +488,25 @@ private Persister.Entity.IJoinable GetPathJoinable(string path) return last; } - private ICriteriaInfoProvider GetPathInfo(string path) + private ICriteriaInfoProvider GetPathInfo(string path, ICriteriaInfoProvider rootProvider) { - StringTokenizer tokens = new StringTokenizer(path, ".", false); - string componentPath = string.Empty; - - // start with the 'rootProvider' - ICriteriaInfoProvider provider; - if (nameCriteriaInfoMap.TryGetValue(rootEntityName, out provider) == false) - throw new ArgumentException("Could not find ICriteriaInfoProvider for: " + path); + var tokens = path.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries); + // start with the root + ICriteriaInfoProvider provider = rootProvider; + if (tokens.Length == 0) + return provider; + int i = 0; + if (entityJoins.TryGetValue(tokens[0], out var entityJoinInfo)) + { + provider = new EntityCriteriaInfoProvider(entityJoinInfo.Persister); + ++i; + } - foreach (string token in tokens) + string componentPath = string.Empty; + for (; i < tokens.Length; i++) { - componentPath += token; + componentPath += tokens[i]; logger.Debug("searching for {0}", componentPath); IType type = provider.GetType(componentPath); if (type.IsAssociationType) @@ -480,10 +527,11 @@ private ICriteriaInfoProvider GetPathInfo(string path) } else { - provider = new EntityCriteriaInfoProvider((NHibernate_Persister_Entity.IQueryable)sessionFactory.GetEntityPersister( - atype.GetAssociatedEntityName( - sessionFactory) - )); + provider = new EntityCriteriaInfoProvider( + GetQueryablePersister( + atype.GetAssociatedEntityName( + sessionFactory) + )); } componentPath = string.Empty; @@ -876,5 +924,11 @@ private void CreateSubQuerySpaces() } } + + private IQueryable GetQueryablePersister(string entityName) + { + return (IQueryable) sessionFactory.GetEntityPersister(entityName); + } } } + diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 5450e09c84e..4dcdd15f832 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -295,6 +295,25 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st } } + internal void AddExplicitEntityJoinAssociation( + IOuterJoinLoadable persister, + string tableAlias, + JoinType joinType, + SqlString withClause) + { + OuterJoinableAssociation assoc = + new OuterJoinableAssociation( + persister.EntityType, + string.Empty, + Array.Empty(), + tableAlias, + joinType, + withClause, + Factory, + enabledFilters); + AddAssociation(tableAlias, assoc); + } + private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJoinLoadable persister, int propertyNumber, string alias, string path, bool nullable, int currentDepth, ILhsAssociationTypeSqlInfo associationTypeSQLInfo) diff --git a/src/NHibernate/Loader/OuterJoinableAssociation.cs b/src/NHibernate/Loader/OuterJoinableAssociation.cs index abd6849a44a..f9d00e3da45 100644 --- a/src/NHibernate/Loader/OuterJoinableAssociation.cs +++ b/src/NHibernate/Loader/OuterJoinableAssociation.cs @@ -34,7 +34,12 @@ public OuterJoinableAssociation(IAssociationType joinableType, String lhsAlias, rhsColumns = JoinHelper.GetRHSColumnNames(joinableType, factory); on = new SqlString(joinableType.GetOnCondition(rhsAlias, factory, enabledFilters)); if (SqlStringHelper.IsNotEmpty(withClause)) - on = on.Append(" and ( ").Append(withClause).Append(" )"); + { + on = on.Count == 0 + ? on.Append(withClause) + : on.Append(" and ( ").Append(withClause).Append(" )"); + } + this.enabledFilters = enabledFilters; // needed later for many-to-many/filter application } @@ -164,4 +169,4 @@ public void AddManyToManyJoin(JoinFragment outerjoin, IQueryableCollection colle joinable.WhereJoinFragment(rhsAlias, false, true)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs index 4c9b88348f7..53cc3116f15 100644 --- a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs +++ b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs @@ -1,3 +1,4 @@ +using NHibernate.Linq; using NHibernate.Util; namespace NHibernate.SqlCommand @@ -39,6 +40,12 @@ public override void AddJoin(string tableName, string alias, string[] fkColumns, _fromFragment.Add(joinString + tableName + ' ' + alias + " on "); + if (fkColumns.Length == 0) + { + _fromFragment.Add(on); + return; + } + for (int j = 0; j < fkColumns.Length; j++) { _fromFragment.Add(fkColumns[j] + "=" + alias + StringHelper.Dot + pkColumns[j]); @@ -96,4 +103,4 @@ public override void AddFromFragmentString(SqlString fromFragmentString) _fromFragment.Add(fromFragmentString); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/SqlCommand/JoinFragment.cs b/src/NHibernate/SqlCommand/JoinFragment.cs index 4b7a47af020..5608ee9adeb 100644 --- a/src/NHibernate/SqlCommand/JoinFragment.cs +++ b/src/NHibernate/SqlCommand/JoinFragment.cs @@ -62,7 +62,7 @@ protected bool AddCondition(SqlStringBuilder buffer, SqlString on) { if (SqlStringHelper.IsNotEmpty(on)) { - if (!on.StartsWithCaseInsensitive(" and")) + if (buffer.Count > 0 && !on.StartsWithCaseInsensitive(" and")) { buffer.Add(" and "); } @@ -79,4 +79,4 @@ protected bool AddCondition(SqlStringBuilder buffer, SqlString on) public bool HasThetaJoins { get; set; } } -} \ No newline at end of file +} From fffd3c5444b86e282b29b632647ef7d0591b827f Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 24 Jan 2018 14:37:12 +0530 Subject: [PATCH 2/8] Interface changes --- .../Async/Criteria/EntityJoinCriteriaTest.cs | 22 ++--- .../Criteria/EntityJoinCriteriaTest.cs | 96 ++++++++++++++++--- .../Criterion/ISupportEntityJoinQueryOver.cs | 9 +- src/NHibernate/Criterion/QueryOver.cs | 20 ++-- src/NHibernate/EntityJoinExtensions.cs | 30 +++--- src/NHibernate/Impl/CriteriaImpl.cs | 2 +- .../Impl/ISupportEntityJoinCriteria.cs | 3 +- 7 files changed, 134 insertions(+), 48 deletions(-) diff --git a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs index a7941c5a175..7474ae7a3fe 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs @@ -39,7 +39,7 @@ public async Task CanJoinNotAssociatedEntityAsync() EntityComplex entityComplex = null; EntityWithNoAssociation root = null; root = await (session.QueryOver(() => root) - .JoinEntityAlias(() => entityComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == entityComplex.Id)).Take(1) + .JoinEntityAlias(() => entityComplex, Restrictions.Where(() => root.Complex1Id == entityComplex.Id), JoinType.InnerJoin).Take(1) .SingleOrDefaultAsync()); entityComplex = await (session.LoadAsync(root.Complex1Id)); @@ -58,7 +58,7 @@ public async Task CanJoinEntityQueryOverAsync() EntityComplex ejQueryOver = null; EntityWithNoAssociation root = null; root = await (session.QueryOver(() => root) - .JoinEntityQueryOver( ()=> ejQueryOver, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id)) + .JoinEntityQueryOver(() => ejQueryOver, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id), JoinType.InnerJoin) .Take(1) .SingleOrDefaultAsync()); ejQueryOver = await (session.LoadAsync(root.Complex1Id)); @@ -78,7 +78,7 @@ public async Task CanQueryOverForAssociationInNotAssociatedEntityAsync() EntityComplex ejComplex = null; EntityWithNoAssociation root = null; root = await (session.QueryOver(() => root) - .JoinEntityQueryOver( ()=> ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) .JoinQueryOver(ej => ej.Child1) .Take(1) .SingleOrDefaultAsync()); @@ -101,7 +101,7 @@ public async Task SimpleProjectionForEntityJoinAsync() EntityComplex ejComplex = null; EntityWithNoAssociation root = null; var name = await (session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) .Select((e) => ejComplex.Name) .Take(1) .SingleOrDefaultAsync()); @@ -124,7 +124,7 @@ public async Task EntityProjectionForEntityJoinAsync() var r = await (session .QueryOver(() => root) .JoinAlias(c => c.SameTypeChild, () => st) - .JoinEntityAlias(() => ejChild1, JoinType.InnerJoin, Restrictions.Where(() => ejChild1.Id == root.Child1.Id)) + .JoinEntityAlias(() => ejChild1, Restrictions.Where(() => ejChild1.Id == root.Child1.Id), JoinType.InnerJoin) .Select( Projections.RootEntity(), Projections.Entity(() => st), @@ -159,10 +159,10 @@ public async Task MixOfJoinsForAssociatedAndNotAssociatedEntitiesAsync() EntitySimpleChild ejLevel2OnEntityComplexForEjLevel1 = null; var obj = await (session .QueryOver(() => root) - .JoinEntityAlias(() => ejLevel1, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null)) + .JoinEntityAlias(() => ejLevel1, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null), JoinType.LeftOuterJoin) .JoinAlias(() => ejLevel1.Child1, () => customChildForEjLevel1, JoinType.InnerJoin) .JoinAlias(() => ejLevel1.SameTypeChild, () => entityComplexForEjLevel1, JoinType.LeftOuterJoin) - .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, JoinType.InnerJoin, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id)) + .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id), JoinType.InnerJoin) .Where(() => customChildForEjLevel1.Id != null && ejLevel2OnEntityComplexForEjLevel1.Id != null) .Take(1) .SingleOrDefaultAsync()); @@ -179,7 +179,7 @@ public async Task EntityJoinForCompositeKeyAsync() EntityWithNoAssociation root = null; root = await (session .QueryOver(() => root) - .JoinEntityAlias(() => ejComposite, JoinType.InnerJoin, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2 )) + .JoinEntityAlias(() => ejComposite, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2), JoinType.InnerJoin) .Take(1).SingleOrDefaultAsync()); var composite = await (session.LoadAsync(_entityWithCompositeId.Key)); @@ -199,7 +199,7 @@ public async Task NullLeftEntityJoinAsync() EntityWithNoAssociation root = null; root = await (session.QueryOver(() => root) //add some non existent - .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) .Take(1) .SingleOrDefaultAsync()); @@ -219,7 +219,7 @@ public async Task NullLeftEntityJoinWithEntityProjectionAsync() EntityWithNoAssociation root = null; var objs = await (session.QueryOver(() => root) //add some non existent - .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) .Select((e) => root.AsEntity(), e => ejLeftNull.AsEntity()) .Take(1) .SingleOrDefaultAsync()); @@ -243,7 +243,7 @@ public async Task EntityJoinForCustomEntityNameAsync() EntityCustomEntityName ejCustomEntity= null; EntityWithNoAssociation root = null; root = await (session.QueryOver(() => root) - .JoinEntityAlias(() => ejCustomEntity, JoinType.InnerJoin, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), customEntityName) + .JoinEntityAlias(() => ejCustomEntity, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), JoinType.InnerJoin, customEntityName) .Take(1) .SingleOrDefaultAsync()); diff --git a/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs index 26ee137a931..7cc7daeeaeb 100644 --- a/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs +++ b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs @@ -28,7 +28,26 @@ public void CanJoinNotAssociatedEntity() EntityComplex entityComplex = null; EntityWithNoAssociation root = null; root = session.QueryOver(() => root) - .JoinEntityAlias(() => entityComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == entityComplex.Id)).Take(1) + .JoinEntityAlias(() => entityComplex, Restrictions.Where(() => root.Complex1Id == entityComplex.Id), JoinType.InnerJoin).Take(1) + .SingleOrDefault(); + entityComplex = session.Load(root.Complex1Id); + + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + //check JoinEntityAlias - JoinAlias analog for entity join + [Test] + public void CanJoinNotAssociatedEntity_Expression() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + .JoinEntityAlias(() => entityComplex, () => root.Complex1Id == entityComplex.Id).Take(1) .SingleOrDefault(); entityComplex = session.Load(root.Complex1Id); @@ -47,7 +66,7 @@ public void CanJoinEntityQueryOver() EntityComplex ejQueryOver = null; EntityWithNoAssociation root = null; root = session.QueryOver(() => root) - .JoinEntityQueryOver( ()=> ejQueryOver, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id)) + .JoinEntityQueryOver(() => ejQueryOver, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id), JoinType.InnerJoin) .Take(1) .SingleOrDefault(); ejQueryOver = session.Load(root.Complex1Id); @@ -67,7 +86,7 @@ public void CanQueryOverForAssociationInNotAssociatedEntity() EntityComplex ejComplex = null; EntityWithNoAssociation root = null; root = session.QueryOver(() => root) - .JoinEntityQueryOver( ()=> ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) .JoinQueryOver(ej => ej.Child1) .Take(1) .SingleOrDefault(); @@ -90,7 +109,7 @@ public void SimpleProjectionForEntityJoin() EntityComplex ejComplex = null; EntityWithNoAssociation root = null; var name = session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejComplex, JoinType.InnerJoin, Restrictions.Where(() => root.Complex1Id == ejComplex.Id)) + .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) .Select((e) => ejComplex.Name) .Take(1) .SingleOrDefault(); @@ -113,7 +132,7 @@ public void EntityProjectionForEntityJoin() var r = session .QueryOver(() => root) .JoinAlias(c => c.SameTypeChild, () => st) - .JoinEntityAlias(() => ejChild1, JoinType.InnerJoin, Restrictions.Where(() => ejChild1.Id == root.Child1.Id)) + .JoinEntityAlias(() => ejChild1, Restrictions.Where(() => ejChild1.Id == root.Child1.Id), JoinType.InnerJoin) .Select( Projections.RootEntity(), Projections.Entity(() => st), @@ -148,10 +167,10 @@ public void MixOfJoinsForAssociatedAndNotAssociatedEntities() EntitySimpleChild ejLevel2OnEntityComplexForEjLevel1 = null; var obj = session .QueryOver(() => root) - .JoinEntityAlias(() => ejLevel1, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null)) + .JoinEntityAlias(() => ejLevel1, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null), JoinType.LeftOuterJoin) .JoinAlias(() => ejLevel1.Child1, () => customChildForEjLevel1, JoinType.InnerJoin) .JoinAlias(() => ejLevel1.SameTypeChild, () => entityComplexForEjLevel1, JoinType.LeftOuterJoin) - .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, JoinType.InnerJoin, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id)) + .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id), JoinType.InnerJoin) .Where(() => customChildForEjLevel1.Id != null && ejLevel2OnEntityComplexForEjLevel1.Id != null) .Take(1) .SingleOrDefault(); @@ -168,7 +187,7 @@ public void EntityJoinForCompositeKey() EntityWithNoAssociation root = null; root = session .QueryOver(() => root) - .JoinEntityAlias(() => ejComposite, JoinType.InnerJoin, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2 )) + .JoinEntityAlias(() => ejComposite, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2), JoinType.InnerJoin) .Take(1).SingleOrDefault(); var composite = session.Load(_entityWithCompositeId.Key); @@ -188,7 +207,7 @@ public void NullLeftEntityJoin() EntityWithNoAssociation root = null; root = session.QueryOver(() => root) //add some non existent - .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) .Take(1) .SingleOrDefault(); @@ -208,7 +227,7 @@ public void NullLeftEntityJoinWithEntityProjection() EntityWithNoAssociation root = null; var objs = session.QueryOver(() => root) //add some non existent - .JoinEntityAlias(() => ejLeftNull, JoinType.LeftOuterJoin, Restrictions.Where(() => ejLeftNull.Id == null)) + .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) .Select((e) => root.AsEntity(), e => ejLeftNull.AsEntity()) .Take(1) .SingleOrDefault(); @@ -232,7 +251,7 @@ public void EntityJoinForCustomEntityName() EntityCustomEntityName ejCustomEntity= null; EntityWithNoAssociation root = null; root = session.QueryOver(() => root) - .JoinEntityAlias(() => ejCustomEntity, JoinType.InnerJoin, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), customEntityName) + .JoinEntityAlias(() => ejCustomEntity, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), JoinType.InnerJoin, customEntityName) .Take(1) .SingleOrDefault(); @@ -243,6 +262,61 @@ public void EntityJoinForCustomEntityName() } } + [Test] + public void EntityJoinFoSubquery_JoinEntityAlias() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ej = null; + EntityWithNoAssociation root = null; + + EntityComplex ejSub = null; + EntityWithNoAssociation rootSub = null; + + var subquery = QueryOver.Of(() => rootSub) + .JoinEntityAlias(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) + .Where(r => ejSub.Name == ej.Name) + .Select(x => ejSub.Id); + + root = session.QueryOver(() => root) + .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .WithSubquery.WhereExists(subquery) + .SingleOrDefault(); + ej = session.Load(root.Complex1Id); + + Assert.That(NHibernateUtil.IsInitialized(ej), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void EntityJoinFoSubquery_JoinQueryOver() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ej = null; + EntityWithNoAssociation root = null; + + EntityComplex ejSub = null; + EntityWithNoAssociation rootSub = null; + + var subquery = QueryOver.Of(() => rootSub) + .JoinEntityQueryOver(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) + .Where(x => x.Name == ej.Name) + .Select(x => ejSub.Id); + + root = session.QueryOver(() => root) + .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .WithSubquery.WhereExists(subquery) + .SingleOrDefault(); + ej = session.Load(root.Complex1Id); + + Assert.That(NHibernateUtil.IsInitialized(ej), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } #region Test Setup protected override HbmMapping GetMappings() diff --git a/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs b/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs index 56d6892e44c..1dd8a089a97 100644 --- a/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs +++ b/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs @@ -4,14 +4,15 @@ namespace NHibernate.Criterion { - //TODO: Make interface more flexible for changes (maybe it should take only 2 params alias + EntityJoinConfig) + // 6.0 TODO: merge into IQueryOver. public interface ISupportEntityJoinQueryOver { - void JoinEntityAlias(Expression> alias, JoinType joinType, ICriterion withClause, string entityName); + void JoinEntityAlias(Expression> alias, ICriterion withClause, JoinType joinType, string entityName); } - + + // 6.0 TODO: merge into IQueryOver. public interface ISupportEntityJoinQueryOver: ISupportEntityJoinQueryOver { - IQueryOver JoinEntityQueryOver(Expression> alias, JoinType joinType, ICriterion withClause, string entityName); + IQueryOver JoinEntityQueryOver(Expression> alias, ICriterion withClause, JoinType joinType, string entityName); } } diff --git a/src/NHibernate/Criterion/QueryOver.cs b/src/NHibernate/Criterion/QueryOver.cs index cd17f845e8b..03e6b98bfe8 100644 --- a/src/NHibernate/Criterion/QueryOver.cs +++ b/src/NHibernate/Criterion/QueryOver.cs @@ -65,9 +65,9 @@ internal static Exception GetDirectUsageException() [Serializable] public abstract partial class QueryOver : QueryOver, IQueryOver { - protected internal QueryOver Create(ICriteria criteria) + protected internal QueryOver Create(ICriteria criteria) { - return new QueryOver(impl, criteria); + return new QueryOver(impl, criteria); } private IList List() @@ -663,7 +663,12 @@ public QueryOver JoinQueryOver(Expression>> path joinType)); } - public QueryOver JoinEntityQueryOver(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) + public QueryOver JoinEntityQueryOver(Expression> alias, Expression> withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) + { + return JoinEntityQueryOver(alias, Restrictions.Where(withClause), joinType, entityName); + } + + public QueryOver JoinEntityQueryOver(Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) { return Create(CreateEntityCriteria(alias, joinType, withClause, entityName)); } @@ -770,7 +775,7 @@ private QueryOver AddAlias(string path, string alias, JoinType j private ICriteria CreateEntityCriteria(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) { - return criteria.CreateEntityCriteria(ExpressionProcessor.FindMemberExpression(alias.Body), joinType, withClause, entityName ?? typeof(U).FullName); + return criteria.CreateEntityCriteria(ExpressionProcessor.FindMemberExpression(alias.Body), withClause, joinType, entityName ?? typeof(U).FullName); } private QueryOver Add(Expression> expression) @@ -989,13 +994,12 @@ IQueryOver IQueryOver.JoinAlias(Expression IQueryOver.JoinAlias(Expression>> path, Expression> alias, JoinType joinType, ICriterion withClause) { return JoinAlias(path, alias, joinType, withClause); } - IQueryOver ISupportEntityJoinQueryOver.JoinEntityQueryOver(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) + IQueryOver ISupportEntityJoinQueryOver.JoinEntityQueryOver(Expression> alias, ICriterion withClause, JoinType joinType, string entityName) { - return JoinEntityQueryOver(alias, joinType, withClause, entityName); + return JoinEntityQueryOver(alias, withClause, joinType, entityName); } - //6.0 TODO: to remove and merge with extension EntityJoinExtensions.JoinEntityAlias - void ISupportEntityJoinQueryOver.JoinEntityAlias(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) + void ISupportEntityJoinQueryOver.JoinEntityAlias(Expression> alias, ICriterion withClause, JoinType joinType, string entityName) { CreateEntityCriteria(alias, joinType, withClause, entityName); } diff --git a/src/NHibernate/EntityJoinExtensions.cs b/src/NHibernate/EntityJoinExtensions.cs index 3cd7ddf3f19..0bc957fd8de 100644 --- a/src/NHibernate/EntityJoinExtensions.cs +++ b/src/NHibernate/EntityJoinExtensions.cs @@ -8,34 +8,42 @@ namespace NHibernate { public static class EntityJoinExtensions { - // 6.0 TODO: merge into 'IQueryOver - public static TThis JoinEntityAlias(this TThis queryOver, Expression> alias, JoinType joinType, ICriterion withClause, string entityName = null) where TThis : IQueryOver + public static TThis JoinEntityAlias(this TThis queryOver, Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) where TThis : IQueryOver { var q = CastOrThrow(queryOver); - q.JoinEntityAlias(alias, joinType, withClause, entityName); + q.JoinEntityAlias(alias, withClause, joinType, entityName); return queryOver; } - public static IQueryOver JoinEntityQueryOver(this IQueryOver queryOver, Expression> alias, JoinType joinType, ICriterion withClause, string entityName = null) + public static TThis JoinEntityAlias(this TThis queryOver, Expression> alias, Expression> withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) where TThis : IQueryOver + { + return JoinEntityAlias(queryOver, alias, Restrictions.Where(withClause), joinType, entityName); + } + + public static IQueryOver JoinEntityQueryOver(this IQueryOver queryOver, Expression> alias, Expression> withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) + { + return JoinEntityQueryOver(queryOver, alias, Restrictions.Where(withClause), joinType, entityName); + } + + public static IQueryOver JoinEntityQueryOver(this IQueryOver queryOver, Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) { var q = CastOrThrow>(queryOver); - return q.JoinEntityQueryOver(alias, joinType, withClause, entityName); + return q.JoinEntityQueryOver(alias, withClause, joinType, entityName); } - // 6.0 TODO: merge into 'ICriteria' - public static ICriteria CreateEntityAlias(this ICriteria criteria, string alias, JoinType joinType, ICriterion withClause, string entityName) + public static ICriteria CreateEntityAlias(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType, string entityName) { - CreateEntityCriteria(criteria, alias, joinType, withClause, entityName); + CreateEntityCriteria(criteria, alias, withClause, joinType, entityName); return criteria; } - public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, JoinType joinType, ICriterion withClause, string entityName) + public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType, string entityName) { var c = CastOrThrow(criteria); - return c.CreateEntityCriteria(alias, joinType, withClause, entityName); + return c.CreateEntityCriteria(alias, withClause, joinType, entityName); } - private static T CastOrThrow(object obj) where T: class + private static T CastOrThrow(object obj) where T : class { return obj as T ?? throw new ArgumentException($"{obj.GetType().FullName} requires to implement {nameof(T)} interface to support explicit entity joins."); diff --git a/src/NHibernate/Impl/CriteriaImpl.cs b/src/NHibernate/Impl/CriteriaImpl.cs index 0ed853dd345..0981561a008 100644 --- a/src/NHibernate/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Impl/CriteriaImpl.cs @@ -372,7 +372,7 @@ public ICriteria CreateAlias(string associationPath, string alias, JoinType join return this; } - public ICriteria CreateEntityCriteria(string alias, JoinType joinType, ICriterion withClause, string entityName) + public ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, string entityName) { return new Subcriteria(this, this, alias, alias, joinType, withClause, entityName); } diff --git a/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs b/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs index 088436af2d9..aef22cedd9f 100644 --- a/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs +++ b/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs @@ -5,7 +5,6 @@ namespace NHibernate.Impl { public interface ISupportEntityJoinCriteria { - //TODO: Make interface more flexible for changes (maybe it should be as simple as CreateEntityCriteria(EntityJoinConfig join) - ICriteria CreateEntityCriteria(string alias, JoinType joinType, ICriterion withClause, string entityName); + ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, string entityName); } } From 817d85b1d43f39177350f036453d827158cc91b9 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 24 Jan 2018 19:49:23 +0530 Subject: [PATCH 3/8] clean up --- src/NHibernate/Impl/CriteriaImpl.cs | 5 ++--- src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs | 3 +-- .../Loader/Criteria/CriteriaQueryTranslator.cs | 9 +++------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/NHibernate/Impl/CriteriaImpl.cs b/src/NHibernate/Impl/CriteriaImpl.cs index 0981561a008..eaee6f5b18a 100644 --- a/src/NHibernate/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Impl/CriteriaImpl.cs @@ -42,7 +42,6 @@ public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria private readonly Dictionary subcriteriaByPath = new Dictionary(); private readonly Dictionary subcriteriaByAlias = new Dictionary(); - private readonly string entityOrClassName; // Projection Fields @@ -72,7 +71,7 @@ public CriteriaImpl(string entityOrClassName, string alias, ISessionImplementor rootAlias = alias; subcriteriaByAlias[alias] = this; } - + public ISessionImplementor Session { get { return session; } @@ -676,7 +675,7 @@ internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, JoinType : this(root, parent, path, null, joinType) { } /// - /// Entity name for "Entity Join" - join for enitity with not mapped association + /// Entity name for "Entity Join" - join for entity with not mapped association /// public string JoinEntityName { get; } diff --git a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs index 08f81ec4174..0a2dd06d017 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs @@ -83,8 +83,7 @@ protected override void AddAssociations() { var tableAlias = translator.GetSQLAlias(entityJoinInfo.Criteria); var criteriaPath = entityJoinInfo.Criteria.Alias; //path for entity join is equal to alias - var persister - = entityJoinInfo.Persister as IOuterJoinLoadable; + var persister = entityJoinInfo.Persister as IOuterJoinLoadable; AddExplicitEntityJoinAssociation(persister, tableAlias, translator.GetJoinType(criteriaPath), GetWithClause(criteriaPath)); IncludeInResultIfNeeded(persister, entityJoinInfo.Criteria, tableAlias); //collect mapped associations for entity join diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index 323f3b7a949..fae80646f39 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -452,7 +452,7 @@ private Persister.Entity.IJoinable GetPathJoinable(string path) { last = entityJoinInfo.Persister; lastEntity = (IPropertyMapping) last; - ++i; + i++; } string componentPath = string.Empty; @@ -500,7 +500,7 @@ private ICriteriaInfoProvider GetPathInfo(string path, ICriteriaInfoProvider roo if (entityJoins.TryGetValue(tokens[0], out var entityJoinInfo)) { provider = new EntityCriteriaInfoProvider(entityJoinInfo.Persister); - ++i; + i++; } string componentPath = string.Empty; @@ -528,10 +528,7 @@ private ICriteriaInfoProvider GetPathInfo(string path, ICriteriaInfoProvider roo else { provider = new EntityCriteriaInfoProvider( - GetQueryablePersister( - atype.GetAssociatedEntityName( - sessionFactory) - )); + GetQueryablePersister(atype.GetAssociatedEntityName(sessionFactory))); } componentPath = string.Empty; From 1f77db9d90f9edea1177810508181078310d414f Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 24 Jan 2018 20:36:59 +0530 Subject: [PATCH 4/8] JoinFragment: explicitly remove " and " clause --- .../Loader/OuterJoinableAssociation.cs | 9 ++------- src/NHibernate/SqlCommand/ANSIJoinFragment.cs | 2 +- src/NHibernate/SqlCommand/JoinFragment.cs | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/NHibernate/Loader/OuterJoinableAssociation.cs b/src/NHibernate/Loader/OuterJoinableAssociation.cs index f9d00e3da45..2411cc20237 100644 --- a/src/NHibernate/Loader/OuterJoinableAssociation.cs +++ b/src/NHibernate/Loader/OuterJoinableAssociation.cs @@ -34,12 +34,7 @@ public OuterJoinableAssociation(IAssociationType joinableType, String lhsAlias, rhsColumns = JoinHelper.GetRHSColumnNames(joinableType, factory); on = new SqlString(joinableType.GetOnCondition(rhsAlias, factory, enabledFilters)); if (SqlStringHelper.IsNotEmpty(withClause)) - { - on = on.Count == 0 - ? on.Append(withClause) - : on.Append(" and ( ").Append(withClause).Append(" )"); - } - + on = on.Append(" and ( ").Append(withClause).Append(" )"); this.enabledFilters = enabledFilters; // needed later for many-to-many/filter application } @@ -161,7 +156,7 @@ public void AddManyToManyJoin(JoinFragment outerjoin, IQueryableCollection colle string manyToManyFilter = collection.GetManyToManyFilterFragment(rhsAlias, enabledFilters); SqlString condition = string.Empty.Equals(manyToManyFilter) ? on - : SqlStringHelper.IsEmpty(on) ? new SqlString(manyToManyFilter) : + : SqlStringHelper.IsEmpty(on) ? new SqlString(manyToManyFilter) : on.Append(" and ").Append(manyToManyFilter); outerjoin.AddJoin(joinable.TableName, rhsAlias, lhsColumns, rhsColumns, joinType, condition); diff --git a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs index 53cc3116f15..bdce2a89569 100644 --- a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs +++ b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs @@ -42,7 +42,7 @@ public override void AddJoin(string tableName, string alias, string[] fkColumns, if (fkColumns.Length == 0) { - _fromFragment.Add(on); + AddBareCondition(_fromFragment, on); return; } diff --git a/src/NHibernate/SqlCommand/JoinFragment.cs b/src/NHibernate/SqlCommand/JoinFragment.cs index 5608ee9adeb..c91e0a7de7a 100644 --- a/src/NHibernate/SqlCommand/JoinFragment.cs +++ b/src/NHibernate/SqlCommand/JoinFragment.cs @@ -58,11 +58,25 @@ protected bool AddCondition(SqlStringBuilder buffer, string on) } } + /// + /// Adds condition to buffer without adding " and " prefix. Existing " and" prefix is removed + /// + protected void AddBareCondition(SqlStringBuilder buffer, SqlString condition) + { + if (SqlStringHelper.IsEmpty(condition)) + return; + + buffer.Add( + condition.StartsWithCaseInsensitive(" and ") + ? condition.Substring(4) + : condition); + } + protected bool AddCondition(SqlStringBuilder buffer, SqlString on) { if (SqlStringHelper.IsNotEmpty(on)) { - if (buffer.Count > 0 && !on.StartsWithCaseInsensitive(" and")) + if (!on.StartsWithCaseInsensitive(" and")) { buffer.Add(" and "); } From b75d6a53daa91079c18d05492d5c515514f369ea Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 24 Jan 2018 20:40:53 +0530 Subject: [PATCH 5/8] async regen --- .../Async/Criteria/EntityJoinCriteriaTest.cs | 74 +++++++++++++++++++ .../Impl/ISupportEntityJoinCriteria.cs | 1 + .../Loader/OuterJoinableAssociation.cs | 4 +- src/NHibernate/SqlCommand/ANSIJoinFragment.cs | 1 - 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs index 7474ae7a3fe..0317ffa6071 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs @@ -48,6 +48,25 @@ public async Task CanJoinNotAssociatedEntityAsync() } } + //check JoinEntityAlias - JoinAlias analog for entity join + [Test] + public async Task CanJoinNotAssociatedEntity_ExpressionAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + .JoinEntityAlias(() => entityComplex, () => root.Complex1Id == entityComplex.Id).Take(1) + .SingleOrDefaultAsync()); + entityComplex = await (session.LoadAsync(root.Complex1Id)); + + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + //check JoinEntityQueryOver - JoinQueryOver analog for entity join [Test] public async Task CanJoinEntityQueryOverAsync() @@ -254,6 +273,61 @@ public async Task EntityJoinForCustomEntityNameAsync() } } + [Test] + public async Task EntityJoinFoSubquery_JoinEntityAliasAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ej = null; + EntityWithNoAssociation root = null; + + EntityComplex ejSub = null; + EntityWithNoAssociation rootSub = null; + + var subquery = QueryOver.Of(() => rootSub) + .JoinEntityAlias(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) + .Where(r => ejSub.Name == ej.Name) + .Select(x => ejSub.Id); + + root = await (session.QueryOver(() => root) + .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .WithSubquery.WhereExists(subquery) + .SingleOrDefaultAsync()); + ej = await (session.LoadAsync(root.Complex1Id)); + + Assert.That(NHibernateUtil.IsInitialized(ej), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task EntityJoinFoSubquery_JoinQueryOverAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ej = null; + EntityWithNoAssociation root = null; + + EntityComplex ejSub = null; + EntityWithNoAssociation rootSub = null; + + var subquery = QueryOver.Of(() => rootSub) + .JoinEntityQueryOver(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) + .Where(x => x.Name == ej.Name) + .Select(x => ejSub.Id); + + root = await (session.QueryOver(() => root) + .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .WithSubquery.WhereExists(subquery) + .SingleOrDefaultAsync()); + ej = await (session.LoadAsync(root.Complex1Id)); + + Assert.That(NHibernateUtil.IsInitialized(ej), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } #region Test Setup protected override HbmMapping GetMappings() diff --git a/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs b/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs index aef22cedd9f..d45aa76f7cf 100644 --- a/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs +++ b/src/NHibernate/Impl/ISupportEntityJoinCriteria.cs @@ -3,6 +3,7 @@ namespace NHibernate.Impl { + // 6.0 TODO: merge into 'ICriteria'. public interface ISupportEntityJoinCriteria { ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, string entityName); diff --git a/src/NHibernate/Loader/OuterJoinableAssociation.cs b/src/NHibernate/Loader/OuterJoinableAssociation.cs index 2411cc20237..abd6849a44a 100644 --- a/src/NHibernate/Loader/OuterJoinableAssociation.cs +++ b/src/NHibernate/Loader/OuterJoinableAssociation.cs @@ -156,7 +156,7 @@ public void AddManyToManyJoin(JoinFragment outerjoin, IQueryableCollection colle string manyToManyFilter = collection.GetManyToManyFilterFragment(rhsAlias, enabledFilters); SqlString condition = string.Empty.Equals(manyToManyFilter) ? on - : SqlStringHelper.IsEmpty(on) ? new SqlString(manyToManyFilter) : + : SqlStringHelper.IsEmpty(on) ? new SqlString(manyToManyFilter) : on.Append(" and ").Append(manyToManyFilter); outerjoin.AddJoin(joinable.TableName, rhsAlias, lhsColumns, rhsColumns, joinType, condition); @@ -164,4 +164,4 @@ public void AddManyToManyJoin(JoinFragment outerjoin, IQueryableCollection colle joinable.WhereJoinFragment(rhsAlias, false, true)); } } -} +} \ No newline at end of file diff --git a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs index bdce2a89569..aa9421fa08e 100644 --- a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs +++ b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs @@ -1,4 +1,3 @@ -using NHibernate.Linq; using NHibernate.Util; namespace NHibernate.SqlCommand From 5e089e64a691c2152ec37751cb4909373c8d8e3c Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 25 Jan 2018 15:16:05 +0530 Subject: [PATCH 6/8] test cleanup --- .../Async/Criteria/EntityJoinCriteriaTest.cs | 93 +++++++++++++------ .../Criteria/EntityJoinCriteriaTest.cs | 93 +++++++++++++------ 2 files changed, 134 insertions(+), 52 deletions(-) diff --git a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs index 0317ffa6071..bce97afd549 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs @@ -38,8 +38,8 @@ public async Task CanJoinNotAssociatedEntityAsync() { EntityComplex entityComplex = null; EntityWithNoAssociation root = null; - root = await (session.QueryOver(() => root) - .JoinEntityAlias(() => entityComplex, Restrictions.Where(() => root.Complex1Id == entityComplex.Id), JoinType.InnerJoin).Take(1) + root = await (session.QueryOver(() => root) + .JoinEntityAlias(() => entityComplex, Restrictions.Where(() => root.Complex1Id == entityComplex.Id)).Take(1) .SingleOrDefaultAsync()); entityComplex = await (session.LoadAsync(root.Complex1Id)); @@ -76,8 +76,28 @@ public async Task CanJoinEntityQueryOverAsync() { EntityComplex ejQueryOver = null; EntityWithNoAssociation root = null; - root = await (session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejQueryOver, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id), JoinType.InnerJoin) + root = await (session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejQueryOver, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id)) + .Take(1) + .SingleOrDefaultAsync()); + ejQueryOver = await (session.LoadAsync(root.Complex1Id)); + + Assert.That(NHibernateUtil.IsInitialized(ejQueryOver), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + //check JoinEntityQueryOver - JoinQueryOver analog for entity join + [Test] + public async Task CanJoinEntityQueryOver_ExpressionAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejQueryOver = null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejQueryOver, () => root.Complex1Id == ejQueryOver.Id) .Take(1) .SingleOrDefaultAsync()); ejQueryOver = await (session.LoadAsync(root.Complex1Id)); @@ -96,8 +116,8 @@ public async Task CanQueryOverForAssociationInNotAssociatedEntityAsync() { EntityComplex ejComplex = null; EntityWithNoAssociation root = null; - root = await (session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) + root = await (session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejComplex, () => root.Complex1Id == ejComplex.Id) .JoinQueryOver(ej => ej.Child1) .Take(1) .SingleOrDefaultAsync()); @@ -119,8 +139,8 @@ public async Task SimpleProjectionForEntityJoinAsync() { EntityComplex ejComplex = null; EntityWithNoAssociation root = null; - var name = await (session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) + var name = await (session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejComplex, () => root.Complex1Id == ejComplex.Id) .Select((e) => ejComplex.Name) .Take(1) .SingleOrDefaultAsync()); @@ -141,9 +161,9 @@ public async Task EntityProjectionForEntityJoinAsync() EntityComplex root = null; EntityComplex st = null; var r = await (session - .QueryOver(() => root) + .QueryOver(() => root) .JoinAlias(c => c.SameTypeChild, () => st) - .JoinEntityAlias(() => ejChild1, Restrictions.Where(() => ejChild1.Id == root.Child1.Id), JoinType.InnerJoin) + .JoinEntityAlias(() => ejChild1, () => ejChild1.Id == root.Child1.Id) .Select( Projections.RootEntity(), Projections.Entity(() => st), @@ -177,11 +197,11 @@ public async Task MixOfJoinsForAssociatedAndNotAssociatedEntitiesAsync() EntityComplex entityComplexForEjLevel1 = null; EntitySimpleChild ejLevel2OnEntityComplexForEjLevel1 = null; var obj = await (session - .QueryOver(() => root) + .QueryOver(() => root) .JoinEntityAlias(() => ejLevel1, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null), JoinType.LeftOuterJoin) .JoinAlias(() => ejLevel1.Child1, () => customChildForEjLevel1, JoinType.InnerJoin) .JoinAlias(() => ejLevel1.SameTypeChild, () => entityComplexForEjLevel1, JoinType.LeftOuterJoin) - .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id), JoinType.InnerJoin) + .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, () => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id) .Where(() => customChildForEjLevel1.Id != null && ejLevel2OnEntityComplexForEjLevel1.Id != null) .Take(1) .SingleOrDefaultAsync()); @@ -197,8 +217,8 @@ public async Task EntityJoinForCompositeKeyAsync() EntityWithCompositeId ejComposite = null; EntityWithNoAssociation root = null; root = await (session - .QueryOver(() => root) - .JoinEntityAlias(() => ejComposite, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2), JoinType.InnerJoin) + .QueryOver(() => root) + .JoinEntityAlias(() => ejComposite, () => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2) .Take(1).SingleOrDefaultAsync()); var composite = await (session.LoadAsync(_entityWithCompositeId.Key)); @@ -216,9 +236,9 @@ public async Task NullLeftEntityJoinAsync() { EntityComplex ejLeftNull = null; EntityWithNoAssociation root = null; - root = await (session.QueryOver(() => root) - //add some non existent - .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) + root = await (session.QueryOver(() => root) + //add some non existent join condition + .JoinEntityAlias(() => ejLeftNull, () => ejLeftNull.Id == null, JoinType.LeftOuterJoin) .Take(1) .SingleOrDefaultAsync()); @@ -236,9 +256,9 @@ public async Task NullLeftEntityJoinWithEntityProjectionAsync() { EntityComplex ejLeftNull = null; EntityWithNoAssociation root = null; - var objs = await (session.QueryOver(() => root) - //add some non existent - .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) + var objs = await (session.QueryOver(() => root) + //add some non existent join condition + .JoinEntityAlias(() => ejLeftNull, () => ejLeftNull.Id == null, JoinType.LeftOuterJoin) .Select((e) => root.AsEntity(), e => ejLeftNull.AsEntity()) .Take(1) .SingleOrDefaultAsync()); @@ -261,12 +281,33 @@ public async Task EntityJoinForCustomEntityNameAsync() { EntityCustomEntityName ejCustomEntity= null; EntityWithNoAssociation root = null; - root = await (session.QueryOver(() => root) + root = await (session.QueryOver(() => root) .JoinEntityAlias(() => ejCustomEntity, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), JoinType.InnerJoin, customEntityName) .Take(1) .SingleOrDefaultAsync()); - ejCustomEntity = await (session.LoadAsync(root.CustomEntityNameId)); + ejCustomEntity = (EntityCustomEntityName) await (session.LoadAsync(customEntityName, root.CustomEntityNameId)); + + Assert.That(NHibernateUtil.IsInitialized(ejCustomEntity), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task EntityJoinForCustomEntityName_ExpressionAsync() + { + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityCustomEntityName ejCustomEntity= null; + EntityWithNoAssociation root = null; + root = await (session.QueryOver(() => root) + .JoinEntityAlias(() => ejCustomEntity, () => ejCustomEntity.Id == root.CustomEntityNameId, JoinType.InnerJoin, customEntityName) + .Take(1) + .SingleOrDefaultAsync()); + + ejCustomEntity = (EntityCustomEntityName) await (session.LoadAsync(customEntityName, root.CustomEntityNameId)); Assert.That(NHibernateUtil.IsInitialized(ejCustomEntity), Is.True); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); @@ -285,13 +326,13 @@ public async Task EntityJoinFoSubquery_JoinEntityAliasAsync() EntityComplex ejSub = null; EntityWithNoAssociation rootSub = null; - var subquery = QueryOver.Of(() => rootSub) + var subquery = QueryOver.Of(() => rootSub) .JoinEntityAlias(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) .Where(r => ejSub.Name == ej.Name) .Select(x => ejSub.Id); root = await (session.QueryOver(() => root) - .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .JoinEntityAlias(() => ej, () => root.Complex1Id == ej.Id) .WithSubquery.WhereExists(subquery) .SingleOrDefaultAsync()); ej = await (session.LoadAsync(root.Complex1Id)); @@ -313,13 +354,13 @@ public async Task EntityJoinFoSubquery_JoinQueryOverAsync() EntityComplex ejSub = null; EntityWithNoAssociation rootSub = null; - var subquery = QueryOver.Of(() => rootSub) + var subquery = QueryOver.Of(() => rootSub) .JoinEntityQueryOver(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) .Where(x => x.Name == ej.Name) .Select(x => ejSub.Id); root = await (session.QueryOver(() => root) - .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .JoinEntityAlias(() => ej, () => root.Complex1Id == ej.Id) .WithSubquery.WhereExists(subquery) .SingleOrDefaultAsync()); ej = await (session.LoadAsync(root.Complex1Id)); diff --git a/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs index 7cc7daeeaeb..aab9ea3fa94 100644 --- a/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs +++ b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs @@ -27,8 +27,8 @@ public void CanJoinNotAssociatedEntity() { EntityComplex entityComplex = null; EntityWithNoAssociation root = null; - root = session.QueryOver(() => root) - .JoinEntityAlias(() => entityComplex, Restrictions.Where(() => root.Complex1Id == entityComplex.Id), JoinType.InnerJoin).Take(1) + root = session.QueryOver(() => root) + .JoinEntityAlias(() => entityComplex, Restrictions.Where(() => root.Complex1Id == entityComplex.Id)).Take(1) .SingleOrDefault(); entityComplex = session.Load(root.Complex1Id); @@ -65,8 +65,28 @@ public void CanJoinEntityQueryOver() { EntityComplex ejQueryOver = null; EntityWithNoAssociation root = null; - root = session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejQueryOver, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id), JoinType.InnerJoin) + root = session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejQueryOver, Restrictions.Where(() => root.Complex1Id == ejQueryOver.Id)) + .Take(1) + .SingleOrDefault(); + ejQueryOver = session.Load(root.Complex1Id); + + Assert.That(NHibernateUtil.IsInitialized(ejQueryOver), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + //check JoinEntityQueryOver - JoinQueryOver analog for entity join + [Test] + public void CanJoinEntityQueryOver_Expression() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex ejQueryOver = null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejQueryOver, () => root.Complex1Id == ejQueryOver.Id) .Take(1) .SingleOrDefault(); ejQueryOver = session.Load(root.Complex1Id); @@ -85,8 +105,8 @@ public void CanQueryOverForAssociationInNotAssociatedEntity() { EntityComplex ejComplex = null; EntityWithNoAssociation root = null; - root = session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) + root = session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejComplex, () => root.Complex1Id == ejComplex.Id) .JoinQueryOver(ej => ej.Child1) .Take(1) .SingleOrDefault(); @@ -108,8 +128,8 @@ public void SimpleProjectionForEntityJoin() { EntityComplex ejComplex = null; EntityWithNoAssociation root = null; - var name = session.QueryOver(() => root) - .JoinEntityQueryOver(() => ejComplex, Restrictions.Where(() => root.Complex1Id == ejComplex.Id), JoinType.InnerJoin) + var name = session.QueryOver(() => root) + .JoinEntityQueryOver(() => ejComplex, () => root.Complex1Id == ejComplex.Id) .Select((e) => ejComplex.Name) .Take(1) .SingleOrDefault(); @@ -130,9 +150,9 @@ public void EntityProjectionForEntityJoin() EntityComplex root = null; EntityComplex st = null; var r = session - .QueryOver(() => root) + .QueryOver(() => root) .JoinAlias(c => c.SameTypeChild, () => st) - .JoinEntityAlias(() => ejChild1, Restrictions.Where(() => ejChild1.Id == root.Child1.Id), JoinType.InnerJoin) + .JoinEntityAlias(() => ejChild1, () => ejChild1.Id == root.Child1.Id) .Select( Projections.RootEntity(), Projections.Entity(() => st), @@ -166,11 +186,11 @@ public void MixOfJoinsForAssociatedAndNotAssociatedEntities() EntityComplex entityComplexForEjLevel1 = null; EntitySimpleChild ejLevel2OnEntityComplexForEjLevel1 = null; var obj = session - .QueryOver(() => root) + .QueryOver(() => root) .JoinEntityAlias(() => ejLevel1, Restrictions.Where(() => ejLevel1.Id == root.SameTypeChild.Id && root.Id != null), JoinType.LeftOuterJoin) .JoinAlias(() => ejLevel1.Child1, () => customChildForEjLevel1, JoinType.InnerJoin) .JoinAlias(() => ejLevel1.SameTypeChild, () => entityComplexForEjLevel1, JoinType.LeftOuterJoin) - .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, Restrictions.Where(() => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id), JoinType.InnerJoin) + .JoinEntityAlias(() => ejLevel2OnEntityComplexForEjLevel1, () => entityComplexForEjLevel1.Id == ejLevel2OnEntityComplexForEjLevel1.Id) .Where(() => customChildForEjLevel1.Id != null && ejLevel2OnEntityComplexForEjLevel1.Id != null) .Take(1) .SingleOrDefault(); @@ -186,8 +206,8 @@ public void EntityJoinForCompositeKey() EntityWithCompositeId ejComposite = null; EntityWithNoAssociation root = null; root = session - .QueryOver(() => root) - .JoinEntityAlias(() => ejComposite, Restrictions.Where(() => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2), JoinType.InnerJoin) + .QueryOver(() => root) + .JoinEntityAlias(() => ejComposite, () => root.Composite1Key1 == ejComposite.Key.Id1 && root.Composite1Key2 == ejComposite.Key.Id2) .Take(1).SingleOrDefault(); var composite = session.Load(_entityWithCompositeId.Key); @@ -205,9 +225,9 @@ public void NullLeftEntityJoin() { EntityComplex ejLeftNull = null; EntityWithNoAssociation root = null; - root = session.QueryOver(() => root) - //add some non existent - .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) + root = session.QueryOver(() => root) + //add some non existent join condition + .JoinEntityAlias(() => ejLeftNull, () => ejLeftNull.Id == null, JoinType.LeftOuterJoin) .Take(1) .SingleOrDefault(); @@ -225,9 +245,9 @@ public void NullLeftEntityJoinWithEntityProjection() { EntityComplex ejLeftNull = null; EntityWithNoAssociation root = null; - var objs = session.QueryOver(() => root) - //add some non existent - .JoinEntityAlias(() => ejLeftNull, Restrictions.Where(() => ejLeftNull.Id == null), JoinType.LeftOuterJoin) + var objs = session.QueryOver(() => root) + //add some non existent join condition + .JoinEntityAlias(() => ejLeftNull, () => ejLeftNull.Id == null, JoinType.LeftOuterJoin) .Select((e) => root.AsEntity(), e => ejLeftNull.AsEntity()) .Take(1) .SingleOrDefault(); @@ -250,12 +270,33 @@ public void EntityJoinForCustomEntityName() { EntityCustomEntityName ejCustomEntity= null; EntityWithNoAssociation root = null; - root = session.QueryOver(() => root) + root = session.QueryOver(() => root) .JoinEntityAlias(() => ejCustomEntity, Restrictions.Where(() => ejCustomEntity.Id == root.CustomEntityNameId), JoinType.InnerJoin, customEntityName) .Take(1) .SingleOrDefault(); - ejCustomEntity = session.Load(root.CustomEntityNameId); + ejCustomEntity = (EntityCustomEntityName) session.Load(customEntityName, root.CustomEntityNameId); + + Assert.That(NHibernateUtil.IsInitialized(ejCustomEntity), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void EntityJoinForCustomEntityName_Expression() + { + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityCustomEntityName ejCustomEntity= null; + EntityWithNoAssociation root = null; + root = session.QueryOver(() => root) + .JoinEntityAlias(() => ejCustomEntity, () => ejCustomEntity.Id == root.CustomEntityNameId, JoinType.InnerJoin, customEntityName) + .Take(1) + .SingleOrDefault(); + + ejCustomEntity = (EntityCustomEntityName) session.Load(customEntityName, root.CustomEntityNameId); Assert.That(NHibernateUtil.IsInitialized(ejCustomEntity), Is.True); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); @@ -274,13 +315,13 @@ public void EntityJoinFoSubquery_JoinEntityAlias() EntityComplex ejSub = null; EntityWithNoAssociation rootSub = null; - var subquery = QueryOver.Of(() => rootSub) + var subquery = QueryOver.Of(() => rootSub) .JoinEntityAlias(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) .Where(r => ejSub.Name == ej.Name) .Select(x => ejSub.Id); root = session.QueryOver(() => root) - .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .JoinEntityAlias(() => ej, () => root.Complex1Id == ej.Id) .WithSubquery.WhereExists(subquery) .SingleOrDefault(); ej = session.Load(root.Complex1Id); @@ -302,13 +343,13 @@ public void EntityJoinFoSubquery_JoinQueryOver() EntityComplex ejSub = null; EntityWithNoAssociation rootSub = null; - var subquery = QueryOver.Of(() => rootSub) + var subquery = QueryOver.Of(() => rootSub) .JoinEntityQueryOver(() => ejSub, () => rootSub.Complex1Id == ejSub.Id) .Where(x => x.Name == ej.Name) .Select(x => ejSub.Id); root = session.QueryOver(() => root) - .JoinEntityAlias(() => ej, Restrictions.Where(() => root.Complex1Id == ej.Id)) + .JoinEntityAlias(() => ej, () => root.Complex1Id == ej.Id) .WithSubquery.WhereExists(subquery) .SingleOrDefault(); ej = session.Load(root.Complex1Id); From 8ac4b7b02d90e1c2a984c0215cb5e529daa51d19 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 25 Jan 2018 15:45:21 +0530 Subject: [PATCH 7/8] Simplified ISupportEntityJoinQueryOver interface --- .../Criterion/ISupportEntityJoinQueryOver.cs | 8 +------- src/NHibernate/Criterion/QueryOver.cs | 12 +----------- src/NHibernate/EntityJoinExtensions.cs | 10 +++++++--- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs b/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs index 1dd8a089a97..11a91d8fa05 100644 --- a/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs +++ b/src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs @@ -4,14 +4,8 @@ namespace NHibernate.Criterion { - // 6.0 TODO: merge into IQueryOver. - public interface ISupportEntityJoinQueryOver - { - void JoinEntityAlias(Expression> alias, ICriterion withClause, JoinType joinType, string entityName); - } - // 6.0 TODO: merge into IQueryOver. - public interface ISupportEntityJoinQueryOver: ISupportEntityJoinQueryOver + public interface ISupportEntityJoinQueryOver { IQueryOver JoinEntityQueryOver(Expression> alias, ICriterion withClause, JoinType joinType, string entityName); } diff --git a/src/NHibernate/Criterion/QueryOver.cs b/src/NHibernate/Criterion/QueryOver.cs index 03e6b98bfe8..7e633541da4 100644 --- a/src/NHibernate/Criterion/QueryOver.cs +++ b/src/NHibernate/Criterion/QueryOver.cs @@ -670,7 +670,7 @@ public QueryOver JoinEntityQueryOver(Expression> alias, Exp public QueryOver JoinEntityQueryOver(Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) { - return Create(CreateEntityCriteria(alias, joinType, withClause, entityName)); + return Create(criteria.CreateEntityCriteria(alias, withClause, joinType, entityName)); } public QueryOver JoinAlias(Expression> path, Expression> alias) @@ -773,11 +773,6 @@ private QueryOver AddAlias(string path, string alias, JoinType j return this; } - private ICriteria CreateEntityCriteria(Expression> alias, JoinType joinType, ICriterion withClause, string entityName) - { - return criteria.CreateEntityCriteria(ExpressionProcessor.FindMemberExpression(alias.Body), withClause, joinType, entityName ?? typeof(U).FullName); - } - private QueryOver Add(Expression> expression) { criteria.Add(ExpressionProcessor.ProcessExpression(expression)); @@ -999,11 +994,6 @@ IQueryOver ISupportEntityJoinQueryOver.JoinEntityQueryOver(E return JoinEntityQueryOver(alias, withClause, joinType, entityName); } - void ISupportEntityJoinQueryOver.JoinEntityAlias(Expression> alias, ICriterion withClause, JoinType joinType, string entityName) - { - CreateEntityCriteria(alias, joinType, withClause, entityName); - } - IQueryOverJoinBuilder IQueryOver.Inner { get { return new IQueryOverJoinBuilder(this, JoinType.InnerJoin); } } diff --git a/src/NHibernate/EntityJoinExtensions.cs b/src/NHibernate/EntityJoinExtensions.cs index 0bc957fd8de..e895d2d8dc3 100644 --- a/src/NHibernate/EntityJoinExtensions.cs +++ b/src/NHibernate/EntityJoinExtensions.cs @@ -10,8 +10,7 @@ public static class EntityJoinExtensions { public static TThis JoinEntityAlias(this TThis queryOver, Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) where TThis : IQueryOver { - var q = CastOrThrow(queryOver); - q.JoinEntityAlias(alias, withClause, joinType, entityName); + CreateEntityCriteria(queryOver.UnderlyingCriteria, alias, withClause, joinType, entityName); return queryOver; } @@ -37,12 +36,17 @@ public static ICriteria CreateEntityAlias(this ICriteria criteria, string alias, return criteria; } - public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType, string entityName) + public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) { var c = CastOrThrow(criteria); return c.CreateEntityCriteria(alias, withClause, joinType, entityName); } + public static ICriteria CreateEntityCriteria(this ICriteria criteria, Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) + { + return CreateEntityCriteria(criteria, ExpressionProcessor.FindMemberExpression(alias.Body), withClause, joinType, entityName ?? typeof(U).FullName); + } + private static T CastOrThrow(object obj) where T : class { return obj as T From 3f6bb9426ff6f67585d17a27ab3dad45235eba98 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 28 Jan 2018 09:20:41 +0530 Subject: [PATCH 8/8] Fixed exception message, added one more Criteria extension --- src/NHibernate/EntityJoinExtensions.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/EntityJoinExtensions.cs b/src/NHibernate/EntityJoinExtensions.cs index e895d2d8dc3..ff88664b36d 100644 --- a/src/NHibernate/EntityJoinExtensions.cs +++ b/src/NHibernate/EntityJoinExtensions.cs @@ -8,6 +8,8 @@ namespace NHibernate { public static class EntityJoinExtensions { + #region QueryOver extensions + public static TThis JoinEntityAlias(this TThis queryOver, Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) where TThis : IQueryOver { CreateEntityCriteria(queryOver.UnderlyingCriteria, alias, withClause, joinType, entityName); @@ -30,13 +32,23 @@ public static IQueryOver JoinEntityQueryOver(thi return q.JoinEntityQueryOver(alias, withClause, joinType, entityName); } + #endregion QueryOver extensions + + #region Criteria extensions + public static ICriteria CreateEntityAlias(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType, string entityName) { CreateEntityCriteria(criteria, alias, withClause, joinType, entityName); return criteria; } + + public static ICriteria CreateEntityAlias(this ICriteria criteria, Expression> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) + { + CreateEntityCriteria(criteria, alias, withClause, joinType, entityName); + return criteria; + } - public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) + public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType, string entityName) { var c = CastOrThrow(criteria); return c.CreateEntityCriteria(alias, withClause, joinType, entityName); @@ -47,10 +59,12 @@ public static ICriteria CreateEntityCriteria(this ICriteria criteria, Express return CreateEntityCriteria(criteria, ExpressionProcessor.FindMemberExpression(alias.Body), withClause, joinType, entityName ?? typeof(U).FullName); } + #endregion Criteria extensions + private static T CastOrThrow(object obj) where T : class { return obj as T - ?? throw new ArgumentException($"{obj.GetType().FullName} requires to implement {nameof(T)} interface to support explicit entity joins."); + ?? throw new ArgumentException($"{obj.GetType().FullName} requires to implement {typeof(T).FullName} interface to support explicit entity joins."); } } }