diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs
index 087ed6a3e2..b97a3cfacf 100644
--- a/src/Umbraco.Core/HashCodeCombiner.cs
+++ b/src/Umbraco.Core/HashCodeCombiner.cs
@@ -34,6 +34,12 @@ namespace Umbraco.Core
AddInt(d.GetHashCode());
}
+ internal void AddString(string s)
+ {
+ if (s != null)
+ AddInt((StringComparer.InvariantCulture).GetHashCode(s));
+ }
+
internal void AddCaseInsensitiveString(string s)
{
if (s != null)
diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs
index d8507ac681..6feee12f99 100644
--- a/src/Umbraco.Core/Persistence/PetaPoco.cs
+++ b/src/Umbraco.Core/Persistence/PetaPoco.cs
@@ -13,6 +13,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.Caching;
using System.Security;
using System.Security.Permissions;
using System.Text;
@@ -1709,8 +1710,32 @@ namespace Umbraco.Core.Persistence
}
public override object ChangeType(object val) { return val; }
}
- public class PocoData
- {
+
+ ///
+ /// Container for a Memory cache object
+ ///
+ ///
+ /// Better to have one memory cache instance than many so it's memory management can be handled more effectively
+ /// http://stackoverflow.com/questions/8463962/using-multiple-instances-of-memorycache
+ ///
+ internal class ManagedCache
+ {
+ public ObjectCache GetCache()
+ {
+ return ObjectCache;
+ }
+
+ static readonly ObjectCache ObjectCache = new MemoryCache("NPoco");
+
+ }
+
+ public class PocoData
+ {
+ //USE ONLY FOR TESTING
+ internal static bool UseLongKeys = false;
+ //USE ONLY FOR TESTING - default is one hr
+ internal static int SlidingExpirationSeconds = 3600;
+
public static PocoData ForObject(object o, string primaryKeyName)
{
var t = o.GetType();
@@ -1734,7 +1759,7 @@ namespace Umbraco.Core.Persistence
#endif
return ForType(t);
}
- static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim();
+
public static PocoData ForType(Type t)
{
#if !PETAPOCO_NO_DYNAMIC
@@ -1742,7 +1767,7 @@ namespace Umbraco.Core.Persistence
throw new InvalidOperationException("Can't use dynamic types with this method");
#endif
// Check cache
- RWLock.EnterReadLock();
+ InnerLock.EnterReadLock();
PocoData pd;
try
{
@@ -1751,12 +1776,12 @@ namespace Umbraco.Core.Persistence
}
finally
{
- RWLock.ExitReadLock();
+ InnerLock.ExitReadLock();
}
// Cache it
- RWLock.EnterWriteLock();
+ InnerLock.EnterWriteLock();
try
{
// Check again
@@ -1770,7 +1795,7 @@ namespace Umbraco.Core.Persistence
}
finally
{
- RWLock.ExitWriteLock();
+ InnerLock.ExitWriteLock();
}
return pd;
@@ -1851,224 +1876,237 @@ namespace Umbraco.Core.Persistence
return tc >= TypeCode.SByte && tc <= TypeCode.UInt64;
}
+
+
// Create factory function that can convert a IDataReader record into a POCO
public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r)
{
- // Check cache
- var key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns);
- RWLock.EnterReadLock();
- try
- {
- // Have we already created it?
- Delegate factory;
- if (PocoFactories.TryGetValue(key, out factory))
- return factory;
- }
- finally
- {
- RWLock.ExitReadLock();
- }
- // Take the writer lock
- RWLock.EnterWriteLock();
+ //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required.
+ // This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are
+ // returning the same structured data:
+ // SELECT * FROM MyTable ORDER BY MyColumn
+ // SELECT * FROM MyTable ORDER BY MyColumn DESC
- try
- {
+ string key;
+ if (UseLongKeys)
+ {
+ key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns);
+ }
+ else
+ {
+ //Create a hashed key, we don't want to store so much string data in memory
+ var combiner = new HashCodeCombiner();
+ combiner.AddCaseInsensitiveString(sql);
+ combiner.AddCaseInsensitiveString(connString);
+ combiner.AddObject(ForceDateTimesToUtc);
+ combiner.AddInt(firstColumn);
+ combiner.AddInt(countColumns);
+ key = combiner.GetCombinedHashCode();
+ }
+
- // Check again, just in case
- Delegate factory;
- if (PocoFactories.TryGetValue(key, out factory))
- return factory;
+ var objectCache = _managedCache.GetCache();
- // Create the method
- var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader) }, true);
- var il = m.GetILGenerator();
+ Func factory = () =>
+ {
+ // Create the method
+ var m = new DynamicMethod("petapoco_factory_" + objectCache.GetCount(), type, new Type[] { typeof(IDataReader) }, true);
+ var il = m.GetILGenerator();
#if !PETAPOCO_NO_DYNAMIC
- if (type == typeof(object))
- {
- // var poco=new T()
- il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj
+ if (type == typeof(object))
+ {
+ // var poco=new T()
+ il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj
- MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add");
+ MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add");
- // Enumerate all fields generating a set assignment for the column
- for (int i = firstColumn; i < firstColumn + countColumns; i++)
- {
- var srcType = r.GetFieldType(i);
+ // Enumerate all fields generating a set assignment for the column
+ for (int i = firstColumn; i < firstColumn + countColumns; i++)
+ {
+ var srcType = r.GetFieldType(i);
- il.Emit(OpCodes.Dup); // obj, obj
- il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname
+ il.Emit(OpCodes.Dup); // obj, obj
+ il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname
- // Get the converter
- Func converter = null;
- if (Database.Mapper != null)
- converter = Database.Mapper.GetFromDbConverter(null, srcType);
- if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime))
- converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
+ // Get the converter
+ Func converter = null;
+ if (Database.Mapper != null)
+ converter = Database.Mapper.GetFromDbConverter(null, srcType);
+ if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime))
+ converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
- // Setup stack for call to converter
- AddConverterToStack(il, converter);
+ // Setup stack for call to converter
+ AddConverterToStack(il, converter);
- // r[i]
- il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr
- il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i
- il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value
+ // r[i]
+ il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr
+ il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i
+ il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value
- // Convert DBNull to null
- il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value
- il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null)
- var lblNotNull = il.DefineLabel();
- il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value
- il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter?
- if (converter != null)
- il.Emit(OpCodes.Pop); // obj, obj, fieldname,
- il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null
- if (converter != null)
- {
- var lblReady = il.DefineLabel();
- il.Emit(OpCodes.Br_S, lblReady);
- il.MarkLabel(lblNotNull);
- il.Emit(OpCodes.Callvirt, fnInvoke);
- il.MarkLabel(lblReady);
- }
- else
- {
- il.MarkLabel(lblNotNull);
- }
+ // Convert DBNull to null
+ il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value
+ il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null)
+ var lblNotNull = il.DefineLabel();
+ il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value
+ il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter?
+ if (converter != null)
+ il.Emit(OpCodes.Pop); // obj, obj, fieldname,
+ il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null
+ if (converter != null)
+ {
+ var lblReady = il.DefineLabel();
+ il.Emit(OpCodes.Br_S, lblReady);
+ il.MarkLabel(lblNotNull);
+ il.Emit(OpCodes.Callvirt, fnInvoke);
+ il.MarkLabel(lblReady);
+ }
+ else
+ {
+ il.MarkLabel(lblNotNull);
+ }
- il.Emit(OpCodes.Callvirt, fnAdd);
- }
- }
- else
+ il.Emit(OpCodes.Callvirt, fnAdd);
+ }
+ }
+ else
#endif
- if (type.IsValueType || type == typeof(string) || type == typeof(byte[]))
- {
- // Do we need to install a converter?
- var srcType = r.GetFieldType(0);
- var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type);
+ if (type.IsValueType || type == typeof(string) || type == typeof(byte[]))
+ {
+ // Do we need to install a converter?
+ var srcType = r.GetFieldType(0);
+ var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type);
- // "if (!rdr.IsDBNull(i))"
- il.Emit(OpCodes.Ldarg_0); // rdr
- il.Emit(OpCodes.Ldc_I4_0); // rdr,0
- il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool
- var lblCont = il.DefineLabel();
- il.Emit(OpCodes.Brfalse_S, lblCont);
- il.Emit(OpCodes.Ldnull); // null
- var lblFin = il.DefineLabel();
- il.Emit(OpCodes.Br_S, lblFin);
+ // "if (!rdr.IsDBNull(i))"
+ il.Emit(OpCodes.Ldarg_0); // rdr
+ il.Emit(OpCodes.Ldc_I4_0); // rdr,0
+ il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool
+ var lblCont = il.DefineLabel();
+ il.Emit(OpCodes.Brfalse_S, lblCont);
+ il.Emit(OpCodes.Ldnull); // null
+ var lblFin = il.DefineLabel();
+ il.Emit(OpCodes.Br_S, lblFin);
- il.MarkLabel(lblCont);
+ il.MarkLabel(lblCont);
- // Setup stack for call to converter
- AddConverterToStack(il, converter);
+ // Setup stack for call to converter
+ AddConverterToStack(il, converter);
- il.Emit(OpCodes.Ldarg_0); // rdr
- il.Emit(OpCodes.Ldc_I4_0); // rdr,0
- il.Emit(OpCodes.Callvirt, fnGetValue); // value
+ il.Emit(OpCodes.Ldarg_0); // rdr
+ il.Emit(OpCodes.Ldc_I4_0); // rdr,0
+ il.Emit(OpCodes.Callvirt, fnGetValue); // value
- // Call the converter
- if (converter != null)
- il.Emit(OpCodes.Callvirt, fnInvoke);
+ // Call the converter
+ if (converter != null)
+ il.Emit(OpCodes.Callvirt, fnInvoke);
- il.MarkLabel(lblFin);
- il.Emit(OpCodes.Unbox_Any, type); // value converted
- }
- else
- {
- // var poco=new T()
- il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
+ il.MarkLabel(lblFin);
+ il.Emit(OpCodes.Unbox_Any, type); // value converted
+ }
+ else
+ {
+ // var poco=new T()
+ il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
- // Enumerate all fields generating a set assignment for the column
- for (int i = firstColumn; i < firstColumn + countColumns; i++)
- {
- // Get the PocoColumn for this db column, ignore if not known
- PocoColumn pc;
- if (!Columns.TryGetValue(r.GetName(i), out pc))
- continue;
+ // Enumerate all fields generating a set assignment for the column
+ for (int i = firstColumn; i < firstColumn + countColumns; i++)
+ {
+ // Get the PocoColumn for this db column, ignore if not known
+ PocoColumn pc;
+ if (!Columns.TryGetValue(r.GetName(i), out pc))
+ continue;
- // Get the source type for this column
- var srcType = r.GetFieldType(i);
- var dstType = pc.PropertyInfo.PropertyType;
+ // Get the source type for this column
+ var srcType = r.GetFieldType(i);
+ var dstType = pc.PropertyInfo.PropertyType;
- // "if (!rdr.IsDBNull(i))"
- il.Emit(OpCodes.Ldarg_0); // poco,rdr
- il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i
- il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool
- var lblNext = il.DefineLabel();
- il.Emit(OpCodes.Brtrue_S, lblNext); // poco
+ // "if (!rdr.IsDBNull(i))"
+ il.Emit(OpCodes.Ldarg_0); // poco,rdr
+ il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i
+ il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool
+ var lblNext = il.DefineLabel();
+ il.Emit(OpCodes.Brtrue_S, lblNext); // poco
- il.Emit(OpCodes.Dup); // poco,poco
+ il.Emit(OpCodes.Dup); // poco,poco
- // Do we need to install a converter?
- var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType);
+ // Do we need to install a converter?
+ var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType);
- // Fast
- bool Handled = false;
- if (converter == null)
- {
- var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });
- if (valuegetter != null
- && valuegetter.ReturnType == srcType
- && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
- {
- il.Emit(OpCodes.Ldarg_0); // *,rdr
- il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
- il.Emit(OpCodes.Callvirt, valuegetter); // *,value
+ // Fast
+ bool Handled = false;
+ if (converter == null)
+ {
+ var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });
+ if (valuegetter != null
+ && valuegetter.ReturnType == srcType
+ && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
+ {
+ il.Emit(OpCodes.Ldarg_0); // *,rdr
+ il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
+ il.Emit(OpCodes.Callvirt, valuegetter); // *,value
- // Convert to Nullable
- if (Nullable.GetUnderlyingType(dstType) != null)
- {
- il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
- }
+ // Convert to Nullable
+ if (Nullable.GetUnderlyingType(dstType) != null)
+ {
+ il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
+ }
- il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
- Handled = true;
- }
- }
+ il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
+ Handled = true;
+ }
+ }
- // Not so fast
- if (!Handled)
- {
- // Setup stack for call to converter
- AddConverterToStack(il, converter);
+ // Not so fast
+ if (!Handled)
+ {
+ // Setup stack for call to converter
+ AddConverterToStack(il, converter);
- // "value = rdr.GetValue(i)"
- il.Emit(OpCodes.Ldarg_0); // *,rdr
- il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
- il.Emit(OpCodes.Callvirt, fnGetValue); // *,value
+ // "value = rdr.GetValue(i)"
+ il.Emit(OpCodes.Ldarg_0); // *,rdr
+ il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
+ il.Emit(OpCodes.Callvirt, fnGetValue); // *,value
- // Call the converter
- if (converter != null)
- il.Emit(OpCodes.Callvirt, fnInvoke);
+ // Call the converter
+ if (converter != null)
+ il.Emit(OpCodes.Callvirt, fnInvoke);
- // Assign it
- il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value
- il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
- }
+ // Assign it
+ il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value
+ il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
+ }
- il.MarkLabel(lblNext);
- }
+ il.MarkLabel(lblNext);
+ }
- var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
- if (fnOnLoaded != null)
- {
- il.Emit(OpCodes.Dup);
- il.Emit(OpCodes.Callvirt, fnOnLoaded);
- }
- }
+ var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
+ if (fnOnLoaded != null)
+ {
+ il.Emit(OpCodes.Dup);
+ il.Emit(OpCodes.Callvirt, fnOnLoaded);
+ }
+ }
- il.Emit(OpCodes.Ret);
+ il.Emit(OpCodes.Ret);
+
+ // return it
+ var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type));
+
+ return del;
+ };
+
+ //lazy usage of AddOrGetExisting ref: http://stackoverflow.com/questions/10559279/how-to-deal-with-costly-building-operations-using-memorycache/15894928#15894928
+ var newValue = new Lazy(factory);
+ // the line belows returns existing item or adds the new value if it doesn't exist
+ var value = (Lazy)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy
+ {
+ //sliding expiration of 1 hr, if the same key isn't used in this
+ // timeframe it will be removed from the cache
+ SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds)
+ });
+ return (value ?? newValue).Value; // Lazy handles the locking itself
- // Cache it, return it
- var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type));
- PocoFactories.Add(key, del);
- return del;
- }
- finally
- {
- RWLock.ExitWriteLock();
- }
}
private static void AddConverterToStack(ILGenerator il, Func converter)
@@ -2144,7 +2182,7 @@ namespace Umbraco.Core.Persistence
return default(T);
}
-
+ ManagedCache _managedCache = new ManagedCache();
static Dictionary m_PocoDatas = new Dictionary();
static List> m_Converters = new List>();
static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) });
@@ -2156,7 +2194,46 @@ namespace Umbraco.Core.Persistence
public string[] QueryColumns { get; private set; }
public TableInfo TableInfo { get; private set; }
public Dictionary Columns { get; private set; }
- Dictionary PocoFactories = new Dictionary();
+ static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim();
+
+ ///
+ /// Returns a report of the current cache being utilized by PetaPoco
+ ///
+ ///
+ public static string PrintDebugCacheReport(out double totalBytes, out IEnumerable allKeys)
+ {
+ var managedCache = new ManagedCache();
+
+ var sb = new StringBuilder();
+ sb.AppendLine("m_PocoDatas:");
+ foreach (var pocoData in m_PocoDatas)
+ {
+ sb.AppendFormat("\t{0}\n", pocoData.Key);
+ sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length);
+ }
+
+ var cache = managedCache.GetCache();
+ allKeys = cache.Select(x => x.Key).ToArray();
+
+ sb.AppendFormat("\tTotal Poco data count:{0}\n", allKeys.Count());
+
+ var keys = string.Join("", cache.Select(x => x.Key));
+ //Bytes in .Net are stored as utf-16 = unicode little endian
+ totalBytes = Encoding.Unicode.GetByteCount(keys);
+
+ sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes);
+
+ sb.AppendLine("\tAll Poco cache items:");
+
+ foreach (var item in cache)
+ {
+ sb.AppendFormat("\t\t Key -> {0}\n", item.Key);
+ sb.AppendFormat("\t\t Value -> {0}\n", item.Value);
+ }
+
+ sb.AppendLine("-------------------END REPORT------------------------");
+ return sb.ToString();
+ }
}
diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs
index 4e1a3de28f..c03d2ca06f 100644
--- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs
+++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence
var expresionist = new PocoToSqlExpressionHelper();
string whereExpression = expresionist.Visit(predicate);
- return sql.Where(whereExpression);
+ return sql.Where(whereExpression, expresionist.GetSqlParameters());
}
public static Sql OrderBy(this Sql sql, Expression> columnMember)
diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs
index 57f076064b..36bcaa8c3a 100644
--- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs
+++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs
@@ -1,28 +1,562 @@
using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
+ internal abstract class BaseExpressionHelper : BaseExpressionHelper
+ {
+ protected abstract string VisitMemberAccess(MemberExpression m);
+
+ protected internal virtual string Visit(Expression exp)
+ {
+
+ if (exp == null) return string.Empty;
+ switch (exp.NodeType)
+ {
+ case ExpressionType.Lambda:
+ return VisitLambda(exp as LambdaExpression);
+ case ExpressionType.MemberAccess:
+ return VisitMemberAccess(exp as MemberExpression);
+ case ExpressionType.Constant:
+ return VisitConstant(exp as ConstantExpression);
+ case ExpressionType.Add:
+ case ExpressionType.AddChecked:
+ case ExpressionType.Subtract:
+ case ExpressionType.SubtractChecked:
+ case ExpressionType.Multiply:
+ case ExpressionType.MultiplyChecked:
+ case ExpressionType.Divide:
+ case ExpressionType.Modulo:
+ case ExpressionType.And:
+ case ExpressionType.AndAlso:
+ case ExpressionType.Or:
+ case ExpressionType.OrElse:
+ case ExpressionType.LessThan:
+ case ExpressionType.LessThanOrEqual:
+ case ExpressionType.GreaterThan:
+ case ExpressionType.GreaterThanOrEqual:
+ case ExpressionType.Equal:
+ case ExpressionType.NotEqual:
+ case ExpressionType.Coalesce:
+ case ExpressionType.ArrayIndex:
+ case ExpressionType.RightShift:
+ case ExpressionType.LeftShift:
+ case ExpressionType.ExclusiveOr:
+ return VisitBinary(exp as BinaryExpression);
+ case ExpressionType.Negate:
+ case ExpressionType.NegateChecked:
+ case ExpressionType.Not:
+ case ExpressionType.Convert:
+ case ExpressionType.ConvertChecked:
+ case ExpressionType.ArrayLength:
+ case ExpressionType.Quote:
+ case ExpressionType.TypeAs:
+ return VisitUnary(exp as UnaryExpression);
+ case ExpressionType.Parameter:
+ return VisitParameter(exp as ParameterExpression);
+ case ExpressionType.Call:
+ return VisitMethodCall(exp as MethodCallExpression);
+ case ExpressionType.New:
+ return VisitNew(exp as NewExpression);
+ case ExpressionType.NewArrayInit:
+ case ExpressionType.NewArrayBounds:
+ return VisitNewArray(exp as NewArrayExpression);
+ default:
+ return exp.ToString();
+ }
+ }
+
+ protected virtual string VisitLambda(LambdaExpression lambda)
+ {
+ if (lambda.Body.NodeType == ExpressionType.MemberAccess)
+ {
+ var m = lambda.Body as MemberExpression;
+
+ if (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());
+ }
+
+ }
+ return Visit(lambda.Body);
+ }
+
+ protected virtual string VisitBinary(BinaryExpression b)
+ {
+ string left, right;
+ var operand = BindOperant(b.NodeType);
+ if (operand == "AND" || operand == "OR")
+ {
+ MemberExpression m = b.Left as MemberExpression;
+ if (m != null && m.Expression != null)
+ {
+ string r = VisitMemberAccess(m);
+
+ SqlParameters.Add(1);
+ left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
+
+ //left = string.Format("{0}={1}", r, GetQuotedTrueValue());
+ }
+ else
+ {
+ left = Visit(b.Left);
+ }
+ m = b.Right as MemberExpression;
+ if (m != null && m.Expression != null)
+ {
+ string r = VisitMemberAccess(m);
+
+ SqlParameters.Add(1);
+ right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
+
+ //right = string.Format("{0}={1}", r, GetQuotedTrueValue());
+ }
+ else
+ {
+ right = Visit(b.Right);
+ }
+ }
+ else
+ {
+ left = Visit(b.Left);
+ right = Visit(b.Right);
+ }
+
+ if (operand == "=" && right == "null") operand = "is";
+ else if (operand == "<>" && right == "null") operand = "is not";
+ else if (operand == "=" || operand == "<>")
+ {
+ //if (IsTrueExpression(right)) right = GetQuotedTrueValue();
+ //else if (IsFalseExpression(right)) right = GetQuotedFalseValue();
+
+ //if (IsTrueExpression(left)) left = GetQuotedTrueValue();
+ //else if (IsFalseExpression(left)) left = GetQuotedFalseValue();
+
+ }
+
+ switch (operand)
+ {
+ case "MOD":
+ case "COALESCE":
+ return string.Format("{0}({1},{2})", operand, left, right);
+ default:
+ return left + " " + operand + " " + right;
+ }
+ }
+
+ protected virtual List VisitExpressionList(ReadOnlyCollection original)
+ {
+ var list = new List();
+ for (int i = 0, n = original.Count; i < n; i++)
+ {
+ if (original[i].NodeType == ExpressionType.NewArrayInit ||
+ original[i].NodeType == ExpressionType.NewArrayBounds)
+ {
+
+ list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression));
+ }
+ else
+ list.Add(Visit(original[i]));
+
+ }
+ return list;
+ }
+
+ protected virtual string VisitNew(NewExpression nex)
+ {
+ // TODO : check !
+ var member = Expression.Convert(nex, typeof(object));
+ var lambda = Expression.Lambda>(member);
+ try
+ {
+ var getter = lambda.Compile();
+ object o = getter();
+
+ SqlParameters.Add(o);
+ return string.Format("@{0}", SqlParameters.Count - 1);
+
+ //return GetQuotedValue(o, o.GetType());
+ }
+ catch (InvalidOperationException)
+ {
+ // FieldName ?
+ List exprs = VisitExpressionList(nex.Arguments);
+ var r = new StringBuilder();
+ foreach (Object e in exprs)
+ {
+ r.AppendFormat("{0}{1}",
+ r.Length > 0 ? "," : "",
+ e);
+ }
+ return r.ToString();
+ }
+
+ }
+
+ protected virtual string VisitParameter(ParameterExpression p)
+ {
+ return p.Name;
+ }
+
+ protected virtual string VisitConstant(ConstantExpression c)
+ {
+ if (c.Value == null)
+ 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());
+ }
+
+ protected virtual string VisitUnary(UnaryExpression u)
+ {
+ switch (u.NodeType)
+ {
+ case ExpressionType.Not:
+ var o = Visit(u.Operand);
+
+ //use a Not equal operator instead of <> since we don't know that <> works in all sql servers
+
+ switch (u.Operand.NodeType)
+ {
+ case ExpressionType.MemberAccess:
+ //In this case it wil be a false property , i.e. x => !Trashed
+ SqlParameters.Add(true);
+ return string.Format("NOT ({0} = @0)", o);
+ default:
+ //In this case it could be anything else, such as: x => !x.Path.StartsWith("-20")
+ return string.Format("NOT ({0})", o);
+ }
+ default:
+ return Visit(u.Operand);
+ }
+ }
+
+ protected virtual string VisitNewArray(NewArrayExpression na)
+ {
+
+ List exprs = VisitExpressionList(na.Expressions);
+ var r = new StringBuilder();
+ foreach (Object e in exprs)
+ {
+ r.Append(r.Length > 0 ? "," + e : e);
+ }
+
+ return r.ToString();
+ }
+
+ protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na)
+ {
+
+ List exprs = VisitExpressionList(na.Expressions);
+ return exprs;
+ }
+
+ protected virtual string BindOperant(ExpressionType e)
+ {
+
+ switch (e)
+ {
+ case ExpressionType.Equal:
+ return "=";
+ case ExpressionType.NotEqual:
+ return "<>";
+ case ExpressionType.GreaterThan:
+ return ">";
+ case ExpressionType.GreaterThanOrEqual:
+ return ">=";
+ case ExpressionType.LessThan:
+ return "<";
+ case ExpressionType.LessThanOrEqual:
+ return "<=";
+ case ExpressionType.AndAlso:
+ return "AND";
+ case ExpressionType.OrElse:
+ return "OR";
+ case ExpressionType.Add:
+ return "+";
+ case ExpressionType.Subtract:
+ return "-";
+ case ExpressionType.Multiply:
+ return "*";
+ case ExpressionType.Divide:
+ return "/";
+ case ExpressionType.Modulo:
+ return "MOD";
+ case ExpressionType.Coalesce:
+ return "COALESCE";
+ default:
+ return e.ToString();
+ }
+ }
+
+ protected virtual string VisitMethodCall(MethodCallExpression m)
+ {
+ //Here's what happens with a MethodCallExpression:
+ // If a method is called that contains a single argument,
+ // then m.Object is the object on the left hand side of the method call, example:
+ // x.Path.StartsWith(content.Path)
+ // m.Object = x.Path
+ // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path
+ // If a method is called that contains multiple arguments, then m.Object == null and the
+ // m.Arguments collection contains the left hand side of the method call, example:
+ // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar)
+ // m.Object == null
+ // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar
+ // So, we need to cater for these scenarios.
+
+ var objectForMethod = m.Object ?? m.Arguments[0];
+ var visitedObjectForMethod = Visit(objectForMethod);
+ 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);
+ case "ToUpper":
+ return string.Format("upper({0})", visitedObjectForMethod);
+ case "ToLower":
+ return string.Format("lower({0})", visitedObjectForMethod);
+ case "SqlWildcard":
+ case "StartsWith":
+ case "EndsWith":
+ case "Contains":
+ case "Equals":
+ case "SqlStartsWith":
+ case "SqlEndsWith":
+ case "SqlContains":
+ case "SqlEquals":
+ case "InvariantStartsWith":
+ case "InvariantEndsWith":
+ case "InvariantContains":
+ case "InvariantEquals":
+
+ string compareValue;
+
+ if (methodArgs[0].NodeType != ExpressionType.Constant)
+ {
+ //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path)
+ // So we'll go get the value:
+ var member = Expression.Convert(methodArgs[0], typeof(object));
+ var lambda = Expression.Lambda>(member);
+ var getter = lambda.Compile();
+ compareValue = getter().ToString();
+ }
+ else
+ {
+ compareValue = methodArgs[0].ToString();
+ }
+
+ //special case, if it is 'Contains' and the member that Contains is being called on is not a string, then
+ // we should be doing an 'In' clause - but we currently do not support this
+ if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type))
+ {
+ throw new NotSupportedException("An array Contains method is not supported");
+ }
+
+ //default column type
+ var colType = TextColumnType.NVarchar;
+
+ //then check if the col type argument has been passed to the current method (this will be the case for methods like
+ // SqlContains and other Sql methods)
+ if (methodArgs.Length > 1)
+ {
+ var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType));
+ if (colTypeArg != null)
+ {
+ colType = (TextColumnType)((ConstantExpression)colTypeArg).Value;
+ }
+ }
+
+ return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType);
+ //case "Substring":
+ // var startIndex = Int32.Parse(args[0].ToString()) + 1;
+ // if (args.Count == 2)
+ // {
+ // var length = Int32.Parse(args[1].ToString());
+ // return string.Format("substring({0} from {1} for {2})",
+ // r,
+ // startIndex,
+ // length);
+ // }
+ // else
+ // return string.Format("substring({0} from {1})",
+ // r,
+ // startIndex);
+ //case "Round":
+ //case "Floor":
+ //case "Ceiling":
+ //case "Coalesce":
+ //case "Abs":
+ //case "Sum":
+ // return string.Format("{0}({1}{2})",
+ // m.Method.Name,
+ // r,
+ // args.Count == 1 ? string.Format(",{0}", args[0]) : "");
+ //case "Concat":
+ // var s = new StringBuilder();
+ // foreach (Object e in args)
+ // {
+ // s.AppendFormat(" || {0}", e);
+ // }
+ // return string.Format("{0}{1}", r, s);
+
+ //case "In":
+
+ // var member = Expression.Convert(m.Arguments[0], typeof(object));
+ // var lambda = Expression.Lambda>(member);
+ // var getter = lambda.Compile();
+
+ // var inArgs = (object[])getter();
+
+ // var sIn = new StringBuilder();
+ // foreach (var e in inArgs)
+ // {
+ // SqlParameters.Add(e);
+
+ // sIn.AppendFormat("{0}{1}",
+ // sIn.Length > 0 ? "," : "",
+ // string.Format("@{0}", SqlParameters.Count - 1));
+
+ // //sIn.AppendFormat("{0}{1}",
+ // // sIn.Length > 0 ? "," : "",
+ // // GetQuotedValue(e, e.GetType()));
+ // }
+
+ // return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
+ //case "Desc":
+ // return string.Format("{0} DESC", r);
+ //case "Alias":
+ //case "As":
+ // return string.Format("{0} As {1}", r,
+ // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString()))));
+
+ default:
+
+ 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());
+ }
+ }
+
+ public virtual string GetQuotedTableName(string tableName)
+ {
+ return string.Format("\"{0}\"", tableName);
+ }
+
+ public virtual string GetQuotedColumnName(string columnName)
+ {
+ return string.Format("\"{0}\"", columnName);
+ }
+
+ public virtual string GetQuotedName(string 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());
+ //}
+ }
+
///
/// Logic that is shared with the expression helpers
///
- internal class BaseExpressionHelper
+ internal class BaseExpressionHelper
{
+ protected List SqlParameters = new List();
+
+ public object[] GetSqlParameters()
+ {
+ return SqlParameters.ToArray();
+ }
+
protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType)
{
switch (verb)
{
case "SqlWildcard":
- return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, RemoveQuote(val), columnType);
+ SqlParameters.Add(RemoveQuote(val));
+ return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "Equals":
- return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, RemoveQuote(val), columnType);
+ SqlParameters.Add(RemoveQuote(val));
+ return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
case "StartsWith":
- return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnStartsWithComparison(col, RemoveQuote(val), columnType);
+ SqlParameters.Add(string.Format("{0}{1}",
+ RemoveQuote(val),
+ SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder()));
+ return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "EndsWith":
- return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEndsWithComparison(col, RemoveQuote(val), columnType);
+ SqlParameters.Add(string.Format("{0}{1}",
+ SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
+ RemoveQuote(val)));
+ return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "Contains":
- return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnContainsComparison(col, RemoveQuote(val), columnType);
+ SqlParameters.Add(string.Format("{0}{1}{0}",
+ SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
+ RemoveQuote(val)));
+ return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "InvariantEquals":
case "SqlEquals":
//recurse
@@ -44,54 +578,54 @@ namespace Umbraco.Core.Persistence.Querying
}
}
- public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null)
- {
- if (value == null) return "NULL";
+ //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null)
+ //{
+ // if (value == null) return "NULL";
- if (escapeCallback == null)
- {
- escapeCallback = EscapeParam;
- }
- if (shouldQuoteCallback == null)
- {
- shouldQuoteCallback = ShouldQuoteValue;
- }
+ // if (escapeCallback == null)
+ // {
+ // escapeCallback = EscapeParam;
+ // }
+ // if (shouldQuoteCallback == null)
+ // {
+ // shouldQuoteCallback = ShouldQuoteValue;
+ // }
- if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string))
- {
- //if (TypeSerializer.CanCreateFromString(fieldType))
- //{
- // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'";
- //}
+ // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string))
+ // {
+ // //if (TypeSerializer.CanCreateFromString(fieldType))
+ // //{
+ // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'";
+ // //}
- throw new NotSupportedException(
- string.Format("Property of type: {0} is not supported", fieldType.FullName));
- }
+ // throw new NotSupportedException(
+ // string.Format("Property of type: {0} is not supported", fieldType.FullName));
+ // }
- if (fieldType == typeof(int))
- return ((int)value).ToString(CultureInfo.InvariantCulture);
+ // if (fieldType == typeof(int))
+ // return ((int)value).ToString(CultureInfo.InvariantCulture);
- if (fieldType == typeof(float))
- return ((float)value).ToString(CultureInfo.InvariantCulture);
+ // if (fieldType == typeof(float))
+ // return ((float)value).ToString(CultureInfo.InvariantCulture);
- if (fieldType == typeof(double))
- return ((double)value).ToString(CultureInfo.InvariantCulture);
+ // if (fieldType == typeof(double))
+ // return ((double)value).ToString(CultureInfo.InvariantCulture);
- if (fieldType == typeof(decimal))
- return ((decimal)value).ToString(CultureInfo.InvariantCulture);
+ // if (fieldType == typeof(decimal))
+ // return ((decimal)value).ToString(CultureInfo.InvariantCulture);
- if (fieldType == typeof(DateTime))
- {
- return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'";
- }
+ // if (fieldType == typeof(DateTime))
+ // {
+ // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'";
+ // }
- if (fieldType == typeof(bool))
- return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture);
+ // if (fieldType == typeof(bool))
+ // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture);
- return shouldQuoteCallback(fieldType)
- ? "'" + escapeCallback(value) + "'"
- : value.ToString();
- }
+ // return shouldQuoteCallback(fieldType)
+ // ? "'" + escapeCallback(value) + "'"
+ // : value.ToString();
+ //}
public virtual string EscapeParam(object paramValue)
{
@@ -107,16 +641,12 @@ 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;
- }
-
- protected virtual string RemoveQuoteFromAlias(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("'"))
&&
@@ -127,5 +657,18 @@ namespace Umbraco.Core.Persistence.Querying
}
return exp;
}
+
+ //protected virtual string RemoveQuoteFromAlias(string exp)
+ //{
+
+ // if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'"))
+ // &&
+ // (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'")))
+ // {
+ // exp = exp.Remove(0, 1);
+ // exp = exp.Remove(exp.Length - 1, 1);
+ // }
+ // return exp;
+ //}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs
index 1d634b9d90..b484465540 100644
--- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs
+++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs
@@ -1,10 +1,26 @@
using System;
+using System.Collections.Generic;
using System.Linq.Expressions;
namespace Umbraco.Core.Persistence.Querying
{
+ ///
+ /// Represents a query for building Linq translatable SQL queries
+ ///
+ ///
public interface IQuery
{
+ ///
+ /// Adds a where clause to the query
+ ///
+ ///
+ /// This instance so calls to this method are chainable
IQuery Where(Expression> predicate);
+
+ ///
+ /// Returns all translated where clauses and their sql parameters
+ ///
+ ///
+ IEnumerable> GetWhereClauses();
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs
index e3ff272cee..1a561b7bf4 100644
--- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs
+++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs
@@ -10,149 +10,21 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
- internal class ModelToSqlExpressionHelper : BaseExpressionHelper
+ internal class ModelToSqlExpressionHelper : BaseExpressionHelper
{
- private string sep = " ";
- private BaseMapper _mapper;
+
+ private readonly BaseMapper _mapper;
public ModelToSqlExpressionHelper()
{
_mapper = MappingResolver.Current.ResolveMapperByType(typeof(T));
}
-
- protected internal virtual string Visit(Expression exp)
+
+ protected override string VisitMemberAccess(MemberExpression m)
{
-
- if (exp == null) return string.Empty;
- switch (exp.NodeType)
- {
- case ExpressionType.Lambda:
- return VisitLambda(exp as LambdaExpression);
- case ExpressionType.MemberAccess:
- return VisitMemberAccess(exp as MemberExpression);
- case ExpressionType.Constant:
- return VisitConstant(exp as ConstantExpression);
- case ExpressionType.Add:
- case ExpressionType.AddChecked:
- case ExpressionType.Subtract:
- case ExpressionType.SubtractChecked:
- case ExpressionType.Multiply:
- case ExpressionType.MultiplyChecked:
- case ExpressionType.Divide:
- case ExpressionType.Modulo:
- case ExpressionType.And:
- case ExpressionType.AndAlso:
- case ExpressionType.Or:
- case ExpressionType.OrElse:
- case ExpressionType.LessThan:
- case ExpressionType.LessThanOrEqual:
- case ExpressionType.GreaterThan:
- case ExpressionType.GreaterThanOrEqual:
- case ExpressionType.Equal:
- case ExpressionType.NotEqual:
- case ExpressionType.Coalesce:
- case ExpressionType.ArrayIndex:
- case ExpressionType.RightShift:
- case ExpressionType.LeftShift:
- case ExpressionType.ExclusiveOr:
- return VisitBinary(exp as BinaryExpression);
- case ExpressionType.Negate:
- case ExpressionType.NegateChecked:
- case ExpressionType.Not:
- case ExpressionType.Convert:
- case ExpressionType.ConvertChecked:
- case ExpressionType.ArrayLength:
- case ExpressionType.Quote:
- case ExpressionType.TypeAs:
- return VisitUnary(exp as UnaryExpression);
- case ExpressionType.Parameter:
- return VisitParameter(exp as ParameterExpression);
- case ExpressionType.Call:
- return VisitMethodCall(exp as MethodCallExpression);
- case ExpressionType.New:
- return VisitNew(exp as NewExpression);
- case ExpressionType.NewArrayInit:
- case ExpressionType.NewArrayBounds:
- return VisitNewArray(exp as NewArrayExpression);
- default:
- return exp.ToString();
- }
- }
-
- protected virtual string VisitLambda(LambdaExpression lambda)
- {
- if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ")
- {
- MemberExpression m = lambda.Body as MemberExpression;
-
- if (m.Expression != null)
- {
- string r = VisitMemberAccess(m);
- return string.Format("{0}={1}", r, GetQuotedTrueValue());
- }
-
- }
- return Visit(lambda.Body);
- }
-
- protected virtual string VisitBinary(BinaryExpression b)
- {
- string left, right;
- var operand = BindOperant(b.NodeType); //sep= " " ??
- if (operand == "AND" || operand == "OR")
- {
- MemberExpression m = b.Left as MemberExpression;
- if (m != null && m.Expression != null)
- {
- string r = VisitMemberAccess(m);
- left = string.Format("{0}={1}", r, GetQuotedTrueValue());
- }
- else
- {
- left = Visit(b.Left);
- }
- m = b.Right as MemberExpression;
- if (m != null && m.Expression != null)
- {
- string r = VisitMemberAccess(m);
- right = string.Format("{0}={1}", r, GetQuotedTrueValue());
- }
- else
- {
- right = Visit(b.Right);
- }
- }
- else
- {
- left = Visit(b.Left);
- right = Visit(b.Right);
- }
-
- if (operand == "=" && right == "null") operand = "is";
- else if (operand == "<>" && right == "null") operand = "is not";
- else if (operand == "=" || operand == "<>")
- {
- if (IsTrueExpression(right)) right = GetQuotedTrueValue();
- else if (IsFalseExpression(right)) right = GetQuotedFalseValue();
-
- if (IsTrueExpression(left)) left = GetQuotedTrueValue();
- else if (IsFalseExpression(left)) left = GetQuotedFalseValue();
-
- }
-
- switch (operand)
- {
- case "MOD":
- case "COALESCE":
- return string.Format("{0}({1},{2})", operand, left, right);
- default:
- return left + sep + operand + sep + right;
- }
- }
-
- protected virtual string VisitMemberAccess(MemberExpression m)
- {
- if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(T))
+ if (m.Expression != null &&
+ m.Expression.NodeType == ExpressionType.Parameter
+ && m.Expression.Type == typeof(T))
{
var field = _mapper.Map(m.Member.Name);
return field;
@@ -168,324 +40,18 @@ namespace Umbraco.Core.Persistence.Querying
var lambda = Expression.Lambda>(member);
var getter = lambda.Compile();
object o = getter();
- return GetQuotedValue(o, o != null ? o.GetType() : null);
+
+ SqlParameters.Add(o);
+ return string.Format("@{0}", SqlParameters.Count - 1);
+
+ //return GetQuotedValue(o, o != null ? o.GetType() : null);
}
-
- protected virtual string VisitNew(NewExpression nex)
- {
- // TODO : check !
- var member = Expression.Convert(nex, typeof(object));
- var lambda = Expression.Lambda>(member);
- try
- {
- var getter = lambda.Compile();
- object o = getter();
- return GetQuotedValue(o, o.GetType());
- }
- catch (System.InvalidOperationException)
- { // FieldName ?
- List exprs = VisitExpressionList(nex.Arguments);
- var r = new StringBuilder();
- foreach (Object e in exprs)
- {
- r.AppendFormat("{0}{1}", r.Length > 0 ? "," : "", e);
- }
- return r.ToString();
- }
-
- }
-
- protected virtual string VisitParameter(ParameterExpression p)
- {
- return p.Name;
- }
-
- protected virtual string VisitConstant(ConstantExpression c)
- {
- if (c.Value == null)
- return "null";
- 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());
- }
-
- protected virtual string VisitUnary(UnaryExpression u)
- {
- switch (u.NodeType)
- {
- case ExpressionType.Not:
- string o = Visit(u.Operand);
- if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool));
- return "NOT (" + o + ")";
- default:
- return Visit(u.Operand);
- }
-
- }
-
- protected virtual string VisitMethodCall(MethodCallExpression m)
- {
- List args = this.VisitExpressionList(m.Arguments);
-
- Object r;
- if (m.Object != null)
- r = Visit(m.Object);
- else
- {
- r = args[0];
- args.RemoveAt(0);
- }
-
- switch (m.Method.Name)
- {
- case "ToUpper":
- return string.Format("upper({0})", r);
- case "ToLower":
- return string.Format("lower({0})", r);
- case "SqlWildcard":
- case "StartsWith":
- case "EndsWith":
- case "Contains":
- case "Equals":
- case "SqlStartsWith":
- case "SqlEndsWith":
- case "SqlContains":
- case "SqlEquals":
- case "InvariantStartsWith":
- case "InvariantEndsWith":
- case "InvariantContains":
- case "InvariantEquals":
- //default
- var colType = TextColumnType.NVarchar;
- //then check if this arg has been passed in
- if (m.Arguments.Count > 1)
- {
- var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType));
- if (colTypeArg != null)
- {
- colType = (TextColumnType) ((ConstantExpression) colTypeArg).Value;
- }
- }
- return HandleStringComparison(r.ToString(), args[0].ToString(), m.Method.Name, colType);
- case "Substring":
- var startIndex = Int32.Parse(args[0].ToString()) + 1;
- if (args.Count == 2)
- {
- var length = Int32.Parse(args[1].ToString());
- return string.Format("substring({0} from {1} for {2})",
- r,
- startIndex,
- length);
- }
- else
- return string.Format("substring({0} from {1})",
- r,
- startIndex);
- case "Round":
- case "Floor":
- case "Ceiling":
- case "Coalesce":
- case "Abs":
- case "Sum":
- return string.Format("{0}({1}{2})",
- m.Method.Name,
- r,
- args.Count == 1 ? string.Format(",{0}", args[0]) : "");
- case "Concat":
- var s = new StringBuilder();
- foreach (Object e in args)
- {
- s.AppendFormat(" || {0}", e);
- }
- return string.Format("{0}{1}", r, s.ToString());
-
- case "In":
-
- var member = Expression.Convert(m.Arguments[1], typeof(object));
- var lambda = Expression.Lambda>(member);
- var getter = lambda.Compile();
-
- var inArgs = getter() as object[];
-
- var sIn = new StringBuilder();
- foreach (Object e in inArgs)
- {
- if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]")
- {
- sIn.AppendFormat("{0}{1}",
- sIn.Length > 0 ? "," : "",
- GetQuotedValue(e, e.GetType()));
- }
- else
- {
- var listArgs = e as IList;
- foreach (Object el in listArgs)
- {
- sIn.AppendFormat("{0}{1}",
- sIn.Length > 0 ? "," : "",
- GetQuotedValue(el, el.GetType()));
- }
- }
- }
-
- return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
- case "Desc":
- return string.Format("{0} DESC", r);
- case "Alias":
- case "As":
- return string.Format("{0} As {1}", r,
- GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString()))));
- case "ToString":
- return r.ToString();
- default:
- 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());
- }
- }
-
- protected virtual List VisitExpressionList(ReadOnlyCollection original)
- {
- var list = new List();
- for (int i = 0, n = original.Count; i < n; i++)
- {
- if (original[i].NodeType == ExpressionType.NewArrayInit ||
- original[i].NodeType == ExpressionType.NewArrayBounds)
- {
-
- list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression));
- }
- else
- list.Add(Visit(original[i]));
-
- }
- return list;
- }
-
- protected virtual string VisitNewArray(NewArrayExpression na)
- {
-
- List exprs = VisitExpressionList(na.Expressions);
- var r = new StringBuilder();
- foreach (Object e in exprs)
- {
- r.Append(r.Length > 0 ? "," + e : e);
- }
-
- return r.ToString();
- }
-
- protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na)
- {
-
- List exprs = VisitExpressionList(na.Expressions);
- return exprs;
- }
-
-
- protected virtual string BindOperant(ExpressionType e)
- {
-
- switch (e)
- {
- case ExpressionType.Equal:
- return "=";
- case ExpressionType.NotEqual:
- return "<>";
- case ExpressionType.GreaterThan:
- return ">";
- case ExpressionType.GreaterThanOrEqual:
- return ">=";
- case ExpressionType.LessThan:
- return "<";
- case ExpressionType.LessThanOrEqual:
- return "<=";
- case ExpressionType.AndAlso:
- return "AND";
- case ExpressionType.OrElse:
- return "OR";
- case ExpressionType.Add:
- return "+";
- case ExpressionType.Subtract:
- return "-";
- case ExpressionType.Multiply:
- return "*";
- case ExpressionType.Divide:
- return "/";
- case ExpressionType.Modulo:
- return "MOD";
- case ExpressionType.Coalesce:
- return "COALESCE";
- default:
- return e.ToString();
- }
- }
-
- public virtual string GetQuotedTableName(string tableName)
- {
- return string.Format("\"{0}\"", tableName);
- }
-
- public virtual string GetQuotedColumnName(string columnName)
- {
- return string.Format("\"{0}\"", columnName);
- }
-
- public virtual string GetQuotedName(string 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());
- }
-
- 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;
- }
+
+ //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;
+ //}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs
index 9a27cc8183..bbdb7a5509 100644
--- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs
+++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -8,159 +9,28 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
- internal class PocoToSqlExpressionHelper : BaseExpressionHelper
+ internal class PocoToSqlExpressionHelper : BaseExpressionHelper
{
- private string sep = " ";
- private Database.PocoData pd;
+ private readonly Database.PocoData _pd;
public PocoToSqlExpressionHelper()
{
- pd = new Database.PocoData(typeof(T));
+ _pd = new Database.PocoData(typeof(T));
}
-
- protected internal virtual string Visit(Expression exp)
- {
-
- if (exp == null) return string.Empty;
- switch (exp.NodeType)
- {
- case ExpressionType.Lambda:
- return VisitLambda(exp as LambdaExpression);
- case ExpressionType.MemberAccess:
- return VisitMemberAccess(exp as MemberExpression);
- case ExpressionType.Constant:
- return VisitConstant(exp as ConstantExpression);
- case ExpressionType.Add:
- case ExpressionType.AddChecked:
- case ExpressionType.Subtract:
- case ExpressionType.SubtractChecked:
- case ExpressionType.Multiply:
- case ExpressionType.MultiplyChecked:
- case ExpressionType.Divide:
- case ExpressionType.Modulo:
- case ExpressionType.And:
- case ExpressionType.AndAlso:
- case ExpressionType.Or:
- case ExpressionType.OrElse:
- case ExpressionType.LessThan:
- case ExpressionType.LessThanOrEqual:
- case ExpressionType.GreaterThan:
- case ExpressionType.GreaterThanOrEqual:
- case ExpressionType.Equal:
- case ExpressionType.NotEqual:
- case ExpressionType.Coalesce:
- case ExpressionType.ArrayIndex:
- case ExpressionType.RightShift:
- case ExpressionType.LeftShift:
- case ExpressionType.ExclusiveOr:
- return VisitBinary(exp as BinaryExpression);
- case ExpressionType.Negate:
- case ExpressionType.NegateChecked:
- case ExpressionType.Not:
- case ExpressionType.Convert:
- case ExpressionType.ConvertChecked:
- case ExpressionType.ArrayLength:
- case ExpressionType.Quote:
- case ExpressionType.TypeAs:
- return VisitUnary(exp as UnaryExpression);
- case ExpressionType.Parameter:
- return VisitParameter(exp as ParameterExpression);
- case ExpressionType.Call:
- return VisitMethodCall(exp as MethodCallExpression);
- case ExpressionType.New:
- return VisitNew(exp as NewExpression);
- case ExpressionType.NewArrayInit:
- case ExpressionType.NewArrayBounds:
- return VisitNewArray(exp as NewArrayExpression);
- default:
- return exp.ToString();
- }
- }
-
- protected virtual string VisitLambda(LambdaExpression lambda)
- {
- if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ")
- {
- MemberExpression m = lambda.Body as MemberExpression;
-
- if (m.Expression != null)
- {
- string r = VisitMemberAccess(m);
- return string.Format("{0}={1}", r, GetQuotedTrueValue());
- }
-
- }
- return Visit(lambda.Body);
- }
-
- protected virtual string VisitBinary(BinaryExpression b)
- {
- string left, right;
- var operand = BindOperant(b.NodeType); //sep= " " ??
- if (operand == "AND" || operand == "OR")
- {
- MemberExpression m = b.Left as MemberExpression;
- if (m != null && m.Expression != null)
- {
- string r = VisitMemberAccess(m);
- left = string.Format("{0}={1}", r, GetQuotedTrueValue());
- }
- else
- {
- left = Visit(b.Left);
- }
- m = b.Right as MemberExpression;
- if (m != null && m.Expression != null)
- {
- string r = VisitMemberAccess(m);
- right = string.Format("{0}={1}", r, GetQuotedTrueValue());
- }
- else
- {
- right = Visit(b.Right);
- }
- }
- else
- {
- left = Visit(b.Left);
- right = Visit(b.Right);
- }
-
- if (operand == "=" && right == "null") operand = "is";
- else if (operand == "<>" && right == "null") operand = "is not";
- else if (operand == "=" || operand == "<>")
- {
- if (IsTrueExpression(right)) right = GetQuotedTrueValue();
- else if (IsFalseExpression(right)) right = GetQuotedFalseValue();
-
- if (IsTrueExpression(left)) left = GetQuotedTrueValue();
- else if (IsFalseExpression(left)) left = GetQuotedFalseValue();
-
- }
-
- switch (operand)
- {
- case "MOD":
- case "COALESCE":
- return string.Format("{0}({1},{2})", operand, left, right);
- default:
- return left + sep + operand + sep + right;
- }
- }
-
- protected virtual string VisitMemberAccess(MemberExpression m)
+
+ protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
- string field = GetFieldName(pd, m.Member.Name);
+ string field = GetFieldName(_pd, m.Member.Name);
return field;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
- string field = GetFieldName(pd, m.Member.Name);
+ string field = GetFieldName(_pd, m.Member.Name);
return field;
}
@@ -168,303 +38,14 @@ namespace Umbraco.Core.Persistence.Querying
var lambda = Expression.Lambda>(member);
var getter = lambda.Compile();
object o = getter();
- return GetQuotedValue(o, o != null ? o.GetType() : null);
+
+ SqlParameters.Add(o);
+ return string.Format("@{0}", SqlParameters.Count - 1);
+
+ //return GetQuotedValue(o, o != null ? o.GetType() : null);
}
-
- protected virtual string VisitNew(NewExpression nex)
- {
- // TODO : check !
- var member = Expression.Convert(nex, typeof(object));
- var lambda = Expression.Lambda>(member);
- try
- {
- var getter = lambda.Compile();
- object o = getter();
- return GetQuotedValue(o, o.GetType());
- }
- catch (System.InvalidOperationException)
- { // FieldName ?
- List exprs = VisitExpressionList(nex.Arguments);
- var r = new StringBuilder();
- foreach (Object e in exprs)
- {
- r.AppendFormat("{0}{1}",
- r.Length > 0 ? "," : "",
- e);
- }
- return r.ToString();
- }
-
- }
-
- protected virtual string VisitParameter(ParameterExpression p)
- {
- return p.Name;
- }
-
- protected virtual string VisitConstant(ConstantExpression c)
- {
- if (c.Value == null)
- return "null";
- else if (c.Value.GetType() == typeof(bool))
- {
- object o = GetQuotedValue(c.Value, c.Value.GetType());
- return string.Format("({0}={1})", GetQuotedTrueValue(), o);
- }
- else
- return GetQuotedValue(c.Value, c.Value.GetType());
- }
-
- protected virtual string VisitUnary(UnaryExpression u)
- {
- switch (u.NodeType)
- {
- case ExpressionType.Not:
- string o = Visit(u.Operand);
- if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool));
- return "NOT (" + o + ")";
- default:
- return Visit(u.Operand);
- }
-
- }
-
- protected virtual string VisitMethodCall(MethodCallExpression m)
- {
- List args = this.VisitExpressionList(m.Arguments);
-
- Object r;
- if (m.Object != null)
- r = Visit(m.Object);
- else
- {
- r = args[0];
- args.RemoveAt(0);
- }
-
- //TODO: We should probably add the same logic we've done for ModelToSqlExpressionHelper with checking for:
- // InvariantStartsWith, InvariantEndsWith, SqlWildcard, etc...
- // since we should be able to easily handle that with the Poco objects too.
-
- switch (m.Method.Name)
- {
- case "ToUpper":
- return string.Format("upper({0})", r);
- case "ToLower":
- return string.Format("lower({0})", r);
- case "SqlWildcard":
- case "StartsWith":
- case "EndsWith":
- case "Contains":
- case "Equals":
- case "SqlStartsWith":
- case "SqlEndsWith":
- case "SqlContains":
- case "SqlEquals":
- case "InvariantStartsWith":
- case "InvariantEndsWith":
- case "InvariantContains":
- case "InvariantEquals":
- //default
- var colType = TextColumnType.NVarchar;
- //then check if this arg has been passed in
- if (m.Arguments.Count > 1)
- {
- var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType));
- if (colTypeArg != null)
- {
- colType = (TextColumnType)((ConstantExpression)colTypeArg).Value;
- }
- }
- return HandleStringComparison(r.ToString(), args[0].ToString(), m.Method.Name, colType);
- case "Substring":
- var startIndex = Int32.Parse(args[0].ToString()) + 1;
- if (args.Count == 2)
- {
- var length = Int32.Parse(args[1].ToString());
- return string.Format("substring({0} from {1} for {2})",
- r,
- startIndex,
- length);
- }
- else
- return string.Format("substring({0} from {1})",
- r,
- startIndex);
- case "Round":
- case "Floor":
- case "Ceiling":
- case "Coalesce":
- case "Abs":
- case "Sum":
- return string.Format("{0}({1}{2})",
- m.Method.Name,
- r,
- args.Count == 1 ? string.Format(",{0}", args[0]) : "");
- case "Concat":
- var s = new StringBuilder();
- foreach (Object e in args)
- {
- s.AppendFormat(" || {0}", e);
- }
- return string.Format("{0}{1}", r, s.ToString());
-
- case "In":
-
- var member = Expression.Convert(m.Arguments[1], typeof(object));
- var lambda = Expression.Lambda>(member);
- var getter = lambda.Compile();
-
- var inArgs = getter() as object[];
-
- var sIn = new StringBuilder();
- foreach (Object e in inArgs)
- {
- if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]")
- {
- sIn.AppendFormat("{0}{1}",
- sIn.Length > 0 ? "," : "",
- GetQuotedValue(e, e.GetType()));
- }
- else
- {
- var listArgs = e as IList;
- foreach (Object el in listArgs)
- {
- sIn.AppendFormat("{0}{1}",
- sIn.Length > 0 ? "," : "",
- GetQuotedValue(el, el.GetType()));
- }
- }
- }
-
- return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
- case "Desc":
- return string.Format("{0} DESC", r);
- case "Alias":
- case "As":
- return string.Format("{0} As {1}", r,
- GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString()))));
- case "ToString":
- return r.ToString();
- default:
- 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());
- }
- }
-
- protected virtual List VisitExpressionList(ReadOnlyCollection original)
- {
- var list = new List();
- for (int i = 0, n = original.Count; i < n; i++)
- {
- if (original[i].NodeType == ExpressionType.NewArrayInit ||
- original[i].NodeType == ExpressionType.NewArrayBounds)
- {
-
- list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression));
- }
- else
- list.Add(Visit(original[i]));
-
- }
- return list;
- }
-
- protected virtual string VisitNewArray(NewArrayExpression na)
- {
-
- List exprs = VisitExpressionList(na.Expressions);
- var r = new StringBuilder();
- foreach (Object e in exprs)
- {
- r.Append(r.Length > 0 ? "," + e : e);
- }
-
- return r.ToString();
- }
-
- protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na)
- {
-
- List exprs = VisitExpressionList(na.Expressions);
- return exprs;
- }
-
-
- protected virtual string BindOperant(ExpressionType e)
- {
-
- switch (e)
- {
- case ExpressionType.Equal:
- return "=";
- case ExpressionType.NotEqual:
- return "<>";
- case ExpressionType.GreaterThan:
- return ">";
- case ExpressionType.GreaterThanOrEqual:
- return ">=";
- case ExpressionType.LessThan:
- return "<";
- case ExpressionType.LessThanOrEqual:
- return "<=";
- case ExpressionType.AndAlso:
- return "AND";
- case ExpressionType.OrElse:
- return "OR";
- case ExpressionType.Add:
- return "+";
- case ExpressionType.Subtract:
- return "-";
- case ExpressionType.Multiply:
- return "*";
- case ExpressionType.Divide:
- return "/";
- case ExpressionType.Modulo:
- return "MOD";
- case ExpressionType.Coalesce:
- return "COALESCE";
- default:
- return e.ToString();
- }
- }
-
- public virtual string GetQuotedTableName(string tableName)
- {
- return string.Format("\"{0}\"", tableName);
- }
-
- public virtual string GetQuotedColumnName(string columnName)
- {
- return string.Format("\"{0}\"", columnName);
- }
-
- public virtual string GetQuotedName(string 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);
- }
-
+
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
@@ -472,34 +53,10 @@ namespace Umbraco.Core.Persistence.Querying
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName));
}
-
- 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());
- }
-
- protected bool IsFieldName(string quotedExp)
- {
- return true;
- }
+
+ //protected bool IsFieldName(string quotedExp)
+ //{
+ // return true;
+ //}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs
index b426619dcf..4dd268268f 100644
--- a/src/Umbraco.Core/Persistence/Querying/Query.cs
+++ b/src/Umbraco.Core/Persistence/Querying/Query.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Linq.Expressions;
namespace Umbraco.Core.Persistence.Querying
@@ -10,37 +11,46 @@ namespace Umbraco.Core.Persistence.Querying
///
public class Query : IQuery
{
- //private readonly ExpressionHelper _expresionist = new ExpressionHelper();
- private readonly ModelToSqlExpressionHelper _expresionist = new ModelToSqlExpressionHelper();
- private readonly List _wheres = new List();
-
- public Query()
- : base()
- {
-
- }
+ private readonly List> _wheres = new List>();
+ ///
+ /// Helper method to be used instead of manually creating an instance
+ ///
public static IQuery Builder
{
- get
- {
- return new Query();
- }
+ get { return new Query(); }
}
+ ///
+ /// Adds a where clause to the query
+ ///
+ ///
+ /// This instance so calls to this method are chainable
public virtual IQuery Where(Expression> predicate)
{
if (predicate != null)
{
- string whereExpression = _expresionist.Visit(predicate);
- _wheres.Add(whereExpression);
+ var expressionHelper = new ModelToSqlExpressionHelper();
+ string whereExpression = expressionHelper.Visit(predicate);
+
+ _wheres.Add(new Tuple(whereExpression, expressionHelper.GetSqlParameters()));
}
return this;
}
-
- public List WhereClauses()
+
+ ///
+ /// Returns all translated where clauses and their sql parameters
+ ///
+ ///
+ public IEnumerable> GetWhereClauses()
{
return _wheres;
}
+
+ [Obsolete("This is no longer used, use the GetWhereClauses method which includes the SQL parameters")]
+ public List WhereClauses()
+ {
+ return _wheres.Select(x => x.Item1).ToList();
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs
index ef5e14a019..b012293340 100644
--- a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs
+++ b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs
@@ -15,14 +15,10 @@ namespace Umbraco.Core.Persistence.Querying
if (sql == null)
throw new Exception("Sql cannot be null");
- var query1 = query as Query;
- if (query1 == null)
- throw new Exception("Query cannot be null");
-
_sql = sql;
- foreach (var clause in query1.WhereClauses())
+ foreach (var clause in query.GetWhereClauses())
{
- _sql.Where(clause);
+ _sql.Where(clause.Item1, clause.Item2);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
index c935d1626c..a96d8b3bbf 100644
--- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
@@ -66,15 +66,31 @@ namespace Umbraco.Core.Persistence.Repositories
{
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
- var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key).Append(GetGroupBy(isContent, isMedia));
- var nodeDto = _work.Database.FirstOrDefault(sql);
- if (nodeDto == null)
- return null;
- var factory = new UmbracoEntityFactory();
- var entity = factory.BuildEntityFromDynamic(nodeDto);
+ var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId);
+
+ if (isMedia)
+ {
+ //for now treat media differently
+ //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
+ var entities = _work.Database.Fetch(
+ new UmbracoEntityRelator().Map, sql);
- return entity;
+ return entities.FirstOrDefault();
+ }
+ else
+ {
+ var nodeDto = _work.Database.FirstOrDefault(sql);
+ if (nodeDto == null)
+ return null;
+
+ var factory = new UmbracoEntityFactory();
+ var entity = factory.BuildEntityFromDynamic(nodeDto);
+
+ return entity;
+ }
+
+
}
public virtual IUmbracoEntity Get(int id)
@@ -94,61 +110,101 @@ namespace Umbraco.Core.Persistence.Repositories
{
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
- var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id).Append(GetGroupBy(isContent, isMedia));
- var nodeDto = _work.Database.FirstOrDefault(sql);
- if (nodeDto == null)
- return null;
+ var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId);
+
+ if (isMedia)
+ {
+ //for now treat media differently
+ //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
+ var entities = _work.Database.Fetch(
+ new UmbracoEntityRelator().Map, sql);
- var factory = new UmbracoEntityFactory();
- var entity = factory.BuildEntityFromDynamic(nodeDto);
+ return entities.FirstOrDefault();
+ }
+ else
+ {
+ var nodeDto = _work.Database.FirstOrDefault(sql);
+ if (nodeDto == null)
+ return null;
- return entity;
+ var factory = new UmbracoEntityFactory();
+ var entity = factory.BuildEntityFromDynamic(nodeDto);
+
+ return entity;
+ }
+
+
}
public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids)
{
if (ids.Any())
{
- foreach (var id in ids)
+ return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.id in (@ids)", new {ids = ids}));
+ }
+ else
+ {
+ return PerformGetAll(objectTypeId);
+ }
+ }
+
+ public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys)
+ {
+ if (keys.Any())
+ {
+ return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.uniqueID in (@keys)", new { keys = keys }));
+ }
+ else
+ {
+ return PerformGetAll(objectTypeId);
+ }
+ }
+
+ private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null)
+ {
+ bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
+ bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
+ var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter);
+
+ var factory = new UmbracoEntityFactory();
+
+ if (isMedia)
+ {
+ //for now treat media differently
+ //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
+ var entities = _work.Database.Fetch(
+ new UmbracoEntityRelator().Map, sql);
+ foreach (var entity in entities)
{
- yield return Get(id, objectTypeId);
+ yield return entity;
}
}
else
{
- bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
- bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
- var sql = GetBaseWhere(GetBase, isContent, isMedia, string.Empty, objectTypeId).Append(GetGroupBy(isContent, isMedia));
-
- var factory = new UmbracoEntityFactory();
-
- if (isMedia)
+ var dtos = _work.Database.Fetch(sql);
+ foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto)))
{
- //for now treat media differently
- //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
- var entities = _work.Database.Fetch(
- new UmbracoEntityRelator().Map, sql);
- foreach (var entity in entities)
- {
- yield return entity;
- }
- }
- else
- {
- var dtos = _work.Database.Fetch(sql);
- foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto)))
- {
- yield return entity;
- }
+ yield return entity;
}
}
}
+
public virtual IEnumerable GetByQuery(IQuery query)
{
- var wheres = string.Concat(" AND ", string.Join(" AND ", ((Query) query).WhereClauses()));
- var sqlClause = GetBase(false, false, wheres);
+ //TODO: We need to fix all of this and how it handles parameters!
+
+ var wheres = query.GetWhereClauses().ToArray();
+
+ var sqlClause = GetBase(false, false, sql1 =>
+ {
+ //adds the additional filters
+ foreach (var whereClause in wheres)
+ {
+ sql1.Where(whereClause.Item1, whereClause.Item2);
+ }
+ });
var translator = new SqlTranslator(sqlClause, query);
var sql = translator.Translate().Append(GetGroupBy(false, false));
@@ -162,28 +218,48 @@ namespace Umbraco.Core.Persistence.Repositories
public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId)
{
+
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
- var wheres = string.Concat(" AND ", string.Join(" AND ", ((Query)query).WhereClauses()));
- var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, wheres, objectTypeId);
+ var wheres = query.GetWhereClauses().ToArray();
+
+ var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql1 =>
+ {
+ //adds the additional filters
+ foreach (var whereClause in wheres)
+ {
+ sql1.Where(whereClause.Item1, whereClause.Item2);
+ }
+
+ }, objectTypeId);
+
var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate().Append(GetGroupBy(isContent, isMedia));
+ var entitySql = translator.Translate();
var factory = new UmbracoEntityFactory();
if (isMedia)
{
+ var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), sql =>
+ {
+ //adds the additional filters
+ foreach (var whereClause in wheres)
+ {
+ sql.Where(whereClause.Item1, whereClause.Item2);
+ }
+ });
+
//treat media differently for now
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
var entities = _work.Database.Fetch(
- new UmbracoEntityRelator().Map, sql);
+ new UmbracoEntityRelator().Map, mediaSql);
return entities;
}
else
{
//use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData
- var dtos = _work.Database.Fetch(sql);
+ var dtos = _work.Database.Fetch(entitySql.Append(GetGroupBy(isContent, false)));
return dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList();
}
}
@@ -193,7 +269,68 @@ namespace Umbraco.Core.Persistence.Repositories
#region Sql Statements
- protected virtual Sql GetBase(bool isContent, bool isMedia, string additionWhereStatement = "")
+ protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId)
+ {
+ var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key);
+
+ if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false));
+
+ return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)));
+ }
+
+ protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId)
+ {
+ var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id);
+
+ if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false));
+
+ return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)));
+ }
+
+ protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action filter)
+ {
+ var entitySql = GetBaseWhere(GetBase, isContent, isMedia, filter, objectTypeId);
+
+ if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false));
+
+ return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter);
+ }
+
+ private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null)
+ {
+ //this will add any dataNvarchar property to the output which can be added to the additional properties
+
+ var joinSql = new Sql()
+ .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias")
+ .From()
+ .InnerJoin()
+ .On(dto => dto.NodeId, dto => dto.NodeId)
+ .InnerJoin()
+ .On(dto => dto.Id, dto => dto.PropertyTypeId)
+ .InnerJoin()
+ .On(dto => dto.DataTypeId, dto => dto.DataTypeId)
+ .Where("umbracoNode.nodeObjectType = @nodeObjectType", new {nodeObjectType = Constants.ObjectTypes.Media});
+
+ if (filter != null)
+ {
+ filter(joinSql);
+ }
+
+ //We're going to create a query to query against the entity SQL
+ // because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join
+ // the entitySql query, we have to join the wrapped query to get the ntext in the result
+
+ var wrappedSql = new Sql("SELECT * FROM (")
+ .Append(entitySql)
+ .Append(new Sql(") tmpTbl LEFT JOIN ("))
+ .Append(joinSql)
+ .Append(new Sql(") as property ON id = property.contentNodeId"))
+ .OrderBy("sortOrder");
+
+ return wrappedSql;
+ }
+
+ protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter)
{
var columns = new List
{
@@ -221,13 +358,9 @@ namespace Umbraco.Core.Persistence.Repositories
columns.Add("contenttype.isContainer");
}
- if (isMedia)
- {
- columns.Add("property.dataNvarchar as umbracoFile");
- columns.Add("property.controlId");
- }
+ //Creates an SQL query to return a single row for the entity
- var sql = new Sql()
+ var entitySql = new Sql()
.Select(columns.ToArray())
.From("umbracoNode umbracoNode")
.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id");
@@ -235,7 +368,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (isContent || isMedia)
{
- sql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id")
+ entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id")
.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType")
.LeftJoin(
"(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published")
@@ -245,60 +378,54 @@ namespace Umbraco.Core.Persistence.Repositories
.On("umbracoNode.id = latest.nodeId");
}
- if (isMedia)
+ if (customFilter != null)
{
- sql.LeftJoin(
- "(SELECT contentNodeId, versionId, dataNvarchar, controlId FROM cmsPropertyData " +
- "INNER JOIN umbracoNode ON cmsPropertyData.contentNodeId = umbracoNode.id " +
- "INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid " +
- "INNER JOIN cmsDataType ON cmsPropertyType.dataTypeId = cmsDataType.nodeId "+
- "WHERE umbracoNode.nodeObjectType = '" + Constants.ObjectTypes.Media + "'" + additionWhereStatement + ") as property")
- .On("umbracoNode.id = property.contentNodeId");
+ customFilter(entitySql);
}
- return sql;
+ return entitySql;
}
- protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, string additionWhereStatement, Guid nodeObjectType)
+ protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Action filter, Guid nodeObjectType)
{
- var sql = baseQuery(isContent, isMedia, additionWhereStatement)
+ var sql = baseQuery(isContent, isMedia, filter)
.Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType });
return sql;
}
- protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, int id)
+ protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, int id)
{
- var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'")
+ var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.id = @Id", new { Id = id })
.Append(GetGroupBy(isContent, isMedia));
return sql;
}
- protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid key)
+ protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid key)
{
- var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'")
+ var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key })
.Append(GetGroupBy(isContent, isMedia));
return sql;
}
- protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id)
+ protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id)
{
- var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'")
+ var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType",
- new { Id = id, NodeObjectType = nodeObjectType });
+ new {Id = id, NodeObjectType = nodeObjectType});
return sql;
}
- protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key)
+ protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key)
{
- var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'")
+ var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType",
new { UniqueID = key, NodeObjectType = nodeObjectType });
return sql;
}
- protected virtual Sql GetGroupBy(bool isContent, bool isMedia)
+ protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true)
{
var columns = new List
{
@@ -324,19 +451,18 @@ namespace Umbraco.Core.Persistence.Repositories
columns.Add("contenttype.thumbnail");
columns.Add("contenttype.isContainer");
}
+
+ var sql = new Sql()
+ .GroupBy(columns.ToArray());
- if (isMedia)
+ if (includeSort)
{
- columns.Add("property.dataNvarchar");
- columns.Add("property.controlId");
+ sql = sql.OrderBy("umbracoNode.sortOrder");
}
- var sql = new Sql()
- .GroupBy(columns.ToArray())
- .OrderBy("umbracoNode.sortOrder");
return sql;
}
-
+
#endregion
///
@@ -418,10 +544,10 @@ namespace Umbraco.Core.Persistence.Repositories
Current.UmbracoProperties = new List();
}
Current.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty
- {
- DataTypeControlId = p.DataTypeControlId,
- Value = p.UmbracoFile
- });
+ {
+ DataTypeControlId = p.DataTypeControlId,
+ Value = p.UmbracoFile
+ });
// Return null to indicate we're not done with this UmbracoEntity yet
return null;
}
@@ -433,7 +559,7 @@ namespace Umbraco.Core.Persistence.Repositories
var prev = Current;
// Setup the new current UmbracoEntity
-
+
Current = _factory.BuildEntityFromDynamic(a);
//add the property/create the prop list if null
diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
index 22e3970e1c..ace637764b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
@@ -188,6 +188,8 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
protected PropertyCollection GetPropertyCollection(int id, Guid versionId, IContentTypeComposition contentType, DateTime createDate, DateTime updateDate)
+
+
{
var sql = new Sql();
sql.Select("*")
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
index b4c0020305..899a462172 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
@@ -13,10 +13,19 @@ namespace Umbraco.Core.Persistence.SqlSyntax
{
string EscapeString(string val);
+ string GetWildcardPlaceholder();
+ string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType);
+ string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType);
+
+ [Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType);
+ [Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType);
+ [Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType);
+ [Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType);
+ [Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType);
string GetQuotedTableName(string tableName);
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
new file mode 100644
index 0000000000..84c6e6e824
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs
@@ -0,0 +1,120 @@
+using System;
+using Umbraco.Core.Persistence.Querying;
+
+namespace Umbraco.Core.Persistence.SqlSyntax
+{
+ ///
+ /// Abstract class for defining MS sql implementations
+ ///
+ ///
+ public abstract class MicrosoftSqlSyntaxProviderBase : SqlSyntaxProviderBase
+ where TSyntax : ISqlSyntaxProvider
+ {
+ public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } }
+
+ public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } }
+
+ public override string GetQuotedTableName(string tableName)
+ {
+ return string.Format("[{0}]", tableName);
+ }
+
+ public override string GetQuotedColumnName(string columnName)
+ {
+ return string.Format("[{0}]", columnName);
+ }
+
+ public override string GetQuotedName(string name)
+ {
+ return string.Format("[{0}]", name);
+ }
+
+ public override string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType)
+ {
+ switch (columnType)
+ {
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnEqualComparison(column, paramIndex, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for = comparison with NText columns but allows this syntax
+ return string.Format("{0} LIKE @{1}", column, paramIndex);
+ default:
+ throw new ArgumentOutOfRangeException("columnType");
+ }
+ }
+
+ public override string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType)
+ {
+ switch (columnType)
+ {
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnWildcardComparison(column, paramIndex, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for upper methods with NText columns
+ return string.Format("{0} LIKE @{1}", column, paramIndex);
+ default:
+ throw new ArgumentOutOfRangeException("columnType");
+ }
+ }
+
+ [Obsolete("Use the overload with the parameter index instead")]
+ public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
+ {
+ switch (columnType)
+ {
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnStartsWithComparison(column, value, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for upper methods with NText columns
+ return string.Format("{0} LIKE '{1}%'", column, value);
+ default:
+ throw new ArgumentOutOfRangeException("columnType");
+ }
+ }
+
+ [Obsolete("Use the overload with the parameter index instead")]
+ public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
+ {
+ switch (columnType)
+ {
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnEndsWithComparison(column, value, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for upper methods with NText columns
+ return string.Format("{0} LIKE '%{1}'", column, value);
+ default:
+ throw new ArgumentOutOfRangeException("columnType");
+ }
+ }
+
+ [Obsolete("Use the overload with the parameter index instead")]
+ public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
+ {
+ switch (columnType)
+ {
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnContainsComparison(column, value, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for upper methods with NText columns
+ return string.Format("{0} LIKE '%{1}%'", column, value);
+ default:
+ throw new ArgumentOutOfRangeException("columnType");
+ }
+ }
+
+ [Obsolete("Use the overload with the parameter index instead")]
+ public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
+ {
+ switch (columnType)
+ {
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnContainsComparison(column, value, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for upper methods with NText columns
+ return string.Format("{0} LIKE '{1}'", column, value);
+ default:
+ throw new ArgumentOutOfRangeException("columnType");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs
index 170b10cb5e..44b8a761c9 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// Represents an SqlSyntaxProvider for Sql Ce
///
[SqlSyntaxProviderAttribute("System.Data.SqlServerCe.4.0")]
- public class SqlCeSyntaxProvider : SqlSyntaxProviderBase
+ public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase
{
public SqlCeSyntaxProvider()
{
@@ -60,6 +60,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return indexType;
}
+ [Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
@@ -74,76 +75,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
}
}
- public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnStartsWithComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '{1}%'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnEndsWithComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '%{1}'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnContainsComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '%{1}%'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnContainsComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '{1}'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetQuotedTableName(string tableName)
- {
- return string.Format("[{0}]", tableName);
- }
-
- public override string GetQuotedColumnName(string columnName)
- {
- return string.Format("[{0}]", columnName);
- }
-
- public override string GetQuotedName(string name)
- {
- return string.Format("[{0}]", name);
- }
+
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
@@ -285,10 +217,10 @@ ORDER BY TABLE_NAME, INDEX_NAME");
}
}
- public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } }
+
public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } }
- public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
index ca9e88fb1e..3388b984ee 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
-using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.SqlSyntax
{
@@ -10,7 +9,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// Represents an SqlSyntaxProvider for Sql Server
///
[SqlSyntaxProviderAttribute("System.Data.SqlClient")]
- public class SqlServerSyntaxProvider : SqlSyntaxProviderBase
+ public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase
{
public SqlServerSyntaxProvider()
{
@@ -33,91 +32,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// Gets/sets the version of the current SQL server instance
///
internal Lazy VersionName { get; set; }
-
- public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnEqualComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for = comparison with NText columns but allows this syntax
- return string.Format("{0} LIKE '{1}'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnStartsWithComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '{1}%'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnEndsWithComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '%{1}'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnContainsComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '%{1}%'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
- {
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnContainsComparison(column, value, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return string.Format("{0} LIKE '{1}'", column, value);
- default:
- throw new ArgumentOutOfRangeException("columnType");
- }
- }
-
- public override string GetQuotedTableName(string tableName)
- {
- return string.Format("[{0}]", tableName);
- }
-
- public override string GetQuotedColumnName(string columnName)
- {
- return string.Format("[{0}]", columnName);
- }
-
- public override string GetQuotedName(string name)
- {
- return string.Format("[{0}]", name);
- }
+
+
public override IEnumerable GetTablesInSchema(Database db)
{
@@ -218,12 +134,11 @@ order by T.name, I.name");
get { return "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; }
}
- public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } }
-
+
public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } }
public override string RenameColumn { get { return "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; } }
- public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
index 1ceeddafb7..08d85e0573 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
@@ -33,6 +33,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax
};
}
+ public string GetWildcardPlaceholder()
+ {
+ return "%";
+ }
+
public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})";
public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})";
@@ -108,34 +113,51 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''"));
}
+ public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType)
+ {
+ //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
+ return string.Format("upper({0}) = upper(@{1})", column, paramIndex);
+ }
+
+ public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType)
+ {
+ //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
+ return string.Format("upper({0}) LIKE upper(@{1})", column, paramIndex);
+ }
+
+ [Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) = '{1}'", column, value.ToUpper());
}
+ [Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
- return string.Format("upper({0}) like '{1}%'", column, value.ToUpper());
+ return string.Format("upper({0}) LIKE '{1}%'", column, value.ToUpper());
}
+ [Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
- return string.Format("upper({0}) like '%{1}'", column, value.ToUpper());
+ return string.Format("upper({0}) LIKE '%{1}'", column, value.ToUpper());
}
+ [Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
- return string.Format("upper({0}) like '%{1}%'", column, value.ToUpper());
+ return string.Format("upper({0}) LIKE '%{1}%'", column, value.ToUpper());
}
+ [Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
- return string.Format("upper({0}) like '{1}'", column, value.ToUpper());
+ return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper());
}
public virtual string GetQuotedTableName(string tableName)
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
index 8bcc965af7..d664ca53f9 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
@@ -10,12 +10,12 @@
///
/// See: http://issues.umbraco.org/issue/U4-3876
///
- public static string GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery)
+ public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery)
{
- return string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)",
- sqlProvider.GetQuotedTableName(tableName),
- sqlProvider.GetQuotedColumnName(columnName),
- subQuery.SQL);
+ return new Sql(string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)",
+ sqlProvider.GetQuotedTableName(tableName),
+ sqlProvider.GetQuotedColumnName(columnName),
+ subQuery.SQL), subQuery.Arguments);
}
}
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index d24f39d726..6988189cdc 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -484,6 +484,10 @@ namespace Umbraco.Core.Services
public IEnumerable GetDescendants(int id)
{
var content = GetById(id);
+ if (content == null)
+ {
+ return Enumerable.Empty();
+ }
return GetDescendants(content);
}
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index 6b237e652d..5402290872 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -402,6 +402,10 @@ namespace Umbraco.Core.Services
public IEnumerable GetDescendants(int id)
{
var media = GetById(id);
+ if (media == null)
+ {
+ return Enumerable.Empty();
+ }
return GetDescendants(media);
}
@@ -1198,4 +1202,4 @@ namespace Umbraco.Core.Services
public static event TypedEventHandler EmptiedRecycleBin;
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 31abc05e87..36192f03ac 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -233,6 +233,7 @@
+
diff --git a/src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs b/src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs
new file mode 100644
index 0000000000..eb177981c3
--- /dev/null
+++ b/src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs
@@ -0,0 +1,133 @@
+//using System;
+//using System.Linq;
+//using NUnit.Framework;
+//using Umbraco.Core.Models;
+//using Umbraco.Core.Persistence;
+//using Umbraco.Core.Persistence.SqlSyntax;
+//using Umbraco.Tests.TestHelpers;
+//using Umbraco.Tests.TestHelpers.Entities;
+
+//namespace Umbraco.Tests.Persistence
+//{
+// [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
+// [TestFixture]
+// public class PetaPocoDynamicQueryTests : BaseDatabaseFactoryTest
+// {
+// [Test]
+// public void Check_Poco_Storage_Growth()
+// {
+// //CreateStuff();
+
+// for (int i = 0; i < 1000; i++)
+// {
+// DatabaseContext.Database.Fetch(
+// "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='" + i + "'");
+// }
+
+// //var oc11 = Database.PocoData.GetCachedPocoData().Count();
+// //var oc12 = Database.PocoData.GetConverters().Count();
+// //var oc13 = Database.GetAutoMappers().Count();
+// //var oc14 = Database.GetMultiPocoFactories().Count();
+
+// //for (int i = 0; i < 2; i++)
+// //{
+// // i1 = DatabaseContext.Database.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES");
+// // r1 = i1.Select(x => x.TABLE_NAME).Cast().ToList();
+// //}
+
+// //var oc21 = Database.PocoData.GetCachedPocoData().Count();
+// //var oc22 = Database.PocoData.GetConverters().Count();
+// //var oc23 = Database.GetAutoMappers().Count();
+// //var oc24 = Database.GetMultiPocoFactories().Count();
+
+// //var roots = ServiceContext.ContentService.GetRootContent();
+// //foreach (var content in roots)
+// //{
+// // var d = ServiceContext.ContentService.GetDescendants(content);
+// //}
+
+// //var oc31 = Database.PocoData.GetCachedPocoData().Count();
+// //var oc32 = Database.PocoData.GetConverters().Count();
+// //var oc33 = Database.GetAutoMappers().Count();
+// //var oc34 = Database.GetMultiPocoFactories().Count();
+
+// //for (int i = 0; i < 2; i++)
+// //{
+// // roots = ServiceContext.ContentService.GetRootContent();
+// // foreach (var content in roots)
+// // {
+// // var d = ServiceContext.ContentService.GetDescendants(content);
+// // }
+// //}
+
+// //var oc41 = Database.PocoData.GetCachedPocoData().Count();
+// //var oc42 = Database.PocoData.GetConverters().Count();
+// //var oc43 = Database.GetAutoMappers().Count();
+// //var oc44 = Database.GetMultiPocoFactories().Count();
+
+// //var i2 = DatabaseContext.Database.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS");
+// //var r2 =
+// // i2.Select(
+// // item =>
+// // new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT,
+// // item.IS_NULLABLE, item.DATA_TYPE)).ToList();
+
+
+// var pocoData = Database.PocoData.GetCachedPocoData();
+// Console.WriteLine("GetCachedPocoData: " + pocoData.Count());
+// foreach (var keyValuePair in pocoData)
+// {
+// Console.WriteLine(keyValuePair.Value.GetFactories().Count());
+// }
+
+// Console.WriteLine("GetConverters: " + Database.PocoData.GetConverters().Count());
+// Console.WriteLine("GetAutoMappers: " + Database.GetAutoMappers().Count());
+// Console.WriteLine("GetMultiPocoFactories: " + Database.GetMultiPocoFactories().Count());
+
+// //Assert.AreEqual(oc11, oc21);
+// //Assert.AreEqual(oc12, oc22);
+// //Assert.AreEqual(oc13, oc23);
+// //Assert.AreEqual(oc14, oc24);
+
+// //Assert.AreEqual(oc31, oc41);
+// //Assert.AreEqual(oc32, oc42);
+// //Assert.AreEqual(oc33, oc43);
+// //Assert.AreEqual(oc34, oc44);
+// }
+
+// public void CreateStuff()
+// {
+// var contentType1 = MockedContentTypes.CreateTextpageContentType("test1", "test1");
+// var contentType2 = MockedContentTypes.CreateTextpageContentType("test2", "test2");
+// var contentType3 = MockedContentTypes.CreateTextpageContentType("test3", "test3");
+// ServiceContext.ContentTypeService.Save(new[] { contentType1, contentType2, contentType3 });
+// contentType1.AllowedContentTypes = new[]
+// {
+// new ContentTypeSort(new Lazy(() => contentType2.Id), 0, contentType2.Alias),
+// new ContentTypeSort(new Lazy(() => contentType3.Id), 1, contentType3.Alias)
+// };
+// contentType2.AllowedContentTypes = new[]
+// {
+// new ContentTypeSort(new Lazy(() => contentType1.Id), 0, contentType1.Alias),
+// new ContentTypeSort(new Lazy(() => contentType3.Id), 1, contentType3.Alias)
+// };
+// contentType3.AllowedContentTypes = new[]
+// {
+// new ContentTypeSort(new Lazy(() => contentType1.Id), 0, contentType1.Alias),
+// new ContentTypeSort(new Lazy(() => contentType2.Id), 1, contentType2.Alias)
+// };
+// ServiceContext.ContentTypeService.Save(new[] { contentType1, contentType2, contentType3 });
+
+// var roots = MockedContent.CreateTextpageContent(contentType1, -1, 3);
+// ServiceContext.ContentService.Save(roots);
+// foreach (var root in roots)
+// {
+// var item1 = MockedContent.CreateTextpageContent(contentType1, root.Id, 3);
+// var item2 = MockedContent.CreateTextpageContent(contentType2, root.Id, 3);
+// var item3 = MockedContent.CreateTextpageContent(contentType3, root.Id, 3);
+
+// ServiceContext.ContentService.Save(item1.Concat(item2).Concat(item3));
+// }
+// }
+// }
+//}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs
index b0b7247edb..6348fadc9f 100644
--- a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs
+++ b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs
@@ -1,14 +1,195 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Text.RegularExpressions;
+using System.Threading;
using NUnit.Framework;
using Umbraco.Core;
+using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
+using Umbraco.Core.Services;
+using Umbraco.Tests.Services;
using Umbraco.Tests.TestHelpers;
+using Umbraco.Tests.TestHelpers.Entities;
namespace Umbraco.Tests.Persistence
{
+ [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
+ [TestFixture, NUnit.Framework.Ignore]
+ public class PetaPocoCachesTest : BaseServiceTest
+ {
+ ///
+ /// This tests the peta poco caches
+ ///
+ ///
+ /// This test WILL fail. This is because we cannot stop PetaPoco from creating more cached items for queries such as
+ /// ContentTypeRepository.GetAll(1,2,3,4);
+ /// when combined with other GetAll queries that pass in an array of Ids, each query generated for different length
+ /// arrays will produce a unique query which then gets added to the cache.
+ ///
+ /// This test confirms this, if you analyze the DIFFERENCE output below you can see why the cached queries grow.
+ ///
+ [Test]
+ public void Check_Peta_Poco_Caches()
+ {
+ var result = new List>>();
+
+ Database.PocoData.UseLongKeys = true;
+
+ for (int i = 0; i < 2; i++)
+ {
+ int id1, id2, id3;
+ string alias;
+ CreateStuff(out id1, out id2, out id3, out alias);
+ QueryStuff(id1, id2, id3, alias);
+
+ double totalBytes1;
+ IEnumerable keys;
+ Console.Write(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys));
+
+ result.Add(new Tuple>(totalBytes1, keys.Count(), keys));
+ }
+
+ for (int index = 0; index < result.Count; index++)
+ {
+ var tuple = result[index];
+ Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2);
+ if (index != 0)
+ {
+ Console.WriteLine("----------------DIFFERENCE---------------------");
+ var diff = tuple.Item3.Except(result[index - 1].Item3);
+ foreach (var d in diff)
+ {
+ Console.WriteLine(d);
+ }
+ }
+
+ }
+
+ var allByteResults = result.Select(x => x.Item1).Distinct();
+ var totalKeys = result.Select(x => x.Item2).Distinct();
+
+ Assert.AreEqual(1, allByteResults.Count());
+ Assert.AreEqual(1, totalKeys.Count());
+ }
+
+ [Test]
+ public void Verify_Memory_Expires()
+ {
+ Database.PocoData.SlidingExpirationSeconds = 2;
+
+ var managedCache = new Database.ManagedCache();
+
+ int id1, id2, id3;
+ string alias;
+ CreateStuff(out id1, out id2, out id3, out alias);
+ QueryStuff(id1, id2, id3, alias);
+
+ var count1 = managedCache.GetCache().GetCount();
+ Console.WriteLine("Keys = " + count1);
+ Assert.Greater(count1, 0);
+
+ Thread.Sleep(10000);
+
+ var count2 = managedCache.GetCache().GetCount();
+ Console.WriteLine("Keys = " + count2);
+ Assert.Less(count2, count1);
+ }
+
+ private void QueryStuff(int id1, int id2, int id3, string alias1)
+ {
+ var contentService = ServiceContext.ContentService;
+
+
+
+ contentService.CountDescendants(id3);
+
+ contentService.CountChildren(id3);
+
+ contentService.Count(contentTypeAlias: alias1);
+
+ contentService.Count();
+
+ contentService.GetById(Guid.NewGuid());
+
+ contentService.GetByLevel(2);
+
+ contentService.GetChildren(id1);
+
+ contentService.GetDescendants(id2);
+
+ contentService.GetVersions(id3);
+
+ contentService.GetRootContent();
+
+ contentService.GetContentForExpiration();
+
+ contentService.GetContentForRelease();
+
+ contentService.GetContentInRecycleBin();
+
+ ((ContentService)contentService).GetPublishedDescendants(new Content("Test", -1, new ContentType(-1))
+ {
+ Id = id1,
+ Path = "-1," + id1
+ });
+
+ contentService.GetByVersion(Guid.NewGuid());
+ }
+
+ private void CreateStuff(out int id1, out int id2, out int id3, out string alias)
+ {
+ var contentService = ServiceContext.ContentService;
+
+ var ctAlias = "umbTextpage" + Guid.NewGuid().ToString("N");
+ alias = ctAlias;
+
+ for (int i = 0; i < 20; i++)
+ {
+ contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0);
+ }
+ var contentTypeService = ServiceContext.ContentTypeService;
+ var contentType = MockedContentTypes.CreateSimpleContentType(ctAlias, "test Doc Type");
+ contentTypeService.Save(contentType);
+ for (int i = 0; i < 20; i++)
+ {
+ contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0);
+ }
+ var parent = contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0);
+ id1 = parent.Id;
+
+ for (int i = 0; i < 20; i++)
+ {
+ contentService.CreateContentWithIdentity("Test", parent, ctAlias);
+ }
+ IContent current = parent;
+ for (int i = 0; i < 20; i++)
+ {
+ current = contentService.CreateContentWithIdentity("Test", current, ctAlias);
+ }
+ contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory" + Guid.NewGuid().ToString("N"), "Mandatory Doc Type", true);
+ contentType.PropertyGroups.First().PropertyTypes.Add(
+ new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Ntext)
+ {
+ Alias = "tags",
+ DataTypeDefinitionId = 1041
+ });
+ contentTypeService.Save(contentType);
+ var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
+
+ contentService.Publish(content1);
+ id2 = content1.Id;
+
+ var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
+
+ contentService.Publish(content2);
+ id3 = content2.Id;
+
+ contentService.MoveToRecycleBin(content1);
+ }
+ }
+
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture]
public class PetaPocoExtensionsTest : BaseDatabaseFactoryTest
diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs
index 78254f6f32..4b1c71901d 100644
--- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs
@@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'");
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972"));
var sql = new Sql();
sql.Select("*")
@@ -36,6 +36,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
@@ -50,8 +56,8 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'")
- .Where("[umbracoNode].[id] = 1050");
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972"))
+ .Where("[umbracoNode].[id] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -67,6 +73,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
@@ -82,9 +94,9 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'")
- .Where("[umbracoNode].[id] = 1050")
- .Where("[cmsContentVersion].[VersionId] = '2b543516-a944-4ee6-88c6-8813da7aaa07'")
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972"))
+ .Where("[umbracoNode].[id] = @0", 1050)
+ .Where("[cmsContentVersion].[VersionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07"))
.OrderBy("[cmsContentVersion].[VersionDate] DESC");
var sql = new Sql();
@@ -102,6 +114,11 @@ namespace Umbraco.Tests.Persistence.Querying
.OrderByDescending(x => x.VersionDate);
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
Console.WriteLine(sql.SQL);
}
@@ -116,8 +133,8 @@ namespace Umbraco.Tests.Persistence.Querying
expected.Select("*");
expected.From("[cmsPropertyData]");
expected.InnerJoin("[cmsPropertyType]").On("[cmsPropertyData].[propertytypeid] = [cmsPropertyType].[id]");
- expected.Where("[cmsPropertyData].[contentNodeId] = 1050");
- expected.Where("[cmsPropertyData].[versionId] = '2b543516-a944-4ee6-88c6-8813da7aaa07'");
+ expected.Where("[cmsPropertyData].[contentNodeId] = @0", 1050);
+ expected.Where("[cmsPropertyData].[versionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07"));
var sql = new Sql();
sql.Select("*")
@@ -128,6 +145,11 @@ namespace Umbraco.Tests.Persistence.Querying
.Where(x => x.VersionId == versionId);
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
Console.WriteLine(sql.SQL);
}
diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs
index 6cc5824e92..e25b95383e 100644
--- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs
@@ -22,8 +22,8 @@ namespace Umbraco.Tests.Persistence.Querying
.On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]")
.InnerJoin("[umbracoNode]")
.On("[cmsContentType].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = 'a2cb7800-f571-4787-9638-bc48539a0efb'")
- .Where("[cmsDocumentType].[IsDefault] = 1");
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb"))
+ .Where("[cmsDocumentType].[IsDefault] = @0", true);
var sql = new Sql();
sql.Select("*")
@@ -37,6 +37,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
@@ -52,9 +58,9 @@ namespace Umbraco.Tests.Persistence.Querying
.On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]")
.InnerJoin("[umbracoNode]")
.On("[cmsContentType].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = 'a2cb7800-f571-4787-9638-bc48539a0efb'")
- .Where("[cmsDocumentType].[IsDefault] = 1")
- .Where("[umbracoNode].[id] = 1050");
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb"))
+ .Where("[cmsDocumentType].[IsDefault] = @0", true)
+ .Where("[umbracoNode].[id] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -64,11 +70,17 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin()
.On(left => left.NodeId, right => right.NodeId)
.Where(x => x.NodeObjectType == NodeObjectType)
- .Where(x => x.IsDefault == true)
+ .Where(x => x.IsDefault)
.Where(x => x.NodeId == 1050);
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
@@ -100,7 +112,7 @@ namespace Umbraco.Tests.Persistence.Querying
var expected = new Sql();
expected.Select("*")
.From("[cmsContentTypeAllowedContentType]")
- .Where("[cmsContentTypeAllowedContentType].[Id] = 1050");
+ .Where("[cmsContentTypeAllowedContentType].[Id] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -109,6 +121,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
@@ -120,7 +138,7 @@ namespace Umbraco.Tests.Persistence.Querying
.From("[cmsPropertyTypeGroup]")
.RightJoin("[cmsPropertyType]").On("[cmsPropertyTypeGroup].[id] = [cmsPropertyType].[propertyTypeGroupId]")
.InnerJoin("[cmsDataType]").On("[cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]")
- .Where("[cmsPropertyType].[contentTypeId] = 1050");
+ .Where("[cmsPropertyType].[contentTypeId] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -133,6 +151,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
}
diff --git a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs
index 1862c45952..113ed68e81 100644
--- a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs
@@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying
expected.Select("*")
.From("[cmsDataType]")
.InnerJoin("[umbracoNode]").On("[cmsDataType].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = '30a2a501-1978-4ddb-a57b-f7efed43ba3c'");
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("30a2a501-1978-4ddb-a57b-f7efed43ba3c"));
var sql = new Sql();
sql.Select("*")
@@ -30,6 +30,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
}
diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs
index ba1592635f..29c73232d3 100644
--- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
- Assert.AreEqual("upper([umbracoNode].[path]) like '-1%'", result);
+ Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result);
+ Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -36,7 +37,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
- Assert.AreEqual("[umbracoNode].[parentID] = -1", result);
+ Assert.AreEqual("[umbracoNode].[parentID] = @0", result);
+ Assert.AreEqual(-1, modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -48,7 +50,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
- Assert.AreEqual("[umbracoUser].[userLogin] = 'hello@@world.com'", result);
+ Assert.AreEqual("[umbracoUser].[userLogin] = @0", result);
+ Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -60,7 +63,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
- Assert.AreEqual("upper([umbracoUser].[userLogin]) = 'HELLO@@WORLD.COM'", result);
+ Assert.AreEqual("upper([umbracoUser].[userLogin]) = upper(@0)", result);
+ Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -75,7 +79,9 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
- Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = 'MYDOMAIN\\\\MYUSER'", result);
+ Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = upper(@0)", result);
+ Assert.AreEqual("mydomain\\myuser", modelToSqlExpressionHelper.GetSqlParameters()[0]);
+
}
[Test]
@@ -90,7 +96,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Poco to Sql ExpressionHelper: \n" + result);
- Assert.AreEqual("upper(`umbracoUser`.`userLogin`) like 'MYDOMAIN\\\\MYUSER%'", result);
+ Assert.AreEqual("upper(`umbracoUser`.`userLogin`) LIKE upper(@0)", result);
+ Assert.AreEqual("mydomain\\myuser%", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
}
diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs
index bbe90637f4..5e680a426e 100644
--- a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Tests.Persistence.Querying
.From("[cmsContentVersion]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c'");
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("b796f64c-1f99-4ffb-b886-4bf4bc011a9c"));
var sql = new Sql();
sql.Select("*")
@@ -33,6 +33,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
}
diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs
index 2bdb58d5b8..31fc08d822 100644
--- a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs
@@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying
expected.Select("*")
.From("[cmsContentType]")
.InnerJoin("[umbracoNode]").On("[cmsContentType].[nodeId] = [umbracoNode].[id]")
- .Where("[umbracoNode].[nodeObjectType] = '4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e'");
+ .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e"));
var sql = new Sql();
sql.Select("*")
@@ -30,6 +30,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
+ Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
+ for (int i = 0; i < expected.Arguments.Length; i++)
+ {
+ Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
+ }
+
Console.WriteLine(sql.SQL);
}
}
diff --git a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs
index 881e4af110..f934b0c536 100644
--- a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
@@ -6,12 +7,141 @@ using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Tests.TestHelpers;
+using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Tests.Persistence.Querying
{
[TestFixture]
public class PetaPocoSqlTests : BaseUsingSqlCeSyntax
{
+ //x =>
+
+ [Test]
+ public void Where_Clause_With_Starts_With_Additional_Parameters()
+ {
+ var content = new NodeDto() { NodeId = 123, Path = "-1,123" };
+ var sql = new Sql("SELECT *").From().Where(x => x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar));
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0))", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual(content.Path + "%", sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_With_Starts_With_By_Variable()
+ {
+ var content = new NodeDto() {NodeId = 123, Path = "-1,123"};
+ var sql = new Sql("SELECT *").From().Where(x => x.Path.StartsWith(content.Path) && x.NodeId != content.NodeId);
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0) AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(2, sql.Arguments.Length);
+ Assert.AreEqual(content.Path + "%", sql.Arguments[0]);
+ Assert.AreEqual(content.NodeId, sql.Arguments[1]);
+ }
+
+ [Test]
+ public void Where_Clause_With_Not_Starts_With()
+ {
+ var level = 1;
+ var sql = new Sql("SELECT *").From().Where(x => x.Level == level && !x.Path.StartsWith("-20"));
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[level] = @0 AND NOT (upper([umbracoNode].[path]) LIKE upper(@1)))", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(2, sql.Arguments.Length);
+ Assert.AreEqual(level, sql.Arguments[0]);
+ Assert.AreEqual("-20%", sql.Arguments[1]);
+ }
+
+ [Test]
+ public void Where_Clause_With_Equals_Clause()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.Text.Equals("Hello@world.com"));
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual("Hello@world.com", sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_With_False_Boolean()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => !x.Trashed);
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual(true, sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_With_Boolean()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.Trashed);
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[trashed] = @0)", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual(true, sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_With_ToUpper()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.Text.ToUpper() == "hello".ToUpper());
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual("hello", sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_With_ToString()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.Text == 1.ToString());
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0)", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual("1", sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_With_Wildcard()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.Text.StartsWith("D"));
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) LIKE upper(@0))", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual("D%", sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_Single_Constant()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.NodeId == 2);
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] = @0)", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual(2, sql.Arguments[0]);
+ }
+
+ [Test]
+ public void Where_Clause_And_Constant()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.NodeId != 2 && x.NodeId != 3);
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] <> @0 AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(2, sql.Arguments.Length);
+ Assert.AreEqual(2, sql.Arguments[0]);
+ Assert.AreEqual(3, sql.Arguments[1]);
+ }
+
+ [Test]
+ public void Where_Clause_Or_Constant()
+ {
+ var sql = new Sql("SELECT *").From().Where(x => x.Text == "hello" || x.NodeId == 3);
+
+ Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0 OR [umbracoNode].[id] = @1)", sql.SQL.Replace("\n", " "));
+ Assert.AreEqual(2, sql.Arguments.Length);
+ Assert.AreEqual("hello", sql.Arguments[0]);
+ Assert.AreEqual(3, sql.Arguments[1]);
+ }
[Test]
public void Can_Select_From_With_Type()
@@ -78,7 +208,7 @@ namespace Umbraco.Tests.Persistence.Querying
public void Can_Use_Where_Predicate()
{
var expected = new Sql();
- expected.Select("*").From("[cmsContent]").Where("[cmsContent].[nodeId] = 1045");
+ expected.Select("*").From("[cmsContent]").Where("[cmsContent].[nodeId] = @0", 1045);
var sql = new Sql();
sql.Select("*").From().Where(x => x.NodeId == 1045);
@@ -94,8 +224,8 @@ namespace Umbraco.Tests.Persistence.Querying
var expected = new Sql();
expected.Select("*")
.From("[cmsContent]")
- .Where("[cmsContent].[nodeId] = 1045")
- .Where("[cmsContent].[contentType] = 1050");
+ .Where("[cmsContent].[nodeId] = @0", 1045)
+ .Where("[cmsContent].[contentType] = @0", 1050);
var sql = new Sql();
sql.Select("*")
diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs
index 25fc2c3d35..28cdd36e3c 100644
--- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs
@@ -12,21 +12,7 @@ namespace Umbraco.Tests.Persistence.Querying
[TestFixture]
public class QueryBuilderTests : BaseUsingSqlCeSyntax
{
- [Test]
- public void Dates_Formatted_Properly()
- {
- var sql = new Sql();
- sql.Select("*").From();
-
- var dt = new DateTime(2013, 11, 21, 13, 25, 55);
-
- var query = Query.Builder.Where(x => x.ExpireDate <= dt);
- var translator = new SqlTranslator(sql, query);
-
- var result = translator.Translate();
-
- Assert.IsTrue(result.SQL.Contains("[expireDate] <= '2013-11-21 13:25:55'"));
- }
+
[Test]
public void Can_Build_StartsWith_Query_For_IContent()
@@ -43,11 +29,15 @@ namespace Umbraco.Tests.Persistence.Querying
var result = translator.Translate();
var strResult = result.SQL;
- string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (upper([umbracoNode].[path]) like '-1%')";
+ string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (upper([umbracoNode].[path]) LIKE upper(@0))";
// Assert
Assert.That(strResult, Is.Not.Empty);
Assert.That(strResult, Is.EqualTo(expectedResult));
+
+ Assert.AreEqual(1, result.Arguments.Length);
+ Assert.AreEqual("-1%", sql.Arguments[0]);
+
Console.WriteLine(strResult);
}
@@ -66,11 +56,15 @@ namespace Umbraco.Tests.Persistence.Querying
var result = translator.Translate();
var strResult = result.SQL;
- string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([umbracoNode].[parentID] = -1)";
+ string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([umbracoNode].[parentID] = @0)";
// Assert
Assert.That(strResult, Is.Not.Empty);
Assert.That(strResult, Is.EqualTo(expectedResult));
+
+ Assert.AreEqual(1, result.Arguments.Length);
+ Assert.AreEqual(-1, sql.Arguments[0]);
+
Console.WriteLine(strResult);
}
@@ -89,11 +83,14 @@ namespace Umbraco.Tests.Persistence.Querying
var result = translator.Translate();
var strResult = result.SQL;
- string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([cmsContentType].[alias] = 'umbTextpage')";
+ string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([cmsContentType].[alias] = @0)";
// Assert
Assert.That(strResult, Is.Not.Empty);
Assert.That(strResult, Is.EqualTo(expectedResult));
+ Assert.AreEqual(1, result.Arguments.Length);
+ Assert.AreEqual("umbTextpage", sql.Arguments[0]);
+
Console.WriteLine(strResult);
}
diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs
index 4862333b07..1c6eaca2bb 100644
--- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs
+++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs
@@ -38,7 +38,11 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider
FROM [cmsContentXml]
INNER JOIN [umbracoNode]
ON [cmsContentXml].[nodeId] = [umbracoNode].[id]
-WHERE ([umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c')) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), sql.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "));
+WHERE ([umbracoNode].[nodeObjectType] = @0)) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "),
+ sql.SQL.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "));
+
+ Assert.AreEqual(1, sql.Arguments.Length);
+ Assert.AreEqual(mediaObjectType, sql.Arguments[0]);
}
[NUnit.Framework.Ignore("This doesn't actually test anything")]