Skip to content

Fix join on interface in Linq/hql #2810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/NHibernate.Test/Async/Linq/ByMethod/JoinTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,14 @@ public async Task CanInnerJoinOnSubclassWithBaseTableReferenceInOnClauseAsync()
join o2 in db.Mammals on o.BodyWeight equals o2.BodyWeight
select new { o, o2 }).Take(1).ToListAsync());
}

[Test(Description = "GH-2805")]
public async Task CanJoinOnInterfaceAsync()
{
var result = await (db.IUsers.Join(db.IUsers,
u => u.Id,
iu => iu.Id,
(u, iu) => iu.Name).Take(1).ToListAsync());
}
}
}
9 changes: 9 additions & 0 deletions src/NHibernate.Test/Linq/ByMethod/JoinTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,14 @@ public void CanInnerJoinOnSubclassWithBaseTableReferenceInOnClause()
join o2 in db.Mammals on o.BodyWeight equals o2.BodyWeight
select new { o, o2 }).Take(1).ToList();
}

[Test(Description = "GH-2805")]
public void CanJoinOnInterface()
{
var result = db.IUsers.Join(db.IUsers,
u => u.Id,
iu => iu.Id,
(u, iu) => iu.Name).Take(1).ToList();
}
}
}
32 changes: 27 additions & 5 deletions src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,17 +792,39 @@ private EntityJoinFromElement CreateEntityJoin(

private IQueryable ResolveEntityJoinReferencedPersister(IASTNode path)
{
string entityName = GetEntityJoinCandidateEntityName(path);

var persister = SessionFactoryHelper.FindQueryableUsingImports(entityName);
if (persister == null && entityName != null)
{
var implementors = SessionFactoryHelper.Factory.GetImplementors(entityName);
//Possible case - join on interface
if (implementors.Length == 1)
persister = SessionFactoryHelper.FindQueryableUsingImports(implementors[0]);
}

if (persister != null)
return persister;

if (path.Type == IDENT)
{
var pathIdentNode = (IdentNode) path;
// Since IDENT node is not expected for implicit join path, we can throw on not found persister
return (IQueryable) SessionFactoryHelper.RequireClassPersister(pathIdentNode.Path);
throw new QuerySyntaxException(entityName + " is not mapped");
}
else if (path.Type == DOT)

return null;
}

private static string GetEntityJoinCandidateEntityName(IASTNode path)
{
switch (path.Type)
{
var pathText = ASTUtil.GetPathText(path);
return SessionFactoryHelper.FindQueryableUsingImports(pathText);
case IDENT:
return ((IdentNode) path).Path;
case DOT:
return ASTUtil.GetPathText(path);
}

return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Impl/SessionFactoryImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ public string[] GetImplementors(string entityOrClassName)
{
// try to get the class from imported names
string importedName = GetImportedClassName(entityOrClassName);
if (importedName != null)
if (importedName != entityOrClassName)
{
clazz = System.Type.GetType(importedName, false);
}
Expand Down
15 changes: 8 additions & 7 deletions src/NHibernate/Linq/Visitors/QueryModelVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -570,16 +570,17 @@ public bool ContainsBaseMember(JoinClause joinClause)
{
// Visit the join inner key only for entities that have subclasses
if (joinClause.InnerSequence is ConstantExpression constantNode &&
constantNode.Value is IEntityNameProvider entityNameProvider &&
!_sessionFactory.GetEntityPersister(entityNameProvider.EntityName).EntityMetamodel.HasSubclasses)
constantNode.Value is IEntityNameProvider &&
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes in this file should be ignored when merged to master (5.3 specific fix)

ExpressionsHelper.TryGetMappedType(_sessionFactory, constantNode, out _, out var _persister, out _, out _) &&
_persister?.EntityMetamodel.HasSubclasses == true)
{
return false;
}
_result = false;
Visit(joinClause.InnerKeySelector);

_result = false;
Visit(joinClause.InnerKeySelector);
return _result;
}

return _result;
return false;
}

protected override Expression VisitMember(MemberExpression node)
Expand Down