* Avoid doing multiple lookups in dictionaries, avoid doing string interpolation & adding single char strings to a StringBuilder, made some private/internal classes & some private methods static when possible, use FrozenSet for InvalidFileNameChars
* Avoid some array + list allocations & async methods and made some private methods static
* Avoid double lookup of XML attribute (and double null check) & avoid an unneeded lookup before writing to a dictionary
* Avoid some double lookups
# Conflicts:
# src/Umbraco.Core/Services/LocalizedTextService.cs
* Avoid double lookups
# Conflicts:
# src/Umbraco.Core/Services/LocalizedTextService.cs
* Avoid double lookups
* List AsSpan, also to trigger a new build that hopefully goes through
* Avoid concatting strings when using writer & more static
* Updated CollectionBenchmarks to show that ToArray isn't always the fastest & Lists can be iterated nearly as fast as arrays (and that ToList is nearly as fast as ToArray on IReadOnlyLists in .NET 8)
* Fix rebase
* Use explicit types ❤️ (I thought it was the other way round...)
# Conflicts:
# src/Umbraco.Core/Services/LocalizedTextService.cs
* Reduce number of lines in HtmlStringUtilities.Truncate to pass code quality analysis
* Avoid double lookups & allocating empty arrays
* Use native List Find instead of LINQ
132 lines
4.0 KiB
C#
132 lines
4.0 KiB
C#
using System.Collections;
|
|
using NPoco;
|
|
using Umbraco.Extensions;
|
|
|
|
namespace Umbraco.Cms.Infrastructure.Persistence;
|
|
|
|
public class SqlTemplate
|
|
{
|
|
private readonly Dictionary<int, object>? _args;
|
|
private readonly string _sql;
|
|
private readonly ISqlContext _sqlContext;
|
|
|
|
internal SqlTemplate(ISqlContext sqlContext, string sql, object[] args)
|
|
{
|
|
_sqlContext = sqlContext;
|
|
_sql = sql;
|
|
if (args.Length > 0)
|
|
{
|
|
_args = new Dictionary<int, object>();
|
|
}
|
|
|
|
for (var i = 0; i < args.Length; i++)
|
|
{
|
|
_args![i] = args[i];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a named argument.
|
|
/// </summary>
|
|
public static object Arg(string name) => new TemplateArg(name);
|
|
|
|
public Sql<ISqlContext> Sql() => new Sql<ISqlContext>(_sqlContext, _sql);
|
|
|
|
// must pass the args, all of them, in the proper order, faster
|
|
public Sql<ISqlContext> Sql(params object[] 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<ISqlContext>(_sqlContext, true, _sql);
|
|
}
|
|
|
|
var isBuilt = !Array.Exists(args, x => x is IEnumerable);
|
|
return new Sql<ISqlContext>(_sqlContext, isBuilt, _sql, args);
|
|
}
|
|
|
|
// can pass named args, not necessary all of them, slower
|
|
// so, not much different from what Where(...) does (ie reflection)
|
|
public Sql<ISqlContext> SqlNamed(object nargs)
|
|
{
|
|
var isBuilt = true;
|
|
var args = new object[_args?.Count ?? 0];
|
|
var properties = nargs.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(nargs));
|
|
for (var i = 0; i < _args?.Count; i++)
|
|
{
|
|
object? value;
|
|
if (_args[i] is TemplateArg templateArg)
|
|
{
|
|
if (!properties.TryGetValue(templateArg.Name ?? string.Empty, out value))
|
|
{
|
|
throw new InvalidOperationException($"Missing argument \"{templateArg.Name}\".");
|
|
}
|
|
|
|
properties.Remove(templateArg.Name!);
|
|
}
|
|
else
|
|
{
|
|
value = _args[i];
|
|
}
|
|
|
|
args[i] = value!;
|
|
|
|
// if value is enumerable then we'll need to expand arguments
|
|
if (value is IEnumerable)
|
|
{
|
|
isBuilt = false;
|
|
}
|
|
}
|
|
|
|
if (properties.Count > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Unknown argument{(properties.Count > 1 ? "s" : string.Empty)}: {string.Join(", ", properties.Keys)}");
|
|
}
|
|
|
|
return new Sql<ISqlContext>(_sqlContext, isBuilt, _sql, args);
|
|
}
|
|
|
|
internal string ToText()
|
|
{
|
|
var sql = new Sql<ISqlContext>(_sqlContext, _sql, _args?.Values.ToArray());
|
|
return sql.ToText();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a WHERE expression argument.
|
|
/// </summary>
|
|
public static T? Arg<T>(string name) => default;
|
|
|
|
/// <summary>
|
|
/// Gets a WHERE IN expression argument.
|
|
/// </summary>
|
|
public static IEnumerable<T?> ArgIn<T>(string name) =>
|
|
|
|
// don't return an empty enumerable, as it breaks NPoco
|
|
new[] { default(T) };
|
|
|
|
// these are created in PocoToSqlExpressionVisitor
|
|
internal sealed class TemplateArg
|
|
{
|
|
public TemplateArg(string? name) => Name = name;
|
|
|
|
public string? Name { get; }
|
|
|
|
public override string ToString() => "@" + Name;
|
|
}
|
|
}
|