diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
index 61f46683d8..d97c748b6f 100644
--- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
+++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
@@ -392,6 +392,23 @@ namespace Umbraco.Core.Persistence
#region Joins
+ ///
+ /// Appends a CROSS JOIN clause to the Sql statement.
+ ///
+ /// The type of the Dto.
+ /// The Sql statement.
+ /// An optional alias for the joined table.
+ /// The Sql statement.
+ public static Sql CrossJoin(this Sql sql, string alias = null)
+ {
+ var type = typeof(TDto);
+ var tableName = type.GetTableName();
+ var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName);
+ if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias);
+
+ return sql.Append("CROSS JOIN " + join);
+ }
+
///
/// Appends an INNER JOIN clause to the Sql statement.
///
@@ -533,6 +550,25 @@ namespace Umbraco.Core.Persistence
return sqlJoin.On(onExpression, expresionist.GetSqlParameters());
}
+ ///
+ /// Appends an ON clause to a SqlJoin statement.
+ ///
+ /// The type of Dto 1.
+ /// The type of Dto 2.
+ /// The type of Dto 3.
+ /// The SqlJoin statement.
+ /// A predicate to transform and use as the ON clause body.
+ /// An optional alias for Dto 1 table.
+ /// An optional alias for Dto 2 table.
+ /// An optional alias for Dto 3 table.
+ /// The Sql statement.
+ public static Sql On(this Sql.SqlJoinClause sqlJoin, Expression> predicate, string aliasLeft = null, string aliasRight = null, string aliasOther = null)
+ {
+ var expresionist = new PocoToSqlExpressionVisitor(sqlJoin.SqlContext, aliasLeft, aliasRight, aliasOther);
+ var onExpression = expresionist.Visit(predicate);
+ return sqlJoin.On(onExpression, expresionist.GetSqlParameters());
+ }
+
#endregion
#region Select
diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
index 4d33977c72..971b65c220 100644
--- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
+++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
@@ -167,4 +167,93 @@ namespace Umbraco.Core.Persistence.Querying
}
}
+ ///
+ /// Represents an expression tree parser used to turn strongly typed expressions into SQL statements.
+ ///
+ /// The type of DTO 1.
+ /// The type of DTO 2.
+ /// The type of DTO 3.
+ /// This visitor is stateful and cannot be reused.
+ internal class PocoToSqlExpressionVisitor : ExpressionVisitorBase
+ {
+ private readonly PocoData _pocoData1, _pocoData2, _pocoData3;
+ private readonly string _alias1, _alias2, _alias3;
+ private string _parameterName1, _parameterName2, _parameterName3;
+
+ public PocoToSqlExpressionVisitor(ISqlContext sqlContext, string alias1, string alias2, string alias3)
+ : base(sqlContext.SqlSyntax)
+ {
+ _pocoData1 = sqlContext.PocoDataFactory.ForType(typeof(TDto1));
+ _pocoData2 = sqlContext.PocoDataFactory.ForType(typeof(TDto2));
+ _pocoData3 = sqlContext.PocoDataFactory.ForType(typeof(TDto3));
+ _alias1 = alias1;
+ _alias2 = alias2;
+ _alias3 = alias3;
+ }
+
+ protected override string VisitLambda(LambdaExpression lambda)
+ {
+ if (lambda.Parameters.Count == 3)
+ {
+ _parameterName1 = lambda.Parameters[0].Name;
+ _parameterName2 = lambda.Parameters[1].Name;
+ _parameterName3 = lambda.Parameters[2].Name;
+ }
+ else if (lambda.Parameters.Count == 2)
+ {
+ _parameterName1 = lambda.Parameters[0].Name;
+ _parameterName2 = lambda.Parameters[1].Name;
+ }
+ else
+ {
+ _parameterName1 = _parameterName2 = null;
+ }
+ return base.VisitLambda(lambda);
+ }
+
+ protected override string VisitMemberAccess(MemberExpression m)
+ {
+ if (m.Expression != null)
+ {
+ if (m.Expression.NodeType == ExpressionType.Parameter)
+ {
+ var pex = (ParameterExpression)m.Expression;
+
+ if (pex.Name == _parameterName1)
+ return Visited ? string.Empty : GetFieldName(_pocoData1, m.Member.Name, _alias1);
+
+ if (pex.Name == _parameterName2)
+ return Visited ? string.Empty : GetFieldName(_pocoData2, m.Member.Name, _alias2);
+
+ if (pex.Name == _parameterName3)
+ return Visited ? string.Empty : GetFieldName(_pocoData3, m.Member.Name, _alias3);
+ }
+ else if (m.Expression.NodeType == ExpressionType.Convert)
+ {
+ // here: which _pd should we use?!
+ throw new NotSupportedException();
+ //return Visited ? string.Empty : GetFieldName(_pd, m.Member.Name);
+ }
+ }
+
+ var member = Expression.Convert(m, typeof(object));
+ var lambda = Expression.Lambda>(member);
+ var getter = lambda.Compile();
+ var o = getter();
+
+ SqlParameters.Add(o);
+
+ // execute if not already compiled
+ return Visited ? string.Empty : "@" + (SqlParameters.Count - 1);
+ }
+
+ protected virtual string GetFieldName(PocoData pocoData, string name, string alias)
+ {
+ var column = pocoData.Columns.FirstOrDefault(x => x.Value.MemberInfoData.Name == name);
+ var tableName = SqlSyntax.GetQuotedTableName(alias ?? pocoData.TableInfo.TableName);
+ var columnName = SqlSyntax.GetQuotedColumnName(column.Value.ColumnName);
+
+ return tableName + "." + columnName;
+ }
+ }
}