Improve NPoco extensions

This commit is contained in:
Stephan
2018-09-17 13:06:20 +02:00
parent e412fd8802
commit a979e8023e
4 changed files with 172 additions and 34 deletions

View File

@@ -7,7 +7,6 @@ using System.Reflection;
using System.Text;
using NPoco;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
@@ -74,10 +73,27 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> Where<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, bool>> predicate, string alias = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto>(sql.SqlContext, alias);
var whereExpression = expresionist.Visit(predicate);
sql.Where(whereExpression, expresionist.GetSqlParameters());
return sql;
var (s, a) = sql.SqlContext.Visit(predicate, alias);
return sql.Where(s, a);
}
/// <summary>
/// Appends an AND clause to a WHERE Sql statement.
/// </summary>
/// <typeparam name="TDto">The type of the Dto.</typeparam>
/// <param name="sql">The Sql statement.</param>
/// <param name="predicate">A predicate to transform and append to the Sql statement.</param>
/// <param name="alias">An optional alias for the table.</param>
/// <returns>The Sql statement.</returns>
/// <remarks>
/// <para>Chaining <c>.Where(...).Where(...)</c> in NPoco works because it merges the two WHERE statements,
/// however if the first statement is not an explicit WHERE statement, chaining fails and two WHERE
/// statements appear in the resulting Sql. This allows for adding an AND clause without problems.</para>
/// </remarks>
public static Sql<ISqlContext> AndWhere<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, bool>> predicate, string alias = null)
{
var (s, a) = sql.SqlContext.Visit(predicate, alias);
return sql.Append("AND (" + s + ")", a);
}
/// <summary>
@@ -92,10 +108,8 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> Where<TDto1, TDto2>(this Sql<ISqlContext> sql, Expression<Func<TDto1, TDto2, bool>> predicate, string alias1 = null, string alias2 = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto1, TDto2>(sql.SqlContext, alias1, alias2);
var whereExpression = expresionist.Visit(predicate);
sql.Where(whereExpression, expresionist.GetSqlParameters());
return sql;
var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2);
return sql.Where(s, a);
}
/// <summary>
@@ -108,7 +122,7 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> WhereIn<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, object>> field, IEnumerable values)
{
var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax);
var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(field);
sql.Where(fieldName + " IN (@values)", new { values });
return sql;
}
@@ -136,7 +150,7 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> WhereNotIn<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, object>> field, IEnumerable values)
{
var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax);
var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(field);
sql.Where(fieldName + " NOT IN (@values)", new { values });
return sql;
}
@@ -164,7 +178,8 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql WhereAnyIn<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, object>>[] fields, IEnumerable values)
{
var fieldNames = fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
var sqlSyntax = sql.SqlContext.SqlSyntax;
var fieldNames = fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
var sb = new StringBuilder();
sb.Append("(");
for (var i = 0; i < fieldNames.Length; i++)
@@ -180,7 +195,7 @@ namespace Umbraco.Core.Persistence
private static Sql<ISqlContext> WhereIn<T>(this Sql<ISqlContext> sql, Expression<Func<T, object>> fieldSelector, Sql valuesSql, bool not)
{
var fieldName = GetFieldName(fieldSelector, sql.SqlContext.SqlSyntax);
var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector);
sql.Where(fieldName + (not ? " NOT" : "") +" IN (" + valuesSql.SQL + ")", valuesSql.Arguments);
return sql;
}
@@ -274,7 +289,7 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> OrderBy<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, object>> field)
{
return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ")");
return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ")");
}
/// <summary>
@@ -286,9 +301,10 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> OrderBy<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
{
var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns<TDto>(withAlias: false)
: fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.OrderBy(columns);
}
@@ -301,7 +317,7 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> OrderByDescending<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, object>> field)
{
return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ") DESC");
return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ") DESC");
}
/// <summary>
@@ -313,9 +329,10 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> OrderByDescending<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
{
var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns<TDto>(withAlias: false)
: fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.OrderBy(columns.Select(x => x + " DESC"));
}
@@ -339,7 +356,7 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> GroupBy<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, object>> field)
{
return sql.GroupBy(GetFieldName(field, sql.SqlContext.SqlSyntax));
return sql.GroupBy(sql.SqlContext.SqlSyntax.GetFieldName(field));
}
/// <summary>
@@ -351,9 +368,10 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> GroupBy<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
{
var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns<TDto>(withAlias: false)
: fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.GroupBy(columns);
}
@@ -366,9 +384,10 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> AndBy<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
{
var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns<TDto>(withAlias: false)
: fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.Append(", " + string.Join(", ", columns));
}
@@ -381,9 +400,10 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> AndByDescending<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
{
var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns<TDto>(withAlias: false)
: fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.Append(", " + string.Join(", ", columns.Select(x => x + " DESC")));
}
@@ -572,9 +592,10 @@ namespace Umbraco.Core.Persistence
public static Sql<ISqlContext> SelectCount<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
{
if (sql == null) throw new ArgumentNullException(nameof(sql));
var sqlSyntax = sql.SqlContext.SqlSyntax;
var columns = fields.Length == 0
? sql.GetColumns<TDto>(withAlias: false)
: fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray();
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
return sql.Select("COUNT (" + string.Join(", ", columns) + ")");
}
@@ -906,7 +927,7 @@ namespace Umbraco.Core.Persistence
public SqlUpd<TDto> Set(Expression<Func<TDto, object>> fieldSelector, object value)
{
var fieldName = GetFieldName(fieldSelector, _sqlContext.SqlSyntax);
var fieldName = _sqlContext.SqlSyntax.GetFieldName(fieldSelector);
_setExpressions.Add(new Tuple<string, object>(fieldName, value));
return this;
}
@@ -1062,17 +1083,6 @@ namespace Umbraco.Core.Persistence
return string.IsNullOrWhiteSpace(attr?.Name) ? column.Name : attr.Name;
}
private static string GetFieldName<TDto>(Expression<Func<TDto, object>> fieldSelector, ISqlSyntaxProvider sqlSyntax)
{
var field = ExpressionHelper.FindProperty(fieldSelector).Item1 as PropertyInfo;
var fieldName = field.GetColumnName();
var type = typeof (TDto);
var tableName = type.GetTableName();
return sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(fieldName);
}
internal static void WriteToConsole(this Sql sql)
{
Console.WriteLine(sql.SQL);

View File

@@ -0,0 +1,78 @@
using System;
using System.Linq.Expressions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence
{
/// <summary>
/// Provides extension methods to <see cref="ISqlContext"/>.
/// </summary>
public static class SqlContextExtensions
{
/// <summary>
/// Visit an expression.
/// </summary>
/// <typeparam name="TDto">The type of the DTO.</typeparam>
/// <param name="sqlContext">An <see cref="ISqlContext"/>.</param>
/// <param name="expression">An expression to visit.</param>
/// <param name="alias">An optional table alias.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto>(this ISqlContext sqlContext, Expression<Func<TDto, object>> expression, string alias = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto>(sqlContext, alias);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
}
/// <summary>
/// Visit an expression.
/// </summary>
/// <typeparam name="TDto">The type of the DTO.</typeparam>
/// <typeparam name="TOut">The type returned by the expression.</typeparam>
/// <param name="sqlContext">An <see cref="ISqlContext"/>.</param>
/// <param name="expression">An expression to visit.</param>
/// <param name="alias">An optional table alias.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto, TOut>(this ISqlContext sqlContext, Expression<Func<TDto, TOut>> expression, string alias = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto>(sqlContext, alias);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
}
/// <summary>
/// Visit an expression.
/// </summary>
/// <typeparam name="TDto1">The type of the first DTO.</typeparam>
/// <typeparam name="TDto2">The type of the second DTO.</typeparam>
/// <param name="sqlContext">An <see cref="ISqlContext"/>.</param>
/// <param name="expression">An expression to visit.</param>
/// <param name="alias1">An optional table alias for the first DTO.</param>
/// <param name="alias2">An optional table alias for the second DTO.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto1, TDto2>(this ISqlContext sqlContext, Expression<Func<TDto1, TDto2, object>> expression, string alias1 = null, string alias2 = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto1, TDto2>(sqlContext, alias1, alias2);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
}
/// <summary>
/// Visit an expression.
/// </summary>
/// <typeparam name="TDto1">The type of the first DTO.</typeparam>
/// <typeparam name="TDto2">The type of the second DTO.</typeparam>
/// <typeparam name="TOut">The type returned by the expression.</typeparam>
/// <param name="sqlContext">An <see cref="ISqlContext"/>.</param>
/// <param name="expression">An expression to visit.</param>
/// <param name="alias1">An optional table alias for the first DTO.</param>
/// <param name="alias2">An optional table alias for the second DTO.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto1, TDto2, TOut>(this ISqlContext sqlContext, Expression<Func<TDto1, TDto2, TOut>> expression, string alias1 = null, string alias2 = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto1, TDto2>(sqlContext, alias1, alias2);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
using NPoco;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
/// <summary>
/// Provides extension methods to <see cref="ISqlSyntaxProvider"/>.
/// </summary>
public static class SqlSyntaxExtensions
{
private static string GetTableName(this Type type)
{
// todo: returning string.Empty for now
// BUT the code bits that calls this method cannot deal with string.Empty so we
// should either throw, or fix these code bits...
var attr = type.FirstAttribute<TableNameAttribute>();
return string.IsNullOrWhiteSpace(attr?.Value) ? string.Empty : attr.Value;
}
private static string GetColumnName(this PropertyInfo column)
{
var attr = column.FirstAttribute<ColumnAttribute>();
return string.IsNullOrWhiteSpace(attr?.Name) ? column.Name : attr.Name;
}
/// <summary>
/// Gets a quoted table and field name.
/// </summary>
/// <typeparam name="TDto">The type of the DTO.</typeparam>
/// <param name="sqlSyntax">An <see cref="ISqlSyntaxProvider"/>.</param>
/// <param name="fieldSelector">An expression specifying the field.</param>
/// <param name="tableAlias">An optional table alias.</param>
/// <returns></returns>
public static string GetFieldName<TDto>(this ISqlSyntaxProvider sqlSyntax, Expression<Func<TDto, object>> fieldSelector, string tableAlias = null)
{
var field = ExpressionHelper.FindProperty(fieldSelector).Item1 as PropertyInfo;
var fieldName = field.GetColumnName();
var type = typeof(TDto);
var tableName = tableAlias ?? type.GetTableName();
return sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(fieldName);
}
}
}

View File

@@ -406,6 +406,8 @@
<Compile Include="Persistence\Repositories\IConsentRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\AuditEntryRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\ConsentRepository.cs" />
<Compile Include="Persistence\SqlContextExtensions.cs" />
<Compile Include="Persistence\SqlSyntaxExtensions.cs" />
<Compile Include="Persistence\UmbracoPocoDataBuilder.cs" />
<Compile Include="PropertyEditors\ColorPickerConfiguration.cs" />
<Compile Include="PropertyEditors\ConfigurationEditorOfTConfiguration.cs" />