Files
Umbraco-CMS/src/Umbraco.Core/Persistence/SqlTemplate.cs

124 lines
4.2 KiB
C#
Raw Normal View History

2017-09-22 15:23:46 +02:00
using System;
2017-09-24 18:54:31 +02:00
using System.Collections;
2017-09-22 15:23:46 +02:00
using System.Collections.Generic;
using System.Linq;
using NPoco;
2017-09-22 18:28:21 +02:00
namespace Umbraco.Core.Persistence
2017-09-22 15:23:46 +02:00
{
public class SqlTemplate
{
2017-09-22 18:48:58 +02:00
private readonly ISqlContext _sqlContext;
2017-09-22 15:23:46 +02:00
private readonly string _sql;
2017-11-10 11:27:12 +01:00
private readonly Dictionary<int, object> _args;
// these are created in PocoToSqlExpressionVisitor
internal class TemplateArg
{
public TemplateArg(string name)
{
Name = name;
}
public string Name { get; }
public override string ToString()
{
return "@" + Name;
}
}
2017-09-22 15:23:46 +02:00
2017-09-22 18:48:58 +02:00
internal SqlTemplate(ISqlContext sqlContext, string sql, object[] args)
2017-09-22 15:23:46 +02:00
{
2017-09-22 18:28:21 +02:00
_sqlContext = sqlContext;
2017-09-22 15:23:46 +02:00
_sql = sql;
if (args.Length > 0)
2017-11-10 11:27:12 +01:00
_args = new Dictionary<int, object>();
2017-09-22 15:23:46 +02:00
for (var i = 0; i < args.Length; i++)
2017-11-10 11:27:12 +01:00
_args[i] = args[i];
2017-09-22 15:23:46 +02:00
}
2017-09-22 18:48:58 +02:00
public Sql<ISqlContext> Sql()
2017-09-22 15:23:46 +02:00
{
2017-09-22 18:48:58 +02:00
return new Sql<ISqlContext>(_sqlContext, _sql);
2017-09-22 15:23:46 +02:00
}
2017-11-10 11:27:12 +01:00
// must pass the args, all of them, in the proper order, faster
2017-09-22 18:48:58 +02:00
public Sql<ISqlContext> Sql(params object[] args)
2017-09-22 15:23:46 +02:00
{
2017-09-24 18:54:31 +02:00
// 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<ISqlContext>(_sqlContext, true, _sql);
var isBuilt = !args.Any(x => x is IEnumerable);
return new Sql<ISqlContext>(_sqlContext, isBuilt, _sql, args);
2017-09-22 15:23:46 +02:00
}
2017-11-10 11:27:12 +01:00
// can pass named args, not necessary all of them, slower
2017-09-22 15:23:46 +02:00
// so, not much different from what Where(...) does (ie reflection)
2017-09-22 18:48:58 +02:00
public Sql<ISqlContext> SqlNamed(object nargs)
2017-09-22 15:23:46 +02:00
{
2017-09-24 18:54:31 +02:00
var isBuilt = true;
2017-09-22 15:23:46 +02:00
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++)
{
2017-11-10 11:27:12 +01:00
object value;
if (_args[i] is TemplateArg templateArg)
{
if (!properties.TryGetValue(templateArg.Name, out value))
throw new InvalidOperationException($"Missing argument \"{templateArg.Name}\".");
properties.Remove(templateArg.Name);
}
else
{
value = _args[i];
}
2017-09-22 15:23:46 +02:00
args[i] = value;
2017-09-24 18:54:31 +02:00
// if value is enumerable then we'll need to expand arguments
if (value is IEnumerable)
isBuilt = false;
2017-09-22 15:23:46 +02:00
}
2017-09-24 18:54:31 +02:00
if (properties.Count > 0)
throw new InvalidOperationException($"Unknown argument{(properties.Count > 1 ? "s" : "")}: {string.Join(", ", properties.Keys)}");
return new Sql<ISqlContext>(_sqlContext, isBuilt, _sql, args);
}
internal void WriteToConsole()
{
2017-11-10 11:27:12 +01:00
new Sql<ISqlContext>(_sqlContext, _sql, _args.Values.ToArray()).WriteToConsole();
2017-09-24 18:54:31 +02:00
}
2017-11-15 08:53:20 +01:00
/// <summary>
/// Gets a named argument.
/// </summary>
2017-11-10 11:27:12 +01:00
public static object Arg(string name) => new TemplateArg(name);
2017-11-15 08:53:20 +01:00
/// <summary>
/// Gets a WHERE expression argument.
/// </summary>
public static T Arg<T>(string name) => default;
2017-09-24 18:54:31 +02:00
2017-11-15 08:53:20 +01:00
/// <summary>
/// Gets a WHERE IN expression argument.
/// </summary>
public static IEnumerable<T> ArgIn<T>(string name)
2017-09-24 18:54:31 +02:00
{
// don't return an empty enumerable, as it breaks NPoco
// fixme - should we cache these arrays?
return new[] { default (T) };
2017-09-22 15:23:46 +02:00
}
}
2017-09-23 10:08:18 +02:00
}