Skip to content

Commit 909fac2

Browse files
bahusoidfredericDelaporte
authored andcommitted
Support to join not associated entities in Criteria (aka Entity Join) (#1545)
1 parent a6a3c53 commit 909fac2

14 files changed

+1384
-47
lines changed

Diff for: src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs

+546
Large diffs are not rendered by default.

Diff for: src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs

+548
Large diffs are not rendered by default.

Diff for: src/NHibernate/Async/Impl/CriteriaImpl.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
namespace NHibernate.Impl
2323
{
2424
using System.Threading.Tasks;
25-
public partial class CriteriaImpl : ICriteria
25+
public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria
2626
{
2727

2828
public async Task<IList> ListAsync(CancellationToken cancellationToken = default(CancellationToken))
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using NHibernate.SqlCommand;
4+
5+
namespace NHibernate.Criterion
6+
{
7+
// 6.0 TODO: merge into IQueryOver<TRoot,TSubType>.
8+
public interface ISupportEntityJoinQueryOver<TRoot>
9+
{
10+
IQueryOver<TRoot, U> JoinEntityQueryOver<U>(Expression<Func<U>> alias, ICriterion withClause, JoinType joinType, string entityName);
11+
}
12+
}

Diff for: src/NHibernate/Criterion/QueryOver.cs

+21-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ internal static Exception GetDirectUsageException()
6565
[Serializable]
6666
public abstract partial class QueryOver<TRoot> : QueryOver, IQueryOver<TRoot>
6767
{
68+
protected internal QueryOver<TRoot, TSubType> Create<TSubType>(ICriteria criteria)
69+
{
70+
return new QueryOver<TRoot, TSubType>(impl, criteria);
71+
}
6872

6973
private IList<TRoot> List()
7074
{
@@ -280,7 +284,8 @@ IQueryOver<TRoot> IQueryOver<TRoot>.ReadOnly()
280284
/// Implementation of the <see cref="IQueryOver&lt;TRoot, TSubType&gt;"/> interface
281285
/// </summary>
282286
[Serializable]
283-
public class QueryOver<TRoot,TSubType> : QueryOver<TRoot>, IQueryOver<TRoot,TSubType>
287+
public class QueryOver<TRoot,TSubType> : QueryOver<TRoot>, IQueryOver<TRoot,TSubType>,
288+
ISupportEntityJoinQueryOver<TRoot>
284289
{
285290

286291
protected internal QueryOver()
@@ -658,6 +663,16 @@ public QueryOver<TRoot,U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path
658663
joinType));
659664
}
660665

666+
public QueryOver<TRoot, U> JoinEntityQueryOver<U>(Expression<Func<U>> alias, Expression<Func<bool>> withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
667+
{
668+
return JoinEntityQueryOver(alias, Restrictions.Where(withClause), joinType, entityName);
669+
}
670+
671+
public QueryOver<TRoot, U> JoinEntityQueryOver<U>(Expression<Func<U>> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
672+
{
673+
return Create<U>(criteria.CreateEntityCriteria(alias, withClause, joinType, entityName));
674+
}
675+
661676
public QueryOver<TRoot,TSubType> JoinAlias(Expression<Func<TSubType, object>> path, Expression<Func<object>> alias)
662677
{
663678
return AddAlias(
@@ -974,6 +989,11 @@ IQueryOver<TRoot,TSubType> IQueryOver<TRoot,TSubType>.JoinAlias<U>(Expression<Fu
974989
IQueryOver<TRoot,TSubType> IQueryOver<TRoot,TSubType>.JoinAlias<U>(Expression<Func<IEnumerable<U>>> path, Expression<Func<U>> alias, JoinType joinType, ICriterion withClause)
975990
{ return JoinAlias(path, alias, joinType, withClause); }
976991

992+
IQueryOver<TRoot, U> ISupportEntityJoinQueryOver<TRoot>.JoinEntityQueryOver<U>(Expression<Func<U>> alias, ICriterion withClause, JoinType joinType, string entityName)
993+
{
994+
return JoinEntityQueryOver(alias, withClause, joinType, entityName);
995+
}
996+
977997
IQueryOverJoinBuilder<TRoot,TSubType> IQueryOver<TRoot,TSubType>.Inner
978998
{ get { return new IQueryOverJoinBuilder<TRoot,TSubType>(this, JoinType.InnerJoin); } }
979999

Diff for: src/NHibernate/EntityJoinExtensions.cs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using NHibernate.Criterion;
4+
using NHibernate.Impl;
5+
using NHibernate.SqlCommand;
6+
7+
namespace NHibernate
8+
{
9+
public static class EntityJoinExtensions
10+
{
11+
#region QueryOver extensions
12+
13+
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
14+
{
15+
CreateEntityCriteria(queryOver.UnderlyingCriteria, alias, withClause, joinType, entityName);
16+
return queryOver;
17+
}
18+
19+
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
20+
{
21+
return JoinEntityAlias(queryOver, alias, Restrictions.Where(withClause), joinType, entityName);
22+
}
23+
24+
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)
25+
{
26+
return JoinEntityQueryOver(queryOver, alias, Restrictions.Where(withClause), joinType, entityName);
27+
}
28+
29+
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)
30+
{
31+
var q = CastOrThrow<ISupportEntityJoinQueryOver<TRoot>>(queryOver);
32+
return q.JoinEntityQueryOver(alias, withClause, joinType, entityName);
33+
}
34+
35+
#endregion QueryOver extensions
36+
37+
#region Criteria extensions
38+
39+
public static ICriteria CreateEntityAlias(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType, string entityName)
40+
{
41+
CreateEntityCriteria(criteria, alias, withClause, joinType, entityName);
42+
return criteria;
43+
}
44+
45+
public static ICriteria CreateEntityAlias<U>(this ICriteria criteria, Expression<Func<U>> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
46+
{
47+
CreateEntityCriteria(criteria, alias, withClause, joinType, entityName);
48+
return criteria;
49+
}
50+
51+
public static ICriteria CreateEntityCriteria(this ICriteria criteria, string alias, ICriterion withClause, JoinType joinType, string entityName)
52+
{
53+
var c = CastOrThrow<ISupportEntityJoinCriteria>(criteria);
54+
return c.CreateEntityCriteria(alias, withClause, joinType, entityName);
55+
}
56+
57+
public static ICriteria CreateEntityCriteria<U>(this ICriteria criteria, Expression<Func<U>> alias, ICriterion withClause, JoinType joinType = JoinType.InnerJoin, string entityName = null)
58+
{
59+
return CreateEntityCriteria(criteria, ExpressionProcessor.FindMemberExpression(alias.Body), withClause, joinType, entityName ?? typeof(U).FullName);
60+
}
61+
62+
#endregion Criteria extensions
63+
64+
private static T CastOrThrow<T>(object obj) where T : class
65+
{
66+
return obj as T
67+
?? throw new ArgumentException($"{obj.GetType().FullName} requires to implement {typeof(T).FullName} interface to support explicit entity joins.");
68+
}
69+
}
70+
}

Diff for: src/NHibernate/Impl/CriteriaImpl.cs

+18-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace NHibernate.Impl
1515
/// Implementation of the <see cref="ICriteria"/> interface
1616
/// </summary>
1717
[Serializable]
18-
public partial class CriteriaImpl : ICriteria
18+
public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria
1919
{
2020
private readonly System.Type persistentClass;
2121
private readonly List<CriterionEntry> criteria = new List<CriterionEntry>();
@@ -371,6 +371,11 @@ public ICriteria CreateAlias(string associationPath, string alias, JoinType join
371371
return this;
372372
}
373373

374+
public ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, string entityName)
375+
{
376+
return new Subcriteria(this, this, alias, alias, joinType, withClause, entityName);
377+
}
378+
374379
public ICriteria Add(ICriteria criteriaInst, ICriterion expression)
375380
{
376381
criteria.Add(new CriterionEntry(expression, criteriaInst));
@@ -647,14 +652,15 @@ public sealed partial class Subcriteria : ICriteria
647652
private readonly JoinType joinType;
648653
private ICriterion withClause;
649654

650-
internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string alias, JoinType joinType, ICriterion withClause)
655+
internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string alias, JoinType joinType, ICriterion withClause, string joinEntityName = null)
651656
{
652657
this.root = root;
653658
this.parent = parent;
654659
this.alias = alias;
655660
this.path = path;
656661
this.joinType = joinType;
657662
this.withClause = withClause;
663+
JoinEntityName = joinEntityName;
658664

659665
root.subcriteriaList.Add(this);
660666

@@ -668,6 +674,16 @@ internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string al
668674
internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, JoinType joinType)
669675
: this(root, parent, path, null, joinType) { }
670676

677+
/// <summary>
678+
/// Entity name for "Entity Join" - join for entity with not mapped association
679+
/// </summary>
680+
public string JoinEntityName { get; }
681+
682+
/// <summary>
683+
/// Is this an Entity join for not mapped association
684+
/// </summary>
685+
public bool IsEntityJoin => JoinEntityName != null;
686+
671687
public ICriterion WithClause
672688
{
673689
get { return withClause; }

Diff for: src/NHibernate/Impl/ISupportEntityJoinCriteria.cs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using NHibernate.Criterion;
2+
using NHibernate.SqlCommand;
3+
4+
namespace NHibernate.Impl
5+
{
6+
// 6.0 TODO: merge into 'ICriteria'.
7+
public interface ISupportEntityJoinCriteria
8+
{
9+
ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, string entityName);
10+
}
11+
}

Diff for: src/NHibernate/Loader/AbstractEntityJoinWalker.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public AbstractEntityJoinWalker(string rootSqlAlias, IOuterJoinLoadable persiste
3131

3232
protected virtual void InitAll(SqlString whereString, SqlString orderByString, LockMode lockMode)
3333
{
34-
WalkEntityTree(persister, Alias);
34+
AddAssociations();
3535
IList<OuterJoinableAssociation> allAssociations = new List<OuterJoinableAssociation>(associations);
3636
allAssociations.Add(CreateAssociation(persister.EntityType, alias));
3737

@@ -48,7 +48,7 @@ protected void InitProjection(SqlString projectionString, SqlString whereString,
4848

4949
protected void InitProjection(SqlString projectionString, SqlString whereString, SqlString orderByString, SqlString groupByString, SqlString havingString, IDictionary<string, IFilter> enabledFilters, LockMode lockMode, IList<EntityProjection> entityProjections)
5050
{
51-
WalkEntityTree(persister, Alias);
51+
AddAssociations();
5252

5353
int countEntities = entityProjections.Count;
5454
if (countEntities > 0)
@@ -81,6 +81,11 @@ protected void InitProjection(SqlString projectionString, SqlString whereString,
8181
InitStatementString(projectionString, whereString, orderByString, groupByString, havingString, lockMode);
8282
}
8383

84+
protected virtual void AddAssociations()
85+
{
86+
WalkEntityTree(persister, Alias);
87+
}
88+
8489
private OuterJoinableAssociation CreateAssociation(EntityType entityType, string tableAlias)
8590
{
8691
return new OuterJoinableAssociation(

Diff for: src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs

+32-13
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ public CriteriaJoinWalker(IOuterJoinLoadable persister, CriteriaQueryTranslator
7676
}
7777
}
7878

79+
protected override void AddAssociations()
80+
{
81+
base.AddAssociations();
82+
foreach (var entityJoinInfo in translator.GetEntityJoins().Values)
83+
{
84+
var tableAlias = translator.GetSQLAlias(entityJoinInfo.Criteria);
85+
var criteriaPath = entityJoinInfo.Criteria.Alias; //path for entity join is equal to alias
86+
var persister = entityJoinInfo.Persister as IOuterJoinLoadable;
87+
AddExplicitEntityJoinAssociation(persister, tableAlias, translator.GetJoinType(criteriaPath), GetWithClause(criteriaPath));
88+
IncludeInResultIfNeeded(persister, entityJoinInfo.Criteria, tableAlias);
89+
//collect mapped associations for entity join
90+
WalkEntityTree(persister, tableAlias, criteriaPath, 1);
91+
}
92+
}
93+
7994
protected override void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth)
8095
{
8196
// NH different behavior (NH-1476, NH-1760, NH-1785)
@@ -199,19 +214,7 @@ protected override string GenerateTableAlias(int n, string path, IJoinable joina
199214
ICriteria subcriteria = translator.GetCriteria(path);
200215
sqlAlias = subcriteria == null ? null : translator.GetSQLAlias(subcriteria);
201216

202-
if (joinable.ConsumesEntityAlias() && !translator.HasProjection)
203-
{
204-
includeInResultRowList.Add(subcriteria != null && subcriteria.Alias != null);
205-
206-
if (sqlAlias != null)
207-
{
208-
if (subcriteria.Alias != null)
209-
{
210-
userAliasList.Add(subcriteria.Alias); //alias may be null
211-
resultTypeList.Add(translator.ResultType(subcriteria));
212-
}
213-
}
214-
}
217+
IncludeInResultIfNeeded(joinable, subcriteria, sqlAlias);
215218
}
216219

217220
if (sqlAlias == null)
@@ -220,6 +223,22 @@ protected override string GenerateTableAlias(int n, string path, IJoinable joina
220223
return sqlAlias;
221224
}
222225

226+
private void IncludeInResultIfNeeded(IJoinable joinable, ICriteria subcriteria, string sqlAlias)
227+
{
228+
if (joinable.ConsumesEntityAlias() && !translator.HasProjection)
229+
{
230+
includeInResultRowList.Add(subcriteria != null && subcriteria.Alias != null);
231+
232+
if (sqlAlias != null)
233+
{
234+
if (subcriteria.Alias != null)
235+
{
236+
userAliasList.Add(subcriteria.Alias); //alias may be null
237+
resultTypeList.Add(translator.ResultType(subcriteria));
238+
}
239+
}
240+
}
241+
}
223242

224243
protected override string GenerateRootAlias(string tableName)
225244
{

0 commit comments

Comments
 (0)