Files
Umbraco-CMS/src/Umbraco.Infrastructure/Persistence/SqlTemplate.cs
Henrik c64ec51305 Nonbreaking performance tweaks (#17106)
* 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
2025-01-31 10:31:06 +01:00

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;
}
}