diff --git a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs index 67b5558ad1f..e95e9c6fb3b 100644 --- a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -666,6 +666,40 @@ private static void AssertFetchComponentManyToOne(Person person) #endregion + #region TestHqlFetchManyToOneAndComponentManyToOne + + [Test] + public async Task TestHqlFetchManyToOneAndComponentManyToOneAsync() + { + Person person; + using (var s = OpenSession()) + { + person = await (s.CreateQuery("from Person p fetch p.Address left join fetch p.Address.Continent left join fetch p.BestFriend where p.Id = 1").UniqueResultAsync()); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + [Test] + public async Task TestLinqFetchManyToOneAndComponentManyToOneAsync() + { + Person person; + using (var s = OpenSession()) + { + person = await (s.Query().Fetch(o => o.BestFriend).Fetch(o => o.Address).ThenFetch(o => o.Continent).FirstOrDefaultAsync(o => o.Id == 1)); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + private static void AssertFetchManyToOneAndComponentManyToOne(Person person) + { + AssertFetchComponentManyToOne(person); + Assert.That(NHibernateUtil.IsInitialized(person.BestFriend), Is.True); + } + + #endregion + #region FetchSubClassFormula [Test] diff --git a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs index 8e01d600671..f1a81fb7e61 100644 --- a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -655,6 +655,40 @@ private static void AssertFetchComponentManyToOne(Person person) #endregion + #region TestHqlFetchManyToOneAndComponentManyToOne + + [Test] + public void TestHqlFetchManyToOneAndComponentManyToOne() + { + Person person; + using (var s = OpenSession()) + { + person = s.CreateQuery("from Person p fetch p.Address left join fetch p.Address.Continent left join fetch p.BestFriend where p.Id = 1").UniqueResult(); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + [Test] + public void TestLinqFetchManyToOneAndComponentManyToOne() + { + Person person; + using (var s = OpenSession()) + { + person = s.Query().Fetch(o => o.BestFriend).Fetch(o => o.Address).ThenFetch(o => o.Continent).FirstOrDefault(o => o.Id == 1); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + private static void AssertFetchManyToOneAndComponentManyToOne(Person person) + { + AssertFetchComponentManyToOne(person); + Assert.That(NHibernateUtil.IsInitialized(person.BestFriend), Is.True); + } + + #endregion + #region FetchSubClassFormula [Test] diff --git a/src/NHibernate/Linq/IntermediateHqlTree.cs b/src/NHibernate/Linq/IntermediateHqlTree.cs index e9fa4bba70e..17417319e41 100644 --- a/src/NHibernate/Linq/IntermediateHqlTree.cs +++ b/src/NHibernate/Linq/IntermediateHqlTree.cs @@ -127,6 +127,11 @@ public void AddFromClause(HqlTreeNode from) _root.NodesPreOrder.OfType().First().AddChild(from); } + internal HqlRange GetFromRangeClause() + { + return _root.NodesPreOrder.OfType().First().Children.OfType().FirstOrDefault(); + } + public void AddSelectClause(HqlTreeNode select) { _root.NodesPreOrder.OfType().First().AddChild(select); diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs index 4eb9a2cbcd6..1055fa03b4f 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs @@ -19,15 +19,31 @@ public void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMode } public void Process(FetchRequestBase resultOperator, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree, string sourceAlias) + { + Process(resultOperator, queryModelVisitor, tree, null, sourceAlias); + } + + private void Process( + FetchRequestBase resultOperator, + QueryModelVisitor queryModelVisitor, + IntermediateHqlTree tree, + HqlTreeNode currentNode, + string sourceAlias) { var memberPath = tree.TreeBuilder.Dot( tree.TreeBuilder.Ident(sourceAlias), tree.TreeBuilder.Ident(resultOperator.RelationMember.Name)); - Process(resultOperator, queryModelVisitor, tree, memberPath, null); + Process(resultOperator, queryModelVisitor, tree, memberPath, currentNode, null); } - private void Process(FetchRequestBase resultOperator, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree, HqlDot memberPath, IType propType) + private void Process( + FetchRequestBase resultOperator, + QueryModelVisitor queryModelVisitor, + IntermediateHqlTree tree, + HqlDot memberPath, + HqlTreeNode currentNode, + IType propType) { if (resultOperator is FetchOneRequest) { @@ -40,8 +56,14 @@ private void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMod if (propType != null && !propType.IsAssociationType) { - tree.AddFromLastChildClause(tree.TreeBuilder.Fetch()); - tree.AddFromLastChildClause(memberPath); + if (currentNode == null) + { + currentNode = tree.GetFromRangeClause() + ?? throw new InvalidOperationException($"Property {resultOperator.RelationMember.Name} cannot be fetched for this type of query."); + } + + currentNode.AddChild(tree.TreeBuilder.Fetch()); + currentNode.AddChild(memberPath); ComponentType componentType = null; foreach (var innerFetch in resultOperator.InnerFetchRequests) @@ -61,7 +83,7 @@ private void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMod memberPath, tree.TreeBuilder.Ident(innerFetch.RelationMember.Name)); - Process(innerFetch, queryModelVisitor, tree, memberPath, componentType.Subtypes[subTypeIndex]); + Process(innerFetch, queryModelVisitor, tree, memberPath, currentNode, componentType.Subtypes[subTypeIndex]); } return; @@ -69,12 +91,13 @@ private void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMod } var alias = queryModelVisitor.Model.GetNewName("_"); - tree.AddFromClause(tree.TreeBuilder.LeftFetchJoin(memberPath, tree.TreeBuilder.Alias(alias))); + currentNode = tree.TreeBuilder.LeftFetchJoin(memberPath, tree.TreeBuilder.Alias(alias)); + tree.AddFromClause(currentNode); tree.AddDistinctRootOperator(); foreach (var innerFetch in resultOperator.InnerFetchRequests) { - Process(innerFetch, queryModelVisitor, tree, alias); + Process(innerFetch, queryModelVisitor, tree, currentNode, alias); } } }