Skip to content

Support to join not associated entities in Criteria (aka Entity Join) #1545

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 8 commits into from
Jan 30, 2018
Merged
Show file tree
Hide file tree
Changes from 7 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
546 changes: 546 additions & 0 deletions src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs

Large diffs are not rendered by default.

548 changes: 548 additions & 0 deletions src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/NHibernate/Async/Impl/CriteriaImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IList> ListAsync(CancellationToken cancellationToken = default(CancellationToken))
Expand Down
12 changes: 12 additions & 0 deletions src/NHibernate/Criterion/ISupportEntityJoinQueryOver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Linq.Expressions;
using NHibernate.SqlCommand;

namespace NHibernate.Criterion
{
// 6.0 TODO: merge into IQueryOver<TRoot,TSubType>.
public interface ISupportEntityJoinQueryOver<TRoot>
{
IQueryOver<TRoot, U> JoinEntityQueryOver<U>(Expression<Func<U>> alias, ICriterion withClause, JoinType joinType, string entityName);
}
}
22 changes: 21 additions & 1 deletion src/NHibernate/Criterion/QueryOver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ internal static Exception GetDirectUsageException()
[Serializable]
public abstract partial class QueryOver<TRoot> : QueryOver, IQueryOver<TRoot>
{
protected internal QueryOver<TRoot, TSubType> Create<TSubType>(ICriteria criteria)
{
return new QueryOver<TRoot, TSubType>(impl, criteria);
}

private IList<TRoot> List()
{
Expand Down Expand Up @@ -280,7 +284,8 @@ IQueryOver<TRoot> IQueryOver<TRoot>.ReadOnly()
/// Implementation of the <see cref="IQueryOver&lt;TRoot, TSubType&gt;"/> interface
/// </summary>
[Serializable]
public class QueryOver<TRoot,TSubType> : QueryOver<TRoot>, IQueryOver<TRoot,TSubType>
public class QueryOver<TRoot,TSubType> : QueryOver<TRoot>, IQueryOver<TRoot,TSubType>,
ISupportEntityJoinQueryOver<TRoot>
Copy link
Member

Choose a reason for hiding this comment

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

I have not seen the reason for implementing this interface on this class rather than on QueryOver<TRoot>. Why this choice?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe because that is the way it looks to be done for other similar cases. But then, ISupportEntityJoinQueryOver<TRoot> "6.0 todo" should be "merge in IQueryOver<TRoot, TSubType>.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I've just put it next to existing JoinAlias/JoinQueryOver methods. Looks more natural to have them in the same place.

{

protected internal QueryOver()
Expand Down Expand Up @@ -658,6 +663,16 @@ public QueryOver<TRoot,U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path
joinType));
}

public QueryOver<TRoot, U> JoinEntityQueryOver<U>(Expression<Func<U>> alias, Expression<Func<bool>> withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
{
return JoinEntityQueryOver(alias, Restrictions.Where(withClause), joinType, entityName);
}

public QueryOver<TRoot, U> JoinEntityQueryOver<U>(Expression<Func<U>> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
{
return Create<U>(criteria.CreateEntityCriteria(alias, withClause, joinType, entityName));
}

public QueryOver<TRoot,TSubType> JoinAlias(Expression<Func<TSubType, object>> path, Expression<Func<object>> alias)
{
return AddAlias(
Expand Down Expand Up @@ -974,6 +989,11 @@ IQueryOver<TRoot,TSubType> IQueryOver<TRoot,TSubType>.JoinAlias<U>(Expression<Fu
IQueryOver<TRoot,TSubType> IQueryOver<TRoot,TSubType>.JoinAlias<U>(Expression<Func<IEnumerable<U>>> path, Expression<Func<U>> alias, JoinType joinType, ICriterion withClause)
{ return JoinAlias(path, alias, joinType, withClause); }

IQueryOver<TRoot, U> ISupportEntityJoinQueryOver<TRoot>.JoinEntityQueryOver<U>(Expression<Func<U>> alias, ICriterion withClause, JoinType joinType, string entityName)
{
return JoinEntityQueryOver(alias, withClause, joinType, entityName);
}

IQueryOverJoinBuilder<TRoot,TSubType> IQueryOver<TRoot,TSubType>.Inner
{ get { return new IQueryOverJoinBuilder<TRoot,TSubType>(this, JoinType.InnerJoin); } }

Expand Down
56 changes: 56 additions & 0 deletions src/NHibernate/EntityJoinExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Linq.Expressions;
using NHibernate.Criterion;
using NHibernate.Impl;
using NHibernate.SqlCommand;

namespace NHibernate
{
public static class EntityJoinExtensions
{
public static TThis JoinEntityAlias<TThis, TEntity>(this TThis queryOver, Expression<Func<TEntity>> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) where TThis : IQueryOver
{
CreateEntityCriteria(queryOver.UnderlyingCriteria, alias, withClause, joinType, entityName);
return queryOver;
}

public static TThis JoinEntityAlias<TThis, TEntity>(this TThis queryOver, Expression<Func<TEntity>> alias, Expression<Func<bool>> withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null) where TThis : IQueryOver
{
return JoinEntityAlias(queryOver, alias, Restrictions.Where(withClause), joinType, entityName);
}

public static IQueryOver<TRoot, TEntity> JoinEntityQueryOver<TRoot, TEntity>(this IQueryOver<TRoot> queryOver, Expression<Func<TEntity>> alias, Expression<Func<bool>> withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
{
return JoinEntityQueryOver(queryOver, alias, Restrictions.Where(withClause), joinType, entityName);
}

public static IQueryOver<TRoot, TEntity> JoinEntityQueryOver<TRoot, TEntity>(this IQueryOver<TRoot> queryOver, Expression<Func<TEntity>> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
{
var q = CastOrThrow<ISupportEntityJoinQueryOver<TRoot>>(queryOver);
return q.JoinEntityQueryOver(alias, withClause, joinType, entityName);
}

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 CreateEntityCriteria(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
{
var c = CastOrThrow<ISupportEntityJoinCriteria>(criteria);
return c.CreateEntityCriteria(alias, withClause, joinType, entityName);
}

public static ICriteria CreateEntityCriteria<U>(this ICriteria criteria, Expression<Func<U>> 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<T>(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.");
}
}
}
20 changes: 18 additions & 2 deletions src/NHibernate/Impl/CriteriaImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace NHibernate.Impl
/// Implementation of the <see cref="ICriteria"/> interface
/// </summary>
[Serializable]
public partial class CriteriaImpl : ICriteria
public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria
{
private readonly System.Type persistentClass;
private readonly List<CriterionEntry> criteria = new List<CriterionEntry>();
Expand Down Expand Up @@ -371,6 +371,11 @@ public ICriteria CreateAlias(string associationPath, string alias, JoinType join
return this;
}

public ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, 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));
Expand Down Expand Up @@ -647,14 +652,15 @@ 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;
this.alias = alias;
this.path = path;
this.joinType = joinType;
this.withClause = withClause;
JoinEntityName = joinEntityName;

root.subcriteriaList.Add(this);

Expand All @@ -668,6 +674,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) { }

/// <summary>
/// Entity name for "Entity Join" - join for entity with not mapped association
/// </summary>
public string JoinEntityName { get; }

/// <summary>
/// Is this an Entity join for not mapped association
/// </summary>
public bool IsEntityJoin => JoinEntityName != null;

public ICriterion WithClause
{
get { return withClause; }
Expand Down
11 changes: 11 additions & 0 deletions src/NHibernate/Impl/ISupportEntityJoinCriteria.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using NHibernate.Criterion;
using NHibernate.SqlCommand;

namespace NHibernate.Impl
{
// 6.0 TODO: merge into 'ICriteria'.
public interface ISupportEntityJoinCriteria
Copy link
Member

Choose a reason for hiding this comment

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

I would add a " 6.0 TODO: merge with ICriteria". Is there a reason for not aiming at this?

{
ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, string entityName);
}
}
9 changes: 7 additions & 2 deletions src/NHibernate/Loader/AbstractEntityJoinWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<OuterJoinableAssociation> allAssociations = new List<OuterJoinableAssociation>(associations);
allAssociations.Add(CreateAssociation(persister.EntityType, alias));

Expand All @@ -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<string, IFilter> enabledFilters, LockMode lockMode, IList<EntityProjection> entityProjections)
{
WalkEntityTree(persister, Alias);
AddAssociations();

int countEntities = entityProjections.Count;
if (countEntities > 0)
Expand Down Expand Up @@ -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(
Expand Down
45 changes: 32 additions & 13 deletions src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ 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)
Expand Down Expand Up @@ -199,19 +214,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)
Expand All @@ -220,6 +223,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)
{
Expand Down
Loading