Skip to content

Commit b27b7f7

Browse files
authored
Fix joining with an inherited property from another table for Linq provider (#2581)
1 parent da95db4 commit b27b7f7

File tree

3 files changed

+72
-3
lines changed

3 files changed

+72
-3
lines changed

src/NHibernate.Test/Async/Linq/ByMethod/JoinTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,13 @@ public async Task CanJoinOnEntityWithSubclassesAsync()
148148
from o2 in db.Animals.Where(x => x.BodyWeight > 50)
149149
select new {o, o2}).Take(1).ToListAsync());
150150
}
151+
152+
[Test(Description = "GH-2580")]
153+
public async Task CanInnerJoinOnSubclassWithBaseTableReferenceInOnClauseAsync()
154+
{
155+
var result = await ((from o in db.Animals
156+
join o2 in db.Mammals on o.BodyWeight equals o2.BodyWeight
157+
select new { o, o2 }).Take(1).ToListAsync());
158+
}
151159
}
152160
}

src/NHibernate.Test/Linq/ByMethod/JoinTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,13 @@ public void CanJoinOnEntityWithSubclasses()
137137
from o2 in db.Animals.Where(x => x.BodyWeight > 50)
138138
select new {o, o2}).Take(1).ToList();
139139
}
140+
141+
[Test(Description = "GH-2580")]
142+
public void CanInnerJoinOnSubclassWithBaseTableReferenceInOnClause()
143+
{
144+
var result = (from o in db.Animals
145+
join o2 in db.Mammals on o.BodyWeight equals o2.BodyWeight
146+
select new { o, o2 }).Take(1).ToList();
147+
}
140148
}
141149
}

src/NHibernate/Linq/Visitors/QueryModelVisitor.cs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Linq.Expressions;
55
using System.Reflection;
6+
using NHibernate.Engine;
67
using NHibernate.Hql.Ast;
78
using NHibernate.Linq.Clauses;
89
using NHibernate.Linq.Expressions;
@@ -12,6 +13,7 @@
1213
using NHibernate.Linq.ResultOperators;
1314
using NHibernate.Linq.ReWriters;
1415
using NHibernate.Linq.Visitors.ResultOperatorProcessors;
16+
using NHibernate.Persister.Entity;
1517
using NHibernate.Util;
1618
using Remotion.Linq;
1719
using Remotion.Linq.Clauses;
@@ -527,10 +529,13 @@ private void AddJoin(JoinClause joinClause, QueryModel queryModel, bool innerJoi
527529
var withClause = equalityVisitor.Visit(joinClause.InnerKeySelector, joinClause.OuterKeySelector);
528530
var alias = _hqlTree.TreeBuilder.Alias(VisitorParameters.QuerySourceNamer.GetName(joinClause));
529531
var joinExpression = HqlGeneratorExpressionVisitor.Visit(joinClause.InnerSequence, VisitorParameters);
532+
var baseMemberCheker = new BaseMemberChecker(VisitorParameters.SessionFactory);
533+
530534
HqlTreeNode join;
531-
// When associations are located inside the inner key selector we have to use a cross join instead of an inner
532-
// join and add the condition in the where statement.
533-
if (queryModel.BodyClauses.OfType<NhJoinClause>().Any(o => o.ParentJoinClause == joinClause))
535+
// When associations or members from another table are located inside the inner key selector we have to use a cross join
536+
// instead of an inner join and add the condition in the where statement.
537+
if (queryModel.BodyClauses.OfType<NhJoinClause>().Any(o => o.ParentJoinClause == joinClause) ||
538+
queryModel.BodyClauses.OfType<JoinClause>().Any(baseMemberCheker.ContainsBaseMember))
534539
{
535540
if (!innerJoin)
536541
{
@@ -551,6 +556,54 @@ private void AddJoin(JoinClause joinClause, QueryModel queryModel, bool innerJoi
551556
_hqlTree.AddFromClause(join);
552557
}
553558

559+
private class BaseMemberChecker : NhExpressionVisitor
560+
{
561+
private readonly ISessionFactoryImplementor _sessionFactory;
562+
private bool _result;
563+
564+
public BaseMemberChecker(ISessionFactoryImplementor sessionFactory)
565+
{
566+
_sessionFactory = sessionFactory;
567+
}
568+
569+
public bool ContainsBaseMember(JoinClause joinClause)
570+
{
571+
// Visit the join inner key only for entities that have subclasses
572+
if (joinClause.InnerSequence is ConstantExpression constantNode &&
573+
constantNode.Value is IEntityNameProvider entityNameProvider &&
574+
!_sessionFactory.GetEntityPersister(entityNameProvider.EntityName).EntityMetamodel.HasSubclasses)
575+
{
576+
return false;
577+
}
578+
579+
_result = false;
580+
Visit(joinClause.InnerKeySelector);
581+
582+
return _result;
583+
}
584+
585+
protected override Expression VisitMember(MemberExpression node)
586+
{
587+
if (ExpressionsHelper.TryGetMappedType(
588+
_sessionFactory,
589+
node,
590+
out _,
591+
out var persister,
592+
out _,
593+
out var propertyPath) &&
594+
persister is IOuterJoinLoadable joinLoadable &&
595+
joinLoadable.EntityMetamodel.GetIdentifierPropertyType(propertyPath) == null &&
596+
joinLoadable.GetPropertyTableName(propertyPath) != joinLoadable.TableName
597+
)
598+
{
599+
_result = true;
600+
return node;
601+
}
602+
603+
return base.VisitMember(node);
604+
}
605+
}
606+
554607
public override void VisitGroupJoinClause(GroupJoinClause groupJoinClause, QueryModel queryModel, int index)
555608
{
556609
throw new NotImplementedException();

0 commit comments

Comments
 (0)