Expand typed join syntax with no lock, outer and custom join types
This commit is contained in:
@@ -6782,9 +6782,13 @@ namespace DynamORM
|
|||||||
public enum DynamicJoinType
|
public enum DynamicJoinType
|
||||||
{
|
{
|
||||||
Inner = 0,
|
Inner = 0,
|
||||||
|
Join,
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Full
|
Full,
|
||||||
|
LeftOuter,
|
||||||
|
RightOuter,
|
||||||
|
FullOuter
|
||||||
}
|
}
|
||||||
/// <summary>Dynamic delete query builder interface.</summary>
|
/// <summary>Dynamic delete query builder interface.</summary>
|
||||||
/// <remarks>This interface it publicly available. Implementation should be hidden.</remarks>
|
/// <remarks>This interface it publicly available. Implementation should be hidden.</remarks>
|
||||||
@@ -7187,8 +7191,18 @@ namespace DynamORM
|
|||||||
/// <param name="on">Join ON predicate.</param>
|
/// <param name="on">Join ON predicate.</param>
|
||||||
/// <param name="alias">Optional alias for joined table.</param>
|
/// <param name="alias">Optional alias for joined table.</param>
|
||||||
/// <param name="joinType">Join type.</param>
|
/// <param name="joinType">Join type.</param>
|
||||||
|
/// <param name="noLock">Adds NOLOCK hint to joined source when supported by provider options.</param>
|
||||||
/// <returns>Builder instance.</returns>
|
/// <returns>Builder instance.</returns>
|
||||||
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner);
|
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner, bool noLock = false);
|
||||||
|
|
||||||
|
/// <summary>Add typed join with custom join type text (for example: CROSS APPLY).</summary>
|
||||||
|
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
|
||||||
|
/// <param name="on">Optional join ON predicate.</param>
|
||||||
|
/// <param name="alias">Optional alias for joined table.</param>
|
||||||
|
/// <param name="joinType">Join type text.</param>
|
||||||
|
/// <param name="noLock">Adds NOLOCK hint to joined source when supported by provider options.</param>
|
||||||
|
/// <returns>Builder instance.</returns>
|
||||||
|
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias, string joinType, bool noLock = false);
|
||||||
|
|
||||||
/// <summary>Add typed join using join-spec builder syntax (<c>As()</c>, join kind and <c>On()</c>).</summary>
|
/// <summary>Add typed join using join-spec builder syntax (<c>As()</c>, join kind and <c>On()</c>).</summary>
|
||||||
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
|
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
|
||||||
@@ -7415,7 +7429,7 @@ namespace DynamORM
|
|||||||
{
|
{
|
||||||
internal TypedJoinBuilder()
|
internal TypedJoinBuilder()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Inner;
|
JoinType = DynamicJoinType.Join;
|
||||||
}
|
}
|
||||||
/// <summary>Gets join alias.</summary>
|
/// <summary>Gets join alias.</summary>
|
||||||
public string Alias { get; private set; }
|
public string Alias { get; private set; }
|
||||||
@@ -7423,9 +7437,18 @@ namespace DynamORM
|
|||||||
/// <summary>Gets join type.</summary>
|
/// <summary>Gets join type.</summary>
|
||||||
public DynamicJoinType JoinType { get; private set; }
|
public DynamicJoinType JoinType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets custom join type text.</summary>
|
||||||
|
public string CustomJoinType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether joined source should use NOLOCK.</summary>
|
||||||
|
public bool UseNoLock { get; private set; }
|
||||||
|
|
||||||
/// <summary>Gets ON predicate.</summary>
|
/// <summary>Gets ON predicate.</summary>
|
||||||
public Expression<Func<TLeft, TRight, bool>> OnPredicate { get; private set; }
|
public Expression<Func<TLeft, TRight, bool>> OnPredicate { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets raw ON condition.</summary>
|
||||||
|
public string OnRawCondition { get; private set; }
|
||||||
|
|
||||||
/// <summary>Sets join alias.</summary>
|
/// <summary>Sets join alias.</summary>
|
||||||
public TypedJoinBuilder<TLeft, TRight> As(string alias)
|
public TypedJoinBuilder<TLeft, TRight> As(string alias)
|
||||||
{
|
{
|
||||||
@@ -7436,24 +7459,71 @@ namespace DynamORM
|
|||||||
public TypedJoinBuilder<TLeft, TRight> Inner()
|
public TypedJoinBuilder<TLeft, TRight> Inner()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Inner;
|
JoinType = DynamicJoinType.Inner;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>Sets plain JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> Join()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.Join;
|
||||||
|
CustomJoinType = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/// <summary>Sets LEFT JOIN.</summary>
|
/// <summary>Sets LEFT JOIN.</summary>
|
||||||
public TypedJoinBuilder<TLeft, TRight> Left()
|
public TypedJoinBuilder<TLeft, TRight> Left()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Left;
|
JoinType = DynamicJoinType.Left;
|
||||||
|
CustomJoinType = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/// <summary>Sets RIGHT JOIN.</summary>
|
/// <summary>Sets RIGHT JOIN.</summary>
|
||||||
public TypedJoinBuilder<TLeft, TRight> Right()
|
public TypedJoinBuilder<TLeft, TRight> Right()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Right;
|
JoinType = DynamicJoinType.Right;
|
||||||
|
CustomJoinType = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/// <summary>Sets FULL JOIN.</summary>
|
/// <summary>Sets FULL JOIN.</summary>
|
||||||
public TypedJoinBuilder<TLeft, TRight> Full()
|
public TypedJoinBuilder<TLeft, TRight> Full()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Full;
|
JoinType = DynamicJoinType.Full;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>Sets LEFT OUTER JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> LeftOuter()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.LeftOuter;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>Sets RIGHT OUTER JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> RightOuter()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.RightOuter;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>Sets FULL OUTER JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> FullOuter()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.FullOuter;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>Sets custom join type text (for example: CROSS APPLY).</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> Type(string joinType)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(joinType))
|
||||||
|
throw new ArgumentNullException("joinType");
|
||||||
|
|
||||||
|
CustomJoinType = joinType.Trim();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>Marks joined source with NOLOCK hint.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> NoLock(bool use = true)
|
||||||
|
{
|
||||||
|
UseNoLock = use;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/// <summary>Sets ON predicate.</summary>
|
/// <summary>Sets ON predicate.</summary>
|
||||||
@@ -7463,6 +7533,17 @@ namespace DynamORM
|
|||||||
throw new ArgumentNullException("predicate");
|
throw new ArgumentNullException("predicate");
|
||||||
|
|
||||||
OnPredicate = predicate;
|
OnPredicate = predicate;
|
||||||
|
OnRawCondition = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/// <summary>Sets raw ON clause (without the ON keyword).</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> OnRaw(string condition)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(condition))
|
||||||
|
throw new ArgumentNullException("condition");
|
||||||
|
|
||||||
|
OnRawCondition = condition.Trim();
|
||||||
|
OnPredicate = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7484,12 +7565,22 @@ namespace DynamORM
|
|||||||
DynamicJoinType jt = DynamicJoinType.Inner;
|
DynamicJoinType jt = DynamicJoinType.Inner;
|
||||||
string normalized = (joinType ?? string.Empty).Trim().ToUpperInvariant();
|
string normalized = (joinType ?? string.Empty).Trim().ToUpperInvariant();
|
||||||
|
|
||||||
if (normalized == "LEFT JOIN" || normalized == "LEFT")
|
if (normalized == "JOIN")
|
||||||
|
jt = DynamicJoinType.Join;
|
||||||
|
else if (normalized == "LEFT OUTER JOIN" || normalized == "LEFT OUTER")
|
||||||
|
jt = DynamicJoinType.LeftOuter;
|
||||||
|
else if (normalized == "RIGHT OUTER JOIN" || normalized == "RIGHT OUTER")
|
||||||
|
jt = DynamicJoinType.RightOuter;
|
||||||
|
else if (normalized == "FULL OUTER JOIN" || normalized == "FULL OUTER")
|
||||||
|
jt = DynamicJoinType.FullOuter;
|
||||||
|
else if (normalized == "LEFT JOIN" || normalized == "LEFT")
|
||||||
jt = DynamicJoinType.Left;
|
jt = DynamicJoinType.Left;
|
||||||
else if (normalized == "RIGHT JOIN" || normalized == "RIGHT")
|
else if (normalized == "RIGHT JOIN" || normalized == "RIGHT")
|
||||||
jt = DynamicJoinType.Right;
|
jt = DynamicJoinType.Right;
|
||||||
else if (normalized == "FULL JOIN" || normalized == "FULL")
|
else if (normalized == "FULL JOIN" || normalized == "FULL")
|
||||||
jt = DynamicJoinType.Full;
|
jt = DynamicJoinType.Full;
|
||||||
|
else if (normalized != "INNER JOIN" && normalized != "INNER" && normalized != "JOIN")
|
||||||
|
return builder.Join(on, alias, joinType);
|
||||||
|
|
||||||
return builder.Join(on, alias, jt);
|
return builder.Join(on, alias, jt);
|
||||||
}
|
}
|
||||||
@@ -9223,7 +9314,7 @@ namespace DynamORM
|
|||||||
|
|
||||||
private string _select;
|
private string _select;
|
||||||
private string _from;
|
private string _from;
|
||||||
private string _join;
|
protected string _join;
|
||||||
private string _groupby;
|
private string _groupby;
|
||||||
private string _orderby;
|
private string _orderby;
|
||||||
|
|
||||||
@@ -10639,10 +10730,14 @@ namespace DynamORM
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner)
|
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner, bool noLock = false)
|
||||||
{
|
{
|
||||||
if (on == null)
|
return Join(on, alias, GetJoinKeyword(joinType), noLock);
|
||||||
throw new ArgumentNullException("on");
|
}
|
||||||
|
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias, string joinType, bool noLock = false)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(joinType))
|
||||||
|
throw new ArgumentNullException("joinType");
|
||||||
|
|
||||||
DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight));
|
DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight));
|
||||||
if (rightMapper == null)
|
if (rightMapper == null)
|
||||||
@@ -10654,27 +10749,28 @@ namespace DynamORM
|
|||||||
string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner;
|
string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner;
|
||||||
string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (Tables.Count + 1).ToString() : alias;
|
string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (Tables.Count + 1).ToString() : alias;
|
||||||
|
|
||||||
BinaryExpression be = on.Body as BinaryExpression;
|
|
||||||
if (be == null || be.NodeType != ExpressionType.Equal)
|
|
||||||
throw new NotSupportedException("Typed join expression is currently limited to equality comparisons.");
|
|
||||||
|
|
||||||
string leftPrefix = GetRootAliasOrTableName();
|
string leftPrefix = GetRootAliasOrTableName();
|
||||||
if (string.IsNullOrEmpty(leftPrefix))
|
if (string.IsNullOrEmpty(leftPrefix))
|
||||||
throw new InvalidOperationException("Join requires source table to be present.");
|
throw new InvalidOperationException("Join requires source table to be present.");
|
||||||
|
|
||||||
string leftExpr = ParseTypedJoinMember<T>(be.Left, leftPrefix, _mapper);
|
string condition = null;
|
||||||
string rightExpr = ParseTypedJoinMember<TRight>(be.Right, rightAlias, rightMapper);
|
if (on != null)
|
||||||
|
condition = ParseTypedJoinCondition(on.Body, leftPrefix, rightAlias, _mapper, rightMapper, on.Parameters[0], on.Parameters[1]);
|
||||||
|
|
||||||
string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + ".";
|
string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + ".";
|
||||||
string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable);
|
string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable);
|
||||||
string joinExpr = string.Format("{0} {1} AS {2} ON ({3} = {4})",
|
string joinExpr = string.Format("{0} {1} AS {2}",
|
||||||
GetJoinKeyword(joinType),
|
joinType.Trim(),
|
||||||
rightTableExpr,
|
rightTableExpr,
|
||||||
rightAlias,
|
rightAlias);
|
||||||
leftExpr,
|
|
||||||
rightExpr);
|
|
||||||
|
|
||||||
base.Join(x => joinExpr);
|
if (SupportNoLock && noLock)
|
||||||
|
joinExpr += " WITH(NOLOCK)";
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(condition))
|
||||||
|
joinExpr += string.Format(" ON {0}", condition);
|
||||||
|
|
||||||
|
AppendJoinClause(joinExpr);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Func<TypedJoinBuilder<T, TRight>, TypedJoinBuilder<T, TRight>> specification)
|
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Func<TypedJoinBuilder<T, TRight>, TypedJoinBuilder<T, TRight>> specification)
|
||||||
@@ -10685,10 +10781,34 @@ namespace DynamORM
|
|||||||
TypedJoinBuilder<T, TRight> spec = specification(new TypedJoinBuilder<T, TRight>());
|
TypedJoinBuilder<T, TRight> spec = specification(new TypedJoinBuilder<T, TRight>());
|
||||||
if (spec == null)
|
if (spec == null)
|
||||||
throw new ArgumentException("Join specification cannot resolve to null.", "specification");
|
throw new ArgumentException("Join specification cannot resolve to null.", "specification");
|
||||||
if (spec.OnPredicate == null)
|
if (spec.OnPredicate != null)
|
||||||
throw new ArgumentException("Join specification must define ON predicate.", "specification");
|
return Join(spec.OnPredicate, spec.Alias, spec.CustomJoinType ?? GetJoinKeyword(spec.JoinType), spec.UseNoLock);
|
||||||
|
|
||||||
return Join(spec.OnPredicate, spec.Alias, spec.JoinType);
|
DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight));
|
||||||
|
if (rightMapper == null)
|
||||||
|
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(TRight).FullName));
|
||||||
|
|
||||||
|
string rightTable = rightMapper.Table == null || string.IsNullOrEmpty(rightMapper.Table.Name)
|
||||||
|
? typeof(TRight).Name
|
||||||
|
: rightMapper.Table.Name;
|
||||||
|
string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner;
|
||||||
|
string rightAlias = string.IsNullOrEmpty(spec.Alias) ? "t" + (Tables.Count + 1).ToString() : spec.Alias;
|
||||||
|
|
||||||
|
string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + ".";
|
||||||
|
string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable);
|
||||||
|
string joinExpr = string.Format("{0} {1} AS {2}",
|
||||||
|
(spec.CustomJoinType ?? GetJoinKeyword(spec.JoinType)).Trim(),
|
||||||
|
rightTableExpr,
|
||||||
|
rightAlias);
|
||||||
|
|
||||||
|
if (SupportNoLock && spec.UseNoLock)
|
||||||
|
joinExpr += " WITH(NOLOCK)";
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(spec.OnRawCondition))
|
||||||
|
joinExpr += string.Format(" ON {0}", spec.OnRawCondition);
|
||||||
|
|
||||||
|
AppendJoinClause(joinExpr);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
public IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null)
|
public IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null)
|
||||||
{
|
{
|
||||||
@@ -11061,16 +11181,82 @@ namespace DynamORM
|
|||||||
{
|
{
|
||||||
switch (joinType)
|
switch (joinType)
|
||||||
{
|
{
|
||||||
|
case DynamicJoinType.Join:
|
||||||
|
return "JOIN";
|
||||||
case DynamicJoinType.Left:
|
case DynamicJoinType.Left:
|
||||||
return "LEFT JOIN";
|
return "LEFT JOIN";
|
||||||
case DynamicJoinType.Right:
|
case DynamicJoinType.Right:
|
||||||
return "RIGHT JOIN";
|
return "RIGHT JOIN";
|
||||||
case DynamicJoinType.Full:
|
case DynamicJoinType.Full:
|
||||||
return "FULL JOIN";
|
return "FULL JOIN";
|
||||||
|
case DynamicJoinType.LeftOuter:
|
||||||
|
return "LEFT OUTER JOIN";
|
||||||
|
case DynamicJoinType.RightOuter:
|
||||||
|
return "RIGHT OUTER JOIN";
|
||||||
|
case DynamicJoinType.FullOuter:
|
||||||
|
return "FULL OUTER JOIN";
|
||||||
default:
|
default:
|
||||||
return "INNER JOIN";
|
return "INNER JOIN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void AppendJoinClause(string joinClause)
|
||||||
|
{
|
||||||
|
_join = string.IsNullOrEmpty(_join)
|
||||||
|
? joinClause
|
||||||
|
: string.Format("{0} {1}", _join, joinClause);
|
||||||
|
}
|
||||||
|
private string ParseTypedJoinCondition(Expression expression, string leftPrefix, string rightPrefix, DynamicTypeMap leftMapper, DynamicTypeMap rightMapper, ParameterExpression leftParameter, ParameterExpression rightParameter)
|
||||||
|
{
|
||||||
|
expression = UnwrapConvert(expression);
|
||||||
|
|
||||||
|
if (expression is BinaryExpression binary)
|
||||||
|
{
|
||||||
|
switch (binary.NodeType)
|
||||||
|
{
|
||||||
|
case ExpressionType.AndAlso:
|
||||||
|
case ExpressionType.And:
|
||||||
|
return string.Format("({0} AND {1})",
|
||||||
|
ParseTypedJoinCondition(binary.Left, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter),
|
||||||
|
ParseTypedJoinCondition(binary.Right, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter));
|
||||||
|
case ExpressionType.OrElse:
|
||||||
|
case ExpressionType.Or:
|
||||||
|
return string.Format("({0} OR {1})",
|
||||||
|
ParseTypedJoinCondition(binary.Left, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter),
|
||||||
|
ParseTypedJoinCondition(binary.Right, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter));
|
||||||
|
case ExpressionType.Equal:
|
||||||
|
case ExpressionType.NotEqual:
|
||||||
|
case ExpressionType.GreaterThan:
|
||||||
|
case ExpressionType.GreaterThanOrEqual:
|
||||||
|
case ExpressionType.LessThan:
|
||||||
|
case ExpressionType.LessThanOrEqual:
|
||||||
|
{
|
||||||
|
string left = ParseTypedJoinValue(binary.Left, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter);
|
||||||
|
string right = ParseTypedJoinValue(binary.Right, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter);
|
||||||
|
string op = GetBinaryOperator(binary.NodeType, IsNullConstant(binary.Right));
|
||||||
|
return string.Format("({0} {1} {2})", left, op, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Not)
|
||||||
|
return string.Format("(NOT {0})", ParseTypedJoinCondition(unary.Operand, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter));
|
||||||
|
|
||||||
|
throw new NotSupportedException(string.Format("Typed join condition is not supported: {0}", expression));
|
||||||
|
}
|
||||||
|
private string ParseTypedJoinValue(Expression expression, string leftPrefix, string rightPrefix, DynamicTypeMap leftMapper, DynamicTypeMap rightMapper, ParameterExpression leftParameter, ParameterExpression rightParameter)
|
||||||
|
{
|
||||||
|
expression = UnwrapConvert(expression);
|
||||||
|
MemberExpression member = expression as MemberExpression;
|
||||||
|
if (member != null && member.Expression is ParameterExpression parameter)
|
||||||
|
{
|
||||||
|
if (parameter == leftParameter)
|
||||||
|
return ParseTypedJoinMemberByMapper(member, leftPrefix, leftMapper);
|
||||||
|
if (parameter == rightParameter)
|
||||||
|
return ParseTypedJoinMemberByMapper(member, rightPrefix, rightMapper);
|
||||||
|
}
|
||||||
|
DynamicSchemaColumn? col = null;
|
||||||
|
object value = EvaluateExpression(expression);
|
||||||
|
return ParseConstant(value, Parameters, col);
|
||||||
|
}
|
||||||
private string ParseTypedJoinMember<TModel>(Expression expression, string tablePrefix, DynamicTypeMap mapper)
|
private string ParseTypedJoinMember<TModel>(Expression expression, string tablePrefix, DynamicTypeMap mapper)
|
||||||
{
|
{
|
||||||
expression = UnwrapConvert(expression);
|
expression = UnwrapConvert(expression);
|
||||||
@@ -11097,6 +11283,27 @@ namespace DynamORM
|
|||||||
|
|
||||||
return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn));
|
return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn));
|
||||||
}
|
}
|
||||||
|
private string ParseTypedJoinMemberByMapper(MemberExpression member, string tablePrefix, DynamicTypeMap mapper)
|
||||||
|
{
|
||||||
|
string mappedColumn = null;
|
||||||
|
PropertyInfo property = member.Member as PropertyInfo;
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
var attrs = property.GetCustomAttributes(typeof(ColumnAttribute), true);
|
||||||
|
ColumnAttribute colAttr = attrs == null ? null : attrs.Cast<ColumnAttribute>().FirstOrDefault();
|
||||||
|
if (colAttr != null && !string.IsNullOrEmpty(colAttr.Name))
|
||||||
|
mappedColumn = colAttr.Name;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(mappedColumn))
|
||||||
|
mappedColumn = mapper.PropertyMap.TryGetValue(member.Member.Name)
|
||||||
|
?? mapper.PropertyMap
|
||||||
|
.Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(x => x.Value)
|
||||||
|
.FirstOrDefault()
|
||||||
|
?? member.Member.Name;
|
||||||
|
|
||||||
|
return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn));
|
||||||
|
}
|
||||||
private static Expression UnwrapConvert(Expression expression)
|
private static Expression UnwrapConvert(Expression expression)
|
||||||
{
|
{
|
||||||
while (expression is UnaryExpression unary &&
|
while (expression is UnaryExpression unary &&
|
||||||
|
|||||||
33
DynamORM.Tests/Helpers/TypedJoinModels.cs
Normal file
33
DynamORM.Tests/Helpers/TypedJoinModels.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||||
|
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using DynamORM.Mapper;
|
||||||
|
|
||||||
|
namespace DynamORM.Tests.Helpers
|
||||||
|
{
|
||||||
|
[Table(Name = "Users", Owner = "dbo")]
|
||||||
|
public class TypedJoinUser
|
||||||
|
{
|
||||||
|
[Column("Id_User", true)]
|
||||||
|
public long IdUser { get; set; }
|
||||||
|
|
||||||
|
[Column("Active")]
|
||||||
|
public int Active { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table(Name = "UserClients", Owner = "dbo")]
|
||||||
|
public class TypedJoinUserClient
|
||||||
|
{
|
||||||
|
[Column("User_Id", true)]
|
||||||
|
public long UserId { get; set; }
|
||||||
|
|
||||||
|
[Column("Users")]
|
||||||
|
public string Users { get; set; }
|
||||||
|
|
||||||
|
[Column("Deleted")]
|
||||||
|
public int Deleted { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ namespace DynamORM.Tests.Select
|
|||||||
public void TestTypedJoin()
|
public void TestTypedJoin()
|
||||||
{
|
{
|
||||||
var cmd = Database.FromTyped<TypedFluentUser>("u")
|
var cmd = Database.FromTyped<TypedFluentUser>("u")
|
||||||
.Join<TypedFluentUser>(j => j.As("x").On((l, r) => l.Id == r.Id))
|
.Join<TypedFluentUser>(j => j.Inner().As("x").On((l, r) => l.Id == r.Id))
|
||||||
.Select(u => u.Id);
|
.Select(u => u.Id);
|
||||||
|
|
||||||
Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u INNER JOIN \"sample_users\" AS x ON (u.\"id_user\" = x.\"id_user\")",
|
Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u INNER JOIN \"sample_users\" AS x ON (u.\"id_user\" = x.\"id_user\")",
|
||||||
|
|||||||
102
DynamORM.Tests/Select/TypedFluentJoinSyntaxTests.cs
Normal file
102
DynamORM.Tests/Select/TypedFluentJoinSyntaxTests.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||||
|
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using DynamORM.Tests.Helpers;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace DynamORM.Tests.Select
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TypedFluentJoinSyntaxTests : TestsBase
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
CreateTestDatabase();
|
||||||
|
CreateDynamicDatabase(
|
||||||
|
DynamicDatabaseOptions.SingleConnection |
|
||||||
|
DynamicDatabaseOptions.SingleTransaction |
|
||||||
|
DynamicDatabaseOptions.SupportNoLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
DestroyDynamicDatabase();
|
||||||
|
DestroyTestDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTypedJoinDefaultJoinKeyword()
|
||||||
|
{
|
||||||
|
var cmd = Database.From<TypedJoinUser>("usr")
|
||||||
|
.Join<TypedJoinUserClient>(j => j.As("uc").On((u, c) => u.IdUser == c.UserId));
|
||||||
|
|
||||||
|
Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")", cmd.CommandText());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTypedInnerJoinWithAndNull()
|
||||||
|
{
|
||||||
|
var cmd = Database.From<TypedJoinUser>("usr")
|
||||||
|
.Join<TypedJoinUserClient>(j => j.Inner().As("uc").On((u, c) => u.IdUser == c.UserId && c.Users != null))
|
||||||
|
.Select(u => u.IdUser);
|
||||||
|
|
||||||
|
Assert.AreEqual("SELECT usr.\"Id_User\" FROM \"dbo\".\"Users\" AS usr INNER JOIN \"dbo\".\"UserClients\" AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))", cmd.CommandText());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTypedJoinWithNoLock()
|
||||||
|
{
|
||||||
|
var cmd = Database.From<TypedJoinUser>("usr", noLock: true)
|
||||||
|
.Join<TypedJoinUserClient>(j => j.Inner().As("uc").NoLock().On((u, c) => u.IdUser == c.UserId));
|
||||||
|
|
||||||
|
Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS usr WITH(NOLOCK) INNER JOIN \"dbo\".\"UserClients\" AS uc WITH(NOLOCK) ON (usr.\"Id_User\" = uc.\"User_Id\")", cmd.CommandText());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTypedLeftOuterJoin()
|
||||||
|
{
|
||||||
|
var cmd = Database.From<TypedJoinUser>("usr")
|
||||||
|
.Join<TypedJoinUserClient>(j => j.LeftOuter().As("uc").On((u, c) => u.IdUser == c.UserId));
|
||||||
|
|
||||||
|
Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS usr LEFT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")", cmd.CommandText());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTypedRightOuterJoin()
|
||||||
|
{
|
||||||
|
var cmd = Database.From<TypedJoinUser>("usr")
|
||||||
|
.Join<TypedJoinUserClient>(j => j.RightOuter().As("uc").On((u, c) => u.IdUser == c.UserId));
|
||||||
|
|
||||||
|
Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS usr RIGHT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")", cmd.CommandText());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTypedCustomJoinTypeCrossApply()
|
||||||
|
{
|
||||||
|
var cmd = Database.From<TypedJoinUser>("usr")
|
||||||
|
.Join<TypedJoinUserClient>(j => j.Type("CROSS APPLY").As("uc"));
|
||||||
|
|
||||||
|
Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS usr CROSS APPLY \"dbo\".\"UserClients\" AS uc", cmd.CommandText());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTypedJoinAndWhereParameterOrder()
|
||||||
|
{
|
||||||
|
var cmd = Database.From<TypedJoinUser>("usr")
|
||||||
|
.Join<TypedJoinUserClient>(j => j.As("uc").On((u, c) => u.IdUser == c.UserId && c.Deleted == 0))
|
||||||
|
.Where(u => u.Active == 1);
|
||||||
|
|
||||||
|
Assert.AreEqual(
|
||||||
|
string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Deleted\" = [${0}])) WHERE (usr.\"Active\" = [${1}])",
|
||||||
|
cmd.Parameters.Keys.ElementAt(0),
|
||||||
|
cmd.Parameters.Keys.ElementAt(1)),
|
||||||
|
cmd.CommandText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,12 @@ namespace DynamORM.Builders
|
|||||||
public enum DynamicJoinType
|
public enum DynamicJoinType
|
||||||
{
|
{
|
||||||
Inner = 0,
|
Inner = 0,
|
||||||
|
Join,
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Full
|
Full,
|
||||||
|
LeftOuter,
|
||||||
|
RightOuter,
|
||||||
|
FullOuter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,18 @@ namespace DynamORM.Builders
|
|||||||
/// <param name="on">Join ON predicate.</param>
|
/// <param name="on">Join ON predicate.</param>
|
||||||
/// <param name="alias">Optional alias for joined table.</param>
|
/// <param name="alias">Optional alias for joined table.</param>
|
||||||
/// <param name="joinType">Join type.</param>
|
/// <param name="joinType">Join type.</param>
|
||||||
|
/// <param name="noLock">Adds NOLOCK hint to joined source when supported by provider options.</param>
|
||||||
/// <returns>Builder instance.</returns>
|
/// <returns>Builder instance.</returns>
|
||||||
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner);
|
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner, bool noLock = false);
|
||||||
|
|
||||||
|
/// <summary>Add typed join with custom join type text (for example: CROSS APPLY).</summary>
|
||||||
|
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
|
||||||
|
/// <param name="on">Optional join ON predicate.</param>
|
||||||
|
/// <param name="alias">Optional alias for joined table.</param>
|
||||||
|
/// <param name="joinType">Join type text.</param>
|
||||||
|
/// <param name="noLock">Adds NOLOCK hint to joined source when supported by provider options.</param>
|
||||||
|
/// <returns>Builder instance.</returns>
|
||||||
|
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias, string joinType, bool noLock = false);
|
||||||
|
|
||||||
/// <summary>Add typed join using join-spec builder syntax (<c>As()</c>, join kind and <c>On()</c>).</summary>
|
/// <summary>Add typed join using join-spec builder syntax (<c>As()</c>, join kind and <c>On()</c>).</summary>
|
||||||
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
|
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace DynamORM.Builders.Implementation
|
|||||||
|
|
||||||
private string _select;
|
private string _select;
|
||||||
private string _from;
|
private string _from;
|
||||||
private string _join;
|
protected string _join;
|
||||||
private string _groupby;
|
private string _groupby;
|
||||||
private string _orderby;
|
private string _orderby;
|
||||||
|
|
||||||
|
|||||||
@@ -65,10 +65,15 @@ namespace DynamORM.Builders.Implementation
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner)
|
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner, bool noLock = false)
|
||||||
{
|
{
|
||||||
if (on == null)
|
return Join(on, alias, GetJoinKeyword(joinType), noLock);
|
||||||
throw new ArgumentNullException("on");
|
}
|
||||||
|
|
||||||
|
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias, string joinType, bool noLock = false)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(joinType))
|
||||||
|
throw new ArgumentNullException("joinType");
|
||||||
|
|
||||||
DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight));
|
DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight));
|
||||||
if (rightMapper == null)
|
if (rightMapper == null)
|
||||||
@@ -80,27 +85,28 @@ namespace DynamORM.Builders.Implementation
|
|||||||
string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner;
|
string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner;
|
||||||
string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (Tables.Count + 1).ToString() : alias;
|
string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (Tables.Count + 1).ToString() : alias;
|
||||||
|
|
||||||
BinaryExpression be = on.Body as BinaryExpression;
|
|
||||||
if (be == null || be.NodeType != ExpressionType.Equal)
|
|
||||||
throw new NotSupportedException("Typed join expression is currently limited to equality comparisons.");
|
|
||||||
|
|
||||||
string leftPrefix = GetRootAliasOrTableName();
|
string leftPrefix = GetRootAliasOrTableName();
|
||||||
if (string.IsNullOrEmpty(leftPrefix))
|
if (string.IsNullOrEmpty(leftPrefix))
|
||||||
throw new InvalidOperationException("Join requires source table to be present.");
|
throw new InvalidOperationException("Join requires source table to be present.");
|
||||||
|
|
||||||
string leftExpr = ParseTypedJoinMember<T>(be.Left, leftPrefix, _mapper);
|
string condition = null;
|
||||||
string rightExpr = ParseTypedJoinMember<TRight>(be.Right, rightAlias, rightMapper);
|
if (on != null)
|
||||||
|
condition = ParseTypedJoinCondition(on.Body, leftPrefix, rightAlias, _mapper, rightMapper, on.Parameters[0], on.Parameters[1]);
|
||||||
|
|
||||||
string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + ".";
|
string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + ".";
|
||||||
string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable);
|
string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable);
|
||||||
string joinExpr = string.Format("{0} {1} AS {2} ON ({3} = {4})",
|
string joinExpr = string.Format("{0} {1} AS {2}",
|
||||||
GetJoinKeyword(joinType),
|
joinType.Trim(),
|
||||||
rightTableExpr,
|
rightTableExpr,
|
||||||
rightAlias,
|
rightAlias);
|
||||||
leftExpr,
|
|
||||||
rightExpr);
|
|
||||||
|
|
||||||
base.Join(x => joinExpr);
|
if (SupportNoLock && noLock)
|
||||||
|
joinExpr += " WITH(NOLOCK)";
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(condition))
|
||||||
|
joinExpr += string.Format(" ON {0}", condition);
|
||||||
|
|
||||||
|
AppendJoinClause(joinExpr);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,10 +118,34 @@ namespace DynamORM.Builders.Implementation
|
|||||||
TypedJoinBuilder<T, TRight> spec = specification(new TypedJoinBuilder<T, TRight>());
|
TypedJoinBuilder<T, TRight> spec = specification(new TypedJoinBuilder<T, TRight>());
|
||||||
if (spec == null)
|
if (spec == null)
|
||||||
throw new ArgumentException("Join specification cannot resolve to null.", "specification");
|
throw new ArgumentException("Join specification cannot resolve to null.", "specification");
|
||||||
if (spec.OnPredicate == null)
|
if (spec.OnPredicate != null)
|
||||||
throw new ArgumentException("Join specification must define ON predicate.", "specification");
|
return Join(spec.OnPredicate, spec.Alias, spec.CustomJoinType ?? GetJoinKeyword(spec.JoinType), spec.UseNoLock);
|
||||||
|
|
||||||
return Join(spec.OnPredicate, spec.Alias, spec.JoinType);
|
DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight));
|
||||||
|
if (rightMapper == null)
|
||||||
|
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(TRight).FullName));
|
||||||
|
|
||||||
|
string rightTable = rightMapper.Table == null || string.IsNullOrEmpty(rightMapper.Table.Name)
|
||||||
|
? typeof(TRight).Name
|
||||||
|
: rightMapper.Table.Name;
|
||||||
|
string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner;
|
||||||
|
string rightAlias = string.IsNullOrEmpty(spec.Alias) ? "t" + (Tables.Count + 1).ToString() : spec.Alias;
|
||||||
|
|
||||||
|
string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + ".";
|
||||||
|
string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable);
|
||||||
|
string joinExpr = string.Format("{0} {1} AS {2}",
|
||||||
|
(spec.CustomJoinType ?? GetJoinKeyword(spec.JoinType)).Trim(),
|
||||||
|
rightTableExpr,
|
||||||
|
rightAlias);
|
||||||
|
|
||||||
|
if (SupportNoLock && spec.UseNoLock)
|
||||||
|
joinExpr += " WITH(NOLOCK)";
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(spec.OnRawCondition))
|
||||||
|
joinExpr += string.Format(" ON {0}", spec.OnRawCondition);
|
||||||
|
|
||||||
|
AppendJoinClause(joinExpr);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public new IDynamicTypedSelectQueryBuilder<T> Join(params Func<dynamic, object>[] func)
|
public new IDynamicTypedSelectQueryBuilder<T> Join(params Func<dynamic, object>[] func)
|
||||||
@@ -511,17 +541,88 @@ namespace DynamORM.Builders.Implementation
|
|||||||
{
|
{
|
||||||
switch (joinType)
|
switch (joinType)
|
||||||
{
|
{
|
||||||
|
case DynamicJoinType.Join:
|
||||||
|
return "JOIN";
|
||||||
case DynamicJoinType.Left:
|
case DynamicJoinType.Left:
|
||||||
return "LEFT JOIN";
|
return "LEFT JOIN";
|
||||||
case DynamicJoinType.Right:
|
case DynamicJoinType.Right:
|
||||||
return "RIGHT JOIN";
|
return "RIGHT JOIN";
|
||||||
case DynamicJoinType.Full:
|
case DynamicJoinType.Full:
|
||||||
return "FULL JOIN";
|
return "FULL JOIN";
|
||||||
|
case DynamicJoinType.LeftOuter:
|
||||||
|
return "LEFT OUTER JOIN";
|
||||||
|
case DynamicJoinType.RightOuter:
|
||||||
|
return "RIGHT OUTER JOIN";
|
||||||
|
case DynamicJoinType.FullOuter:
|
||||||
|
return "FULL OUTER JOIN";
|
||||||
default:
|
default:
|
||||||
return "INNER JOIN";
|
return "INNER JOIN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AppendJoinClause(string joinClause)
|
||||||
|
{
|
||||||
|
_join = string.IsNullOrEmpty(_join)
|
||||||
|
? joinClause
|
||||||
|
: string.Format("{0} {1}", _join, joinClause);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ParseTypedJoinCondition(Expression expression, string leftPrefix, string rightPrefix, DynamicTypeMap leftMapper, DynamicTypeMap rightMapper, ParameterExpression leftParameter, ParameterExpression rightParameter)
|
||||||
|
{
|
||||||
|
expression = UnwrapConvert(expression);
|
||||||
|
|
||||||
|
if (expression is BinaryExpression binary)
|
||||||
|
{
|
||||||
|
switch (binary.NodeType)
|
||||||
|
{
|
||||||
|
case ExpressionType.AndAlso:
|
||||||
|
case ExpressionType.And:
|
||||||
|
return string.Format("({0} AND {1})",
|
||||||
|
ParseTypedJoinCondition(binary.Left, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter),
|
||||||
|
ParseTypedJoinCondition(binary.Right, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter));
|
||||||
|
case ExpressionType.OrElse:
|
||||||
|
case ExpressionType.Or:
|
||||||
|
return string.Format("({0} OR {1})",
|
||||||
|
ParseTypedJoinCondition(binary.Left, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter),
|
||||||
|
ParseTypedJoinCondition(binary.Right, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter));
|
||||||
|
case ExpressionType.Equal:
|
||||||
|
case ExpressionType.NotEqual:
|
||||||
|
case ExpressionType.GreaterThan:
|
||||||
|
case ExpressionType.GreaterThanOrEqual:
|
||||||
|
case ExpressionType.LessThan:
|
||||||
|
case ExpressionType.LessThanOrEqual:
|
||||||
|
{
|
||||||
|
string left = ParseTypedJoinValue(binary.Left, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter);
|
||||||
|
string right = ParseTypedJoinValue(binary.Right, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter);
|
||||||
|
string op = GetBinaryOperator(binary.NodeType, IsNullConstant(binary.Right));
|
||||||
|
return string.Format("({0} {1} {2})", left, op, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Not)
|
||||||
|
return string.Format("(NOT {0})", ParseTypedJoinCondition(unary.Operand, leftPrefix, rightPrefix, leftMapper, rightMapper, leftParameter, rightParameter));
|
||||||
|
|
||||||
|
throw new NotSupportedException(string.Format("Typed join condition is not supported: {0}", expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ParseTypedJoinValue(Expression expression, string leftPrefix, string rightPrefix, DynamicTypeMap leftMapper, DynamicTypeMap rightMapper, ParameterExpression leftParameter, ParameterExpression rightParameter)
|
||||||
|
{
|
||||||
|
expression = UnwrapConvert(expression);
|
||||||
|
MemberExpression member = expression as MemberExpression;
|
||||||
|
if (member != null && member.Expression is ParameterExpression parameter)
|
||||||
|
{
|
||||||
|
if (parameter == leftParameter)
|
||||||
|
return ParseTypedJoinMemberByMapper(member, leftPrefix, leftMapper);
|
||||||
|
if (parameter == rightParameter)
|
||||||
|
return ParseTypedJoinMemberByMapper(member, rightPrefix, rightMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicSchemaColumn? col = null;
|
||||||
|
object value = EvaluateExpression(expression);
|
||||||
|
return ParseConstant(value, Parameters, col);
|
||||||
|
}
|
||||||
|
|
||||||
private string ParseTypedJoinMember<TModel>(Expression expression, string tablePrefix, DynamicTypeMap mapper)
|
private string ParseTypedJoinMember<TModel>(Expression expression, string tablePrefix, DynamicTypeMap mapper)
|
||||||
{
|
{
|
||||||
expression = UnwrapConvert(expression);
|
expression = UnwrapConvert(expression);
|
||||||
@@ -550,6 +651,29 @@ namespace DynamORM.Builders.Implementation
|
|||||||
return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn));
|
return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ParseTypedJoinMemberByMapper(MemberExpression member, string tablePrefix, DynamicTypeMap mapper)
|
||||||
|
{
|
||||||
|
string mappedColumn = null;
|
||||||
|
PropertyInfo property = member.Member as PropertyInfo;
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
var attrs = property.GetCustomAttributes(typeof(ColumnAttribute), true);
|
||||||
|
ColumnAttribute colAttr = attrs == null ? null : attrs.Cast<ColumnAttribute>().FirstOrDefault();
|
||||||
|
if (colAttr != null && !string.IsNullOrEmpty(colAttr.Name))
|
||||||
|
mappedColumn = colAttr.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(mappedColumn))
|
||||||
|
mappedColumn = mapper.PropertyMap.TryGetValue(member.Member.Name)
|
||||||
|
?? mapper.PropertyMap
|
||||||
|
.Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(x => x.Value)
|
||||||
|
.FirstOrDefault()
|
||||||
|
?? member.Member.Name;
|
||||||
|
|
||||||
|
return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn));
|
||||||
|
}
|
||||||
|
|
||||||
private static Expression UnwrapConvert(Expression expression)
|
private static Expression UnwrapConvert(Expression expression)
|
||||||
{
|
{
|
||||||
while (expression is UnaryExpression unary &&
|
while (expression is UnaryExpression unary &&
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace DynamORM.Builders
|
|||||||
{
|
{
|
||||||
internal TypedJoinBuilder()
|
internal TypedJoinBuilder()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Inner;
|
JoinType = DynamicJoinType.Join;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets join alias.</summary>
|
/// <summary>Gets join alias.</summary>
|
||||||
@@ -25,9 +25,18 @@ namespace DynamORM.Builders
|
|||||||
/// <summary>Gets join type.</summary>
|
/// <summary>Gets join type.</summary>
|
||||||
public DynamicJoinType JoinType { get; private set; }
|
public DynamicJoinType JoinType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets custom join type text.</summary>
|
||||||
|
public string CustomJoinType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether joined source should use NOLOCK.</summary>
|
||||||
|
public bool UseNoLock { get; private set; }
|
||||||
|
|
||||||
/// <summary>Gets ON predicate.</summary>
|
/// <summary>Gets ON predicate.</summary>
|
||||||
public Expression<Func<TLeft, TRight, bool>> OnPredicate { get; private set; }
|
public Expression<Func<TLeft, TRight, bool>> OnPredicate { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets raw ON condition.</summary>
|
||||||
|
public string OnRawCondition { get; private set; }
|
||||||
|
|
||||||
/// <summary>Sets join alias.</summary>
|
/// <summary>Sets join alias.</summary>
|
||||||
public TypedJoinBuilder<TLeft, TRight> As(string alias)
|
public TypedJoinBuilder<TLeft, TRight> As(string alias)
|
||||||
{
|
{
|
||||||
@@ -39,6 +48,15 @@ namespace DynamORM.Builders
|
|||||||
public TypedJoinBuilder<TLeft, TRight> Inner()
|
public TypedJoinBuilder<TLeft, TRight> Inner()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Inner;
|
JoinType = DynamicJoinType.Inner;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets plain JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> Join()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.Join;
|
||||||
|
CustomJoinType = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +64,7 @@ namespace DynamORM.Builders
|
|||||||
public TypedJoinBuilder<TLeft, TRight> Left()
|
public TypedJoinBuilder<TLeft, TRight> Left()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Left;
|
JoinType = DynamicJoinType.Left;
|
||||||
|
CustomJoinType = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +72,7 @@ namespace DynamORM.Builders
|
|||||||
public TypedJoinBuilder<TLeft, TRight> Right()
|
public TypedJoinBuilder<TLeft, TRight> Right()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Right;
|
JoinType = DynamicJoinType.Right;
|
||||||
|
CustomJoinType = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +80,48 @@ namespace DynamORM.Builders
|
|||||||
public TypedJoinBuilder<TLeft, TRight> Full()
|
public TypedJoinBuilder<TLeft, TRight> Full()
|
||||||
{
|
{
|
||||||
JoinType = DynamicJoinType.Full;
|
JoinType = DynamicJoinType.Full;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets LEFT OUTER JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> LeftOuter()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.LeftOuter;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets RIGHT OUTER JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> RightOuter()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.RightOuter;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets FULL OUTER JOIN.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> FullOuter()
|
||||||
|
{
|
||||||
|
JoinType = DynamicJoinType.FullOuter;
|
||||||
|
CustomJoinType = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets custom join type text (for example: CROSS APPLY).</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> Type(string joinType)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(joinType))
|
||||||
|
throw new ArgumentNullException("joinType");
|
||||||
|
|
||||||
|
CustomJoinType = joinType.Trim();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Marks joined source with NOLOCK hint.</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> NoLock(bool use = true)
|
||||||
|
{
|
||||||
|
UseNoLock = use;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +132,18 @@ namespace DynamORM.Builders
|
|||||||
throw new ArgumentNullException("predicate");
|
throw new ArgumentNullException("predicate");
|
||||||
|
|
||||||
OnPredicate = predicate;
|
OnPredicate = predicate;
|
||||||
|
OnRawCondition = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets raw ON clause (without the ON keyword).</summary>
|
||||||
|
public TypedJoinBuilder<TLeft, TRight> OnRaw(string condition)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(condition))
|
||||||
|
throw new ArgumentNullException("condition");
|
||||||
|
|
||||||
|
OnRawCondition = condition.Trim();
|
||||||
|
OnPredicate = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,22 @@ namespace DynamORM.Builders
|
|||||||
DynamicJoinType jt = DynamicJoinType.Inner;
|
DynamicJoinType jt = DynamicJoinType.Inner;
|
||||||
string normalized = (joinType ?? string.Empty).Trim().ToUpperInvariant();
|
string normalized = (joinType ?? string.Empty).Trim().ToUpperInvariant();
|
||||||
|
|
||||||
if (normalized == "LEFT JOIN" || normalized == "LEFT")
|
if (normalized == "JOIN")
|
||||||
|
jt = DynamicJoinType.Join;
|
||||||
|
else if (normalized == "LEFT OUTER JOIN" || normalized == "LEFT OUTER")
|
||||||
|
jt = DynamicJoinType.LeftOuter;
|
||||||
|
else if (normalized == "RIGHT OUTER JOIN" || normalized == "RIGHT OUTER")
|
||||||
|
jt = DynamicJoinType.RightOuter;
|
||||||
|
else if (normalized == "FULL OUTER JOIN" || normalized == "FULL OUTER")
|
||||||
|
jt = DynamicJoinType.FullOuter;
|
||||||
|
else if (normalized == "LEFT JOIN" || normalized == "LEFT")
|
||||||
jt = DynamicJoinType.Left;
|
jt = DynamicJoinType.Left;
|
||||||
else if (normalized == "RIGHT JOIN" || normalized == "RIGHT")
|
else if (normalized == "RIGHT JOIN" || normalized == "RIGHT")
|
||||||
jt = DynamicJoinType.Right;
|
jt = DynamicJoinType.Right;
|
||||||
else if (normalized == "FULL JOIN" || normalized == "FULL")
|
else if (normalized == "FULL JOIN" || normalized == "FULL")
|
||||||
jt = DynamicJoinType.Full;
|
jt = DynamicJoinType.Full;
|
||||||
|
else if (normalized != "INNER JOIN" && normalized != "INNER" && normalized != "JOIN")
|
||||||
|
return builder.Join(on, alias, joinType);
|
||||||
|
|
||||||
return builder.Join(on, alias, jt);
|
return builder.Join(on, alias, jt);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user