diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs
index e170f9fb2a..11eab07ec2 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs
@@ -14,7 +14,7 @@
string AuthCookieName { get; }
string AuthCookieDomain { get; }
-
+
///
/// A boolean indicating that by default the email address will be the username
///
diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs
index e701b4ea12..1356e55775 100644
--- a/src/Umbraco.Core/Constants-Examine.cs
+++ b/src/Umbraco.Core/Constants-Examine.cs
@@ -18,7 +18,7 @@
/// The alias of the external content indexer
///
public const string ExternalIndexer = "ExternalIndexer";
-
+
///
/// The alias of the internal member searcher
///
diff --git a/src/Umbraco.Core/DateTimeExtensions.cs b/src/Umbraco.Core/DateTimeExtensions.cs
index bb993b8bc9..d82ec99c6a 100644
--- a/src/Umbraco.Core/DateTimeExtensions.cs
+++ b/src/Umbraco.Core/DateTimeExtensions.cs
@@ -42,7 +42,7 @@ namespace Umbraco.Core
Minute,
Second
}
-
+
///
/// Calculates the number of minutes from a date time, on a rolling daily basis (so if
/// date time is before the time, calculate onto next day)
@@ -57,10 +57,10 @@ namespace Umbraco.Core
{
scheduledTime = "0" + scheduledTime;
}
-
+
var scheduledHour = int.Parse(scheduledTime.Substring(0, 2));
var scheduledMinute = int.Parse(scheduledTime.Substring(2));
-
+
DateTime scheduledDateTime;
if (IsScheduledInRemainingDay(fromDateTime, scheduledHour, scheduledMinute))
{
@@ -71,10 +71,10 @@ namespace Umbraco.Core
var nextDay = fromDateTime.AddDays(1);
scheduledDateTime = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, scheduledHour, scheduledMinute, 0);
}
-
+
return (int)(scheduledDateTime - fromDateTime).TotalMinutes;
}
-
+
private static bool IsScheduledInRemainingDay(DateTime fromDateTime, int scheduledHour, int scheduledMinute)
{
return scheduledHour > fromDateTime.Hour || (scheduledHour == fromDateTime.Hour && scheduledMinute >= fromDateTime.Minute);
diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs
index 30e3ac52b8..620dc8ae03 100644
--- a/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Migrations
ILogger Logger { get; }
ILocalMigration GetLocalMigration();
-
+
ISqlContext SqlContext { get; }
}
}
diff --git a/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs b/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs
index 7c7a6ccdbd..76547265b9 100644
--- a/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs
+++ b/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Querying
///
public string VisitResult
{
- get { return _visitResult; }
+ get => _visitResult;
set
{
if (Visited)
diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
index 6846ea1e30..2b0350d8c7 100644
--- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
+++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs
@@ -20,6 +20,41 @@ namespace Umbraco.Core.Persistence.Querying
_pd = sqlContext.PocoDataFactory.ForType(typeof(TDto));
}
+ protected override string VisitMethodCall(MethodCallExpression m)
+ {
+ var declaring = m.Method.DeclaringType;
+ if (declaring != typeof (SqlTemplate))
+ return base.VisitMethodCall(m);
+
+ if (m.Method.Name != "Arg" && m.Method.Name != "ArgIn")
+ throw new NotSupportedException($"Method SqlTemplate.{m.Method.Name} is not supported.");
+
+ var parameters = m.Method.GetParameters();
+ if (parameters.Length != 1 || parameters[0].ParameterType != typeof (string))
+ throw new NotSupportedException($"Method SqlTemplate.{m.Method.Name}({string.Join(", ", parameters.Select(x => x.ParameterType))} is not supported.");
+
+ var arg = m.Arguments[0];
+ string name;
+ if (arg.NodeType == ExpressionType.Constant)
+ {
+ name = arg.ToString();
+ }
+ else
+ {
+ // though... we probably should avoid doing this
+ var member = Expression.Convert(arg, typeof (object));
+ var lambda = Expression.Lambda>(member);
+ var getter = lambda.Compile();
+ name = getter().ToString();
+ }
+
+ SqlParameters.Add(RemoveQuote(name));
+
+ return Visited
+ ? string.Empty
+ : $"@{SqlParameters.Count - 1}";
+ }
+
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(TDto))
@@ -95,6 +130,10 @@ namespace Umbraco.Core.Persistence.Querying
_parameterName1 = lambda.Parameters[0].Name;
_parameterName2 = lambda.Parameters[1].Name;
}
+ else
+ {
+ _parameterName1 = _parameterName2 = null;
+ }
return base.VisitLambda(lambda);
}
diff --git a/src/Umbraco.Core/Persistence/SqlTemplate.cs b/src/Umbraco.Core/Persistence/SqlTemplate.cs
index ffe0ca99e1..3dc3b7389f 100644
--- a/src/Umbraco.Core/Persistence/SqlTemplate.cs
+++ b/src/Umbraco.Core/Persistence/SqlTemplate.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NPoco;
@@ -29,22 +30,61 @@ namespace Umbraco.Core.Persistence
// must pass the args in the proper order, faster
public Sql Sql(params object[] args)
{
- return new Sql(_sqlContext, _sql, args);
+ // if the type is an "unspeakable name" it is an anonymous compiler-generated object
+ // see https://stackoverflow.com/questions/9256594
+ // => assume it's an anonymous type object containing named arguments
+ // (of course this means we cannot use *real* objects here and need SqlNamed - bah)
+ if (args.Length == 1 && args[0].GetType().Name.Contains("<"))
+ return SqlNamed(args[0]);
+
+ if (args.Length != _args.Count)
+ throw new ArgumentException("Invalid number of arguments.", nameof(args));
+
+ if (args.Length == 0)
+ return new Sql(_sqlContext, true, _sql);
+
+ var isBuilt = !args.Any(x => x is IEnumerable);
+ return new Sql(_sqlContext, isBuilt, _sql, args);
}
// can pass named args, slower
// so, not much different from what Where(...) does (ie reflection)
public Sql SqlNamed(object nargs)
{
+ var isBuilt = true;
var args = new object[_args.Count];
var properties = nargs.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(nargs));
for (var i = 0; i < _args.Count; i++)
{
if (!properties.TryGetValue(_args[i], out var value))
- throw new InvalidOperationException($"Invalid argument name \"{_args[i]}\".");
+ throw new InvalidOperationException($"Missing argument \"{_args[i]}\".");
args[i] = value;
+ properties.Remove(_args[i]);
+
+ // if value is enumerable then we'll need to expand arguments
+ if (value is IEnumerable)
+ isBuilt = false;
}
- return new Sql(_sqlContext, _sql, args);
+ if (properties.Count > 0)
+ throw new InvalidOperationException($"Unknown argument{(properties.Count > 1 ? "s" : "")}: {string.Join(", ", properties.Keys)}");
+ return new Sql(_sqlContext, isBuilt, _sql, args);
+ }
+
+ internal void WriteToConsole()
+ {
+ new Sql(_sqlContext, _sql, _args.Values.Cast