From 5f805b0804ec84a67add17d9f8137535d0b89594 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 25 Sep 2018 10:55:06 +0200 Subject: [PATCH] Extend NPoco fluent querying --- .../Persistence/NPocoSqlExtensions.cs | 36 ++++++++ .../Querying/PocoToSqlExpressionVisitor.cs | 89 +++++++++++++++++++ 2 files changed, 125 insertions(+) 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; + } + } }