U4-9111 Benchmark BulkCopy changes from U4-9107

This commit is contained in:
Shannon
2016-10-27 17:36:08 +02:00
parent 7495d89a79
commit 21e2c35ab0
13 changed files with 554 additions and 216 deletions

View File

@@ -8,7 +8,17 @@ namespace Umbraco.Core.Persistence.Mappers
{
public abstract class BaseMapper
{
private readonly ISqlSyntaxProvider _sqlSyntax;
protected BaseMapper() : this(SqlSyntaxContext.SqlSyntaxProvider)
{
}
protected BaseMapper(ISqlSyntaxProvider sqlSyntax)
{
_sqlSyntax = sqlSyntax;
}
internal abstract ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache { get; }
internal abstract void BuildMap();
@@ -58,8 +68,8 @@ namespace Umbraco.Core.Persistence.Mappers
string columnName = columnAttribute.Name;
string columnMap = string.Format("{0}.{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(tableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(columnName));
_sqlSyntax.GetQuotedTableName(tableName),
_sqlSyntax.GetQuotedColumnName(columnName));
return columnMap;
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Linq.Expressions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -16,6 +17,11 @@ namespace Umbraco.Core.Persistence.Mappers
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
public ContentMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax)
{
}
//NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it
// otherwise that would fail because there is no public constructor.
public ContentMapper()

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
@@ -10,22 +11,98 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal abstract class BaseExpressionHelper<T> : BaseExpressionHelper
/// <summary>
/// This is used to determine if the expression result is cached and therefore only the SQL parameters will be extracted
/// </summary>
/// <remarks>
/// This saves some performance overhead since the SQL string itself does not get generated because it already exists.
/// </remarks>
internal class CachedExpression : Expression
{
public CachedExpression()
{
CompiledOutput = null;
}
public Expression InnerExpression { get; private set; }
/// <summary>
/// The compiled SQL statement output
/// </summary>
public string CompiledOutput { get; private set; }
public bool IsCompiled
{
get { return CompiledOutput.IsNullOrWhiteSpace() == false; }
}
public void Compile(string output)
{
if (IsCompiled)
throw new InvalidOperationException("Cached expression is already compiled");
CompiledOutput = output;
}
public void Wrap(Expression exp)
{
InnerExpression = exp;
}
}
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression
/// </summary>
/// <remarks>
/// Logic that is shared with the expression helpers. This object stores state, it cannot be re-used to parse an expression.
/// </remarks>
internal abstract class BaseExpressionHelper
{
protected BaseExpressionHelper(ISqlSyntaxProvider sqlSyntax)
{
SqlSyntax = sqlSyntax;
}
/// <summary>
/// Indicates that the SQL statement has already been compiled, so Visiting will just generate the Sql Parameters
/// </summary>
protected bool IsCompiled { get; set; }
protected ISqlSyntaxProvider SqlSyntax { get; private set; }
protected List<object> SqlParameters = new List<object>();
protected abstract string VisitMemberAccess(MemberExpression m);
protected internal virtual string Visit(Expression exp)
{
//set the flag if it is already compiled
var compiledExp = exp as CachedExpression;
if (compiledExp != null)
{
if (compiledExp.IsCompiled)
{
IsCompiled = true;
}
exp = compiledExp.InnerExpression;
}
if (exp == null) return string.Empty;
string result;
switch (exp.NodeType)
{
case ExpressionType.Lambda:
return VisitLambda(exp as LambdaExpression);
result = VisitLambda(exp as LambdaExpression);
break;
case ExpressionType.MemberAccess:
return VisitMemberAccess(exp as MemberExpression);
result = VisitMemberAccess(exp as MemberExpression);
break;
case ExpressionType.Constant:
return VisitConstant(exp as ConstantExpression);
result = VisitConstant(exp as ConstantExpression);
break;
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
@@ -49,7 +126,8 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.RightShift:
case ExpressionType.LeftShift:
case ExpressionType.ExclusiveOr:
return VisitBinary(exp as BinaryExpression);
result = VisitBinary(exp as BinaryExpression);
break;
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
@@ -58,19 +136,37 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.ArrayLength:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
return VisitUnary(exp as UnaryExpression);
result = VisitUnary(exp as UnaryExpression);
break;
case ExpressionType.Parameter:
return VisitParameter(exp as ParameterExpression);
result = VisitParameter(exp as ParameterExpression);
break;
case ExpressionType.Call:
return VisitMethodCall(exp as MethodCallExpression);
result = VisitMethodCall(exp as MethodCallExpression);
break;
case ExpressionType.New:
return VisitNew(exp as NewExpression);
result = VisitNew(exp as NewExpression);
break;
case ExpressionType.NewArrayInit:
case ExpressionType.NewArrayBounds:
return VisitNewArray(exp as NewArrayExpression);
result = VisitNewArray(exp as NewArrayExpression);
break;
default:
return exp.ToString();
result = exp.ToString();
break;
}
if (compiledExp != null)
{
if (compiledExp.IsCompiled == false)
{
compiledExp.Compile(result);
}
return compiledExp.CompiledOutput;
}
return result;
}
protected virtual string VisitLambda(LambdaExpression lambda)
@@ -79,14 +175,20 @@ namespace Umbraco.Core.Persistence.Querying
{
var m = lambda.Body as MemberExpression;
if (m.Expression != null)
if (m != null && m.Expression != null)
{
//This deals with members that are boolean (i.e. x => IsTrashed )
string r = VisitMemberAccess(m);
SqlParameters.Add(true);
return string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//return string.Format("{0}={1}", r, GetQuotedTrueValue());
SqlParameters.Add(true);
//don't execute if compiled
if (IsCompiled == false)
{
return string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
}
}
@@ -95,8 +197,10 @@ namespace Umbraco.Core.Persistence.Querying
protected virtual string VisitBinary(BinaryExpression b)
{
string left, right;
var operand = BindOperant(b.NodeType);
var left = string.Empty;
var right = string.Empty;
var operand = BindOperant(b.NodeType);
if (operand == "AND" || operand == "OR")
{
MemberExpression m = b.Left as MemberExpression;
@@ -105,9 +209,12 @@ namespace Umbraco.Core.Persistence.Querying
string r = VisitMemberAccess(m);
SqlParameters.Add(1);
left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//left = string.Format("{0}={1}", r, GetQuotedTrueValue());
//don't execute if compiled
if (IsCompiled == false)
{
left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
}
}
else
{
@@ -119,9 +226,12 @@ namespace Umbraco.Core.Persistence.Querying
string r = VisitMemberAccess(m);
SqlParameters.Add(1);
right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//right = string.Format("{0}={1}", r, GetQuotedTrueValue());
//don't execute if compiled
if (IsCompiled == false)
{
right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
}
}
else
{
@@ -132,14 +242,14 @@ namespace Umbraco.Core.Persistence.Querying
{
// deal with (x == true|false) - most common
var constRight = b.Right as ConstantExpression;
if (constRight != null && constRight.Type == typeof (bool))
return ((bool) constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left);
if (constRight != null && constRight.Type == typeof(bool))
return ((bool)constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left);
right = Visit(b.Right);
// deal with (true|false == x) - why not
var constLeft = b.Left as ConstantExpression;
if (constLeft != null && constLeft.Type == typeof (bool))
return ((bool) constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right);
if (constLeft != null && constLeft.Type == typeof(bool))
return ((bool)constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right);
left = Visit(b.Left);
}
else if (operand == "<>")
@@ -147,13 +257,13 @@ namespace Umbraco.Core.Persistence.Querying
// deal with (x != true|false) - most common
var constRight = b.Right as ConstantExpression;
if (constRight != null && constRight.Type == typeof(bool))
return ((bool) constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left);
return ((bool)constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left);
right = Visit(b.Right);
// deal with (true|false != x) - why not
var constLeft = b.Left as ConstantExpression;
if (constLeft != null && constLeft.Type == typeof(bool))
return ((bool) constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right);
return ((bool)constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right);
left = Visit(b.Left);
}
else
@@ -178,9 +288,21 @@ namespace Umbraco.Core.Persistence.Querying
{
case "MOD":
case "COALESCE":
return string.Format("{0}({1},{2})", operand, left, right);
//don't execute if compiled
if (IsCompiled == false)
{
return string.Format("{0}({1},{2})", operand, left, right);
}
//already compiled, return
return string.Empty;
default:
return "(" + left + " " + operand + " " + right + ")";
//don't execute if compiled
if (IsCompiled == false)
{
return string.Concat("(", left, " ", operand, " ", right, ")");
}
//already compiled, return
return string.Empty;
}
}
@@ -213,22 +335,33 @@ namespace Umbraco.Core.Persistence.Querying
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o.GetType());
//don't execute if compiled
if (IsCompiled == false)
{
return string.Format("@{0}", SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
}
catch (InvalidOperationException)
{
// FieldName ?
List<Object> exprs = VisitExpressionList(nex.Arguments);
var r = new StringBuilder();
foreach (Object e in exprs)
{
//don't execute if compiled
if (IsCompiled == false)
{
r.AppendFormat("{0}{1}",
r.Length > 0 ? "," : "",
e);
// FieldName ?
List<Object> exprs = VisitExpressionList(nex.Arguments);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.AppendFormat("{0}{1}",
r.Length > 0 ? "," : "",
e);
}
return r.ToString();
}
return r.ToString();
//already compiled, return
return string.Empty;
}
}
@@ -244,14 +377,14 @@ namespace Umbraco.Core.Persistence.Querying
return "null";
SqlParameters.Add(c.Value);
return string.Format("@{0}", SqlParameters.Count - 1);
//if (c.Value is bool)
//{
// object o = GetQuotedValue(c.Value, c.Value.GetType());
// return string.Format("({0}={1})", GetQuotedTrueValue(), o);
//}
//return GetQuotedValue(c.Value, c.Value.GetType());
//don't execute if compiled
if (IsCompiled == false)
{
return string.Format("@{0}", SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
}
protected virtual string VisitUnary(UnaryExpression u)
@@ -277,10 +410,22 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.MemberAccess:
// false property , i.e. x => !Trashed
SqlParameters.Add(true);
return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1);
//don't execute if compiled
if (IsCompiled == false)
{
return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
default:
// could be anything else, such as: x => !x.Path.StartsWith("-20")
return "NOT (" + o + ")";
//don't execute if compiled
if (IsCompiled == false)
{
// could be anything else, such as: x => !x.Path.StartsWith("-20")
return string.Concat("NOT (", o, ")");
}
//already compiled, return
return string.Empty;
}
}
@@ -293,7 +438,14 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.MemberAccess:
// true property, i.e. x => Trashed
SqlParameters.Add(true);
return string.Format("({0} = @{1})", o, SqlParameters.Count - 1);
//don't execute if compiled
if (IsCompiled == false)
{
return string.Format("({0} = @{1})", o, SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
default:
// could be anything else, such as: x => x.Path.StartsWith("-20")
return o;
@@ -302,15 +454,22 @@ namespace Umbraco.Core.Persistence.Querying
protected virtual string VisitNewArray(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.Append(r.Length > 0 ? "," + e : e);
}
return r.ToString();
//don't execute if compiled
if (IsCompiled == false)
{
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.Append(r.Length > 0 ? "," + e : e);
}
return r.ToString();
}
//already compiled, return
return string.Empty;
}
protected virtual List<Object> VisitNewArrayFromExpressionList(NewArrayExpression na)
@@ -375,19 +534,31 @@ namespace Umbraco.Core.Persistence.Querying
var objectForMethod = m.Object ?? m.Arguments[0];
var visitedObjectForMethod = Visit(objectForMethod);
var methodArgs = m.Object == null
? m.Arguments.Skip(1).ToArray()
var methodArgs = m.Object == null
? m.Arguments.Skip(1).ToArray()
: m.Arguments.ToArray();
switch (m.Method.Name)
{
case "ToString":
SqlParameters.Add(objectForMethod.ToString());
return string.Format("@{0}", SqlParameters.Count - 1);
//don't execute if compiled
if (IsCompiled == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
case "ToUpper":
return string.Format("upper({0})", visitedObjectForMethod);
//don't execute if compiled
if (IsCompiled == false)
return string.Format("upper({0})", visitedObjectForMethod);
//already compiled, return
return string.Empty;
case "ToLower":
return string.Format("lower({0})", visitedObjectForMethod);
//don't execute if compiled
if (IsCompiled == false)
return string.Format("lower({0})", visitedObjectForMethod);
//already compiled, return
return string.Empty;
case "SqlWildcard":
case "StartsWith":
case "EndsWith":
@@ -401,7 +572,7 @@ namespace Umbraco.Core.Persistence.Querying
case "InvariantEndsWith":
case "InvariantContains":
case "InvariantEquals":
string compareValue;
if (methodArgs[0].NodeType != ExpressionType.Constant)
@@ -488,7 +659,12 @@ namespace Umbraco.Core.Persistence.Querying
SqlParameters.Add(RemoveQuote(replaceValue));
return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1);
//don't execute if compiled
if (IsCompiled == false)
return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
//case "Substring":
// var startIndex = Int32.Parse(args[0].ToString()) + 1;
// if (args.Count == 2)
@@ -555,77 +731,42 @@ namespace Umbraco.Core.Persistence.Querying
throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name);
//var s2 = new StringBuilder();
//foreach (Object e in args)
//{
// s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType()));
//}
//return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString());
//var s2 = new StringBuilder();
//foreach (Object e in args)
//{
// s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType()));
//}
//return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString());
}
}
public virtual string GetQuotedTableName(string tableName)
{
//already compiled, return
if (IsCompiled)
return tableName;
return string.Format("\"{0}\"", tableName);
}
public virtual string GetQuotedColumnName(string columnName)
{
//already compiled, return
if (IsCompiled)
return columnName;
return string.Format("\"{0}\"", columnName);
}
public virtual string GetQuotedName(string name)
{
//already compiled, return
if (IsCompiled)
return name;
return string.Format("\"{0}\"", name);
}
//private string GetQuotedTrueValue()
//{
// return GetQuotedValue(true, typeof(bool));
//}
//private string GetQuotedFalseValue()
//{
// return GetQuotedValue(false, typeof(bool));
//}
//public virtual string GetQuotedValue(object value, Type fieldType)
//{
// return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue);
//}
//private string GetTrueExpression()
//{
// object o = GetQuotedTrueValue();
// return string.Format("({0}={1})", o, o);
//}
//private string GetFalseExpression()
//{
// return string.Format("({0}={1})",
// GetQuotedTrueValue(),
// GetQuotedFalseValue());
//}
//private bool IsTrueExpression(string exp)
//{
// return (exp == GetTrueExpression());
//}
//private bool IsFalseExpression(string exp)
//{
// return (exp == GetFalseExpression());
//}
}
/// <summary>
/// Logic that is shared with the expression helpers
/// </summary>
internal class BaseExpressionHelper
{
protected List<object> SqlParameters = new List<object>();
public object[] GetSqlParameters()
{
return SqlParameters.ToArray();
@@ -637,25 +778,45 @@ namespace Umbraco.Core.Persistence.Querying
{
case "SqlWildcard":
SqlParameters.Add(RemoveQuote(val));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (IsCompiled == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "Equals":
SqlParameters.Add(RemoveQuote(val));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (IsCompiled == false)
return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "StartsWith":
SqlParameters.Add(string.Format("{0}{1}",
RemoveQuote(val),
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder()));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
SqlSyntax.GetWildcardPlaceholder()));
//don't execute if compiled
if (IsCompiled == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "EndsWith":
SqlParameters.Add(string.Format("{0}{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
SqlSyntax.GetWildcardPlaceholder(),
RemoveQuote(val)));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (IsCompiled == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "Contains":
SqlParameters.Add(string.Format("{0}{1}{0}",
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
SqlSyntax.GetWildcardPlaceholder(),
RemoveQuote(val)));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (IsCompiled == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "InvariantEquals":
case "SqlEquals":
//recurse
@@ -730,7 +891,7 @@ namespace Umbraco.Core.Persistence.Querying
{
return paramValue == null
? string.Empty
: SqlSyntaxContext.SqlSyntaxProvider.EscapeString(paramValue.ToString());
: SqlSyntax.EscapeString(paramValue.ToString());
}
public virtual bool ShouldQuoteValue(Type fieldType)
@@ -740,16 +901,9 @@ namespace Umbraco.Core.Persistence.Querying
protected virtual string RemoveQuote(string exp)
{
//if (exp.StartsWith("'") && exp.EndsWith("'"))
//{
// exp = exp.Remove(0, 1);
// exp = exp.Remove(exp.Length - 1, 1);
//}
//return exp;
if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'"))
&&
(exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'")))
&&
(exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'")))
{
exp = exp.Remove(0, 1);
exp = exp.Remove(exp.Length - 1, 1);

View File

@@ -1,31 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
/// We have to wait till v8 to make this change I suppose.
/// </summary>
internal static class QueryExtensions
{
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
{
var q = query as Query<T>;
if (q == null)
{
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
}
return q.GetWhereClauses();
}
}
/// <summary>
/// Represents a query for building Linq translatable SQL queries
/// </summary>

View File

@@ -11,14 +11,24 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class ModelToSqlExpressionHelper<T> : BaseExpressionHelper<T>
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression based on Umbraco's business logic Models
/// </summary>
/// <remarks>
/// This object stores state, it cannot be re-used to parse an expression
/// </remarks>
internal class ModelToSqlExpressionHelper<T> : BaseExpressionHelper
{
private readonly BaseMapper _mapper;
public ModelToSqlExpressionHelper()
public ModelToSqlExpressionHelper(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper) : base(sqlSyntax)
{
_mapper = mapper;
}
public ModelToSqlExpressionHelper() : this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T)))
{
_mapper = MappingResolver.Current.ResolveMapperByType(typeof(T));
}
protected override string VisitMemberAccess(MemberExpression m)
@@ -27,18 +37,30 @@ namespace Umbraco.Core.Persistence.Querying
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
//don't execute if compiled
if (IsCompiled == false)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
//don't execute if compiled
if (IsCompiled == false)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
var member = Expression.Convert(m, typeof(object));
@@ -47,16 +69,13 @@ namespace Umbraco.Core.Persistence.Querying
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
//don't execute if compiled
if (IsCompiled == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
}
//protected bool IsFieldName(string quotedExp)
//{
// //Not entirely sure this is reliable, but its better then simply returning true
// return quotedExp.LastIndexOf("'", StringComparison.InvariantCultureIgnoreCase) + 1 != quotedExp.Length;
//}
}
}

View File

@@ -9,11 +9,18 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class PocoToSqlExpressionHelper<T> : BaseExpressionHelper<T>
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression based on Umbraco's PetaPoco dto Models
/// </summary>
/// <remarks>
/// This object stores state, it cannot be re-used to parse an expression
/// </remarks>
internal class PocoToSqlExpressionHelper<T> : BaseExpressionHelper
{
private readonly Database.PocoData _pd;
public PocoToSqlExpressionHelper()
public PocoToSqlExpressionHelper() : base(SqlSyntaxContext.SqlSyntaxProvider)
{
_pd = new Database.PocoData(typeof(T));
}
@@ -24,14 +31,26 @@ namespace Umbraco.Core.Persistence.Querying
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
//don't execute if compiled
if (IsCompiled == false)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
//don't execute if compiled
if (IsCompiled == false)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
var member = Expression.Convert(m, typeof(object));
@@ -40,23 +59,21 @@ namespace Umbraco.Core.Persistence.Querying
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
//don't execute if compiled
if (IsCompiled == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
}
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
return string.Format("{0}.{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName));
SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntax.GetQuotedColumnName(column.Value.ColumnName));
}
//protected bool IsFieldName(string quotedExp)
//{
// return true;
//}
}
}

View File

@@ -30,6 +30,7 @@ namespace Umbraco.Core.Persistence.Querying
{
if (predicate != null)
{
//TODO: This should have an SqlSyntax object passed in, this ctor is relying on a singleton
var expressionHelper = new ModelToSqlExpressionHelper<T>();
string whereExpression = expressionHelper.Visit(predicate);

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
/// We have to wait till v8 to make this change I suppose.
/// </summary>
internal static class QueryExtensions
{
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
{
var q = query as Query<T>;
if (q == null)
{
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
}
return q.GetWhereClauses();
}
}
}

View File

@@ -474,6 +474,7 @@
<Compile Include="Media\Exif\Utility.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeOne\UpdateUserLanguagesToIsoCode.cs" />
<Compile Include="Persistence\PocoDataDataReader.cs" />
<Compile Include="Persistence\Querying\QueryExtensions.cs" />
<Compile Include="Persistence\RecordPersistenceType.cs" />
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
<Compile Include="Persistence\Repositories\AuditRepository.cs" />

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;
using System.Linq.Expressions;
using System.Threading;
using System.Xml;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.Migrations.Initial;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Tests.TestHelpers;
using ILogger = Umbraco.Core.Logging.ILogger;
namespace Umbraco.Tests.Benchmarks
{
[Config(typeof(Config))]
public class ModelToSqlExpressionHelperBenchmarks
{
private class Config : ManualConfig
{
public Config()
{
Add(new MemoryDiagnoser());
}
}
public ModelToSqlExpressionHelperBenchmarks()
{
_contentMapper = new ContentMapper(_syntaxProvider);
_contentMapper.BuildMap();
_cachedExpression = new CachedExpression();
}
private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider();
private readonly BaseMapper _contentMapper;
private readonly CachedExpression _cachedExpression;
//private static readonly Expression<Func<IContent, bool>> TemplatePredicate = content =>
// content.Path.StartsWith(string.Empty) && content.Published && (content.ContentTypeId == 0 || content.ContentTypeId == 0);
[Benchmark(Baseline = true)]
public void WithNonCached()
{
for (int i = 0; i < 100; i++)
{
var a = i;
var b = i*10;
Expression<Func<IContent, bool>> predicate = content =>
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>(_syntaxProvider, _contentMapper);
var result = modelToSqlExpressionHelper.Visit(predicate);
}
}
[Benchmark]
public void WithSQL()
{
var subQuery = new Sql()
//.Select("umbracoNode.id as nodeId")
.From<ContentDto>(_syntaxProvider)
.InnerJoin<NodeDto>(_syntaxProvider)
.On<ContentDto, NodeDto>(_syntaxProvider, left => left.NodeId, right => right.NodeId)
.WhereIn<ContentDto>(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
}
[Benchmark()]
public void WithCachedExpression()
{
for (int i = 0; i < 100; i++)
{
var a = i;
var b = i * 10;
Expression<Func<IContent, bool>> predicate = content =>
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>(_syntaxProvider, _contentMapper);
//wrap it!
_cachedExpression.Wrap(predicate);
var result = modelToSqlExpressionHelper.Visit(_cachedExpression);
}
}
}
}

View File

@@ -11,7 +11,8 @@ namespace Umbraco.Tests.Benchmarks
{
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<BulkInsertBenchmarks>();
var summary = BenchmarkRunner.Run<ModelToSqlExpressionHelperBenchmarks>();
//var summary = BenchmarkRunner.Run<BulkInsertBenchmarks>();
Console.ReadLine();
}

View File

@@ -96,6 +96,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ModelToSqlExpressionHelperBenchmarks.cs" />
<Compile Include="BulkInsertBenchmarks.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -103,16 +104,11 @@
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
<None Include="_TraceEventProgrammersGuide.docx" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
</ItemGroup>
<ItemGroup>
<Content Include="TraceEvent.ReadMe.txt" />
<Content Include="TraceEvent.ReleaseNotes.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
<Project>{31785bc3-256c-4613-b2f5-a1b0bdded8c1}</Project>

View File

@@ -16,19 +16,45 @@ namespace Umbraco.Tests.Persistence.Querying
[TestFixture]
public class ExpressionTests : BaseUsingSqlCeSyntax
{
// [Test]
// public void Can_Query_With_Content_Type_Alias()
// {
// //Arrange
// Expression<Func<IMedia, bool>> predicate = content => content.ContentType.Alias == "Test";
// var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
// var result = modelToSqlExpressionHelper.Visit(predicate);
// [Test]
// public void Can_Query_With_Content_Type_Alias()
// {
// //Arrange
// Expression<Func<IMedia, bool>> predicate = content => content.ContentType.Alias == "Test";
// var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
// var result = modelToSqlExpressionHelper.Visit(predicate);
// Debug.Print("Model to Sql ExpressionHelper: \n" + result);
// Debug.Print("Model to Sql ExpressionHelper: \n" + result);
// Assert.AreEqual("[cmsContentType].[alias] = @0", result);
// Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]);
// }
// Assert.AreEqual("[cmsContentType].[alias] = @0", result);
// Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]);
// }
[Test]
public void CachedExpression_Can_Verify_Path_StartsWith_Predicate_In_Same_Result()
{
//Arrange
//use a single cached expression for multiple expressions and ensure the correct output
// is done for both of them.
var cachedExpression = new CachedExpression();
Expression<Func<IContent, bool>> predicate1 = content => content.Path.StartsWith("-1");
cachedExpression.Wrap(predicate1);
var modelToSqlExpressionHelper1 = new ModelToSqlExpressionHelper<IContent>();
var result1 = modelToSqlExpressionHelper1.Visit(cachedExpression);
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result1);
Assert.AreEqual("-1%", modelToSqlExpressionHelper1.GetSqlParameters()[0]);
Expression<Func<IContent, bool>> predicate2 = content => content.Path.StartsWith("-1,123,97");
cachedExpression.Wrap(predicate2);
var modelToSqlExpressionHelper2 = new ModelToSqlExpressionHelper<IContent>();
var result2 = modelToSqlExpressionHelper2.Visit(cachedExpression);
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result2);
Assert.AreEqual("-1,123,97%", modelToSqlExpressionHelper2.GetSqlParameters()[0]);
}
[Test]
public void Can_Verify_Path_StartsWith_Predicate_In_Same_Result()
@@ -37,9 +63,7 @@ namespace Umbraco.Tests.Persistence.Querying
Expression<Func<IContent, bool>> predicate = content => content.Path.StartsWith("-1");
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result);
Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}