Expand typed SQL DSL with predicates, subqueries and projections

This commit is contained in:
root
2026-02-27 08:34:16 +01:00
parent 8b1737a53b
commit 97ab4c1e15
12 changed files with 692 additions and 24 deletions

View File

@@ -7226,6 +7226,9 @@ namespace DynamORM
/// <summary>Add typed SQL DSL insert assignment.</summary>
IDynamicTypedInsertQueryBuilder<T> InsertSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory);
/// <summary>Add typed SQL DSL insert assignments from object projection.</summary>
IDynamicTypedInsertQueryBuilder<T> InsertSql(Func<TypedTableContext<T>, object> values);
}
/// <summary>Typed select query builder for mapped entities.</summary>
/// <typeparam name="T">Mapped entity type.</typeparam>
@@ -7327,6 +7330,9 @@ namespace DynamORM
/// <summary>Add typed SQL DSL assignment.</summary>
IDynamicTypedUpdateQueryBuilder<T> SetSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory);
/// <summary>Add typed SQL DSL assignments from object projection.</summary>
IDynamicTypedUpdateQueryBuilder<T> SetSql(Func<TypedTableContext<T>, object> values);
}
/// <summary>Dynamic update query builder interface.</summary>
/// <remarks>This interface it publicly available. Implementation should be hidden.</remarks>
@@ -10724,7 +10730,7 @@ namespace DynamORM
}
public IDynamicTypedDeleteQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
{
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName);
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName, RenderSubQuery);
if (string.IsNullOrEmpty(WhereCondition))
WhereCondition = condition;
else
@@ -10765,6 +10771,14 @@ namespace DynamORM
DynamicSchemaColumn? columnSchema = null;
return ParseConstant(value, Parameters, columnSchema);
}
private string RenderSubQuery(Builders.IDynamicSelectQueryBuilder query)
{
foreach (KeyValuePair<string, IParameter> item in query.Parameters)
if (!Parameters.ContainsKey(item.Key))
Parameters.Add(item.Key, item.Value);
return query.CommandText();
}
}
/// <summary>Typed wrapper over <see cref="DynamicInsertQueryBuilder"/> with property-to-column translation.</summary>
/// <typeparam name="T">Mapped entity type.</typeparam>
@@ -10795,12 +10809,30 @@ namespace DynamORM
public IDynamicTypedInsertQueryBuilder<T> InsertSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory)
{
string column = FixObjectName(TypedModifyHelper.GetMappedColumn(selector), onlyColumn: true);
string value = TypedModifyHelper.RenderExpression(valueFactory, null, RenderValue, Database.DecorateName);
string value = TypedModifyHelper.RenderExpression(valueFactory, null, RenderValue, Database.DecorateName, RenderSubQuery);
_columns = _columns == null ? column : string.Format("{0}, {1}", _columns, column);
_values = _values == null ? value : string.Format("{0}, {1}", _values, value);
return this;
}
public IDynamicTypedInsertQueryBuilder<T> InsertSql(Func<TypedTableContext<T>, object> values)
{
if (values == null)
throw new ArgumentNullException("values");
object data = values(new TypedTableContext<T>(null));
foreach (KeyValuePair<string, object> item in data.ToDictionary())
{
string column = FixObjectName(TypedModifyHelper.GetMappedColumnByName(typeof(T), item.Key), onlyColumn: true);
string value = (item.Value as TypedSqlExpression) != null
? TypedModifyHelper.RenderExpression<T>(u => (TypedSqlExpression)item.Value, null, RenderValue, Database.DecorateName, RenderSubQuery)
: RenderValue(item.Value);
_columns = _columns == null ? column : string.Format("{0}, {1}", _columns, column);
_values = _values == null ? value : string.Format("{0}, {1}", _values, value);
}
return this;
}
public new IDynamicTypedInsertQueryBuilder<T> Values(Func<dynamic, object> fn, params Func<dynamic, object>[] func)
{
base.Values(fn, func);
@@ -10824,6 +10856,14 @@ namespace DynamORM
DynamicSchemaColumn? columnSchema = null;
return ParseConstant(value, Parameters, columnSchema);
}
private string RenderSubQuery(Builders.IDynamicSelectQueryBuilder query)
{
foreach (KeyValuePair<string, IParameter> item in query.Parameters)
if (!Parameters.ContainsKey(item.Key))
Parameters.Add(item.Key, item.Value);
return query.CommandText();
}
}
/// <summary>Typed wrapper over <see cref="DynamicSelectQueryBuilder"/> with property-to-column translation.</summary>
/// <typeparam name="T">Mapped entity type.</typeparam>
@@ -10863,6 +10903,17 @@ namespace DynamORM
{
return _builder.Database.DecorateName(name);
}
public string RenderSubQuery(IDynamicSelectQueryBuilder query)
{
if (query == null)
throw new ArgumentNullException("query");
foreach (var item in query.Parameters)
if (!_builder.Parameters.ContainsKey(item.Key))
_builder.Parameters.Add(item.Key, item.Value);
return query.CommandText();
}
}
private readonly DynamicTypeMap _mapper;
@@ -11633,7 +11684,7 @@ namespace DynamORM
}
public IDynamicTypedUpdateQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
{
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName);
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName, RenderSubQuery);
if (string.IsNullOrEmpty(WhereCondition))
WhereCondition = condition;
else
@@ -11644,11 +11695,29 @@ namespace DynamORM
public IDynamicTypedUpdateQueryBuilder<T> SetSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory)
{
string column = FixObjectName(TypedModifyHelper.GetMappedColumn(selector), onlyColumn: true);
string value = TypedModifyHelper.RenderExpression(valueFactory, null, RenderValue, Database.DecorateName);
string value = TypedModifyHelper.RenderExpression(valueFactory, null, RenderValue, Database.DecorateName, RenderSubQuery);
string assignment = string.Format("{0} = {1}", column, value);
_columns = _columns == null ? assignment : string.Format("{0}, {1}", _columns, assignment);
return this;
}
public IDynamicTypedUpdateQueryBuilder<T> SetSql(Func<TypedTableContext<T>, object> values)
{
if (values == null)
throw new ArgumentNullException("values");
object data = values(new TypedTableContext<T>(null));
foreach (KeyValuePair<string, object> item in data.ToDictionary())
{
string column = FixObjectName(TypedModifyHelper.GetMappedColumnByName(typeof(T), item.Key), onlyColumn: true);
string value = (item.Value as TypedSqlExpression) != null
? TypedModifyHelper.RenderExpression<T>(u => (TypedSqlExpression)item.Value, null, RenderValue, Database.DecorateName, RenderSubQuery)
: RenderValue(item.Value);
string assignment = string.Format("{0} = {1}", column, value);
_columns = _columns == null ? assignment : string.Format("{0}, {1}", _columns, assignment);
}
return this;
}
public new IDynamicTypedUpdateQueryBuilder<T> Update(string column, object value)
{
base.Update(column, value);
@@ -11707,6 +11776,14 @@ namespace DynamORM
DynamicSchemaColumn? columnSchema = null;
return ParseConstant(value, Parameters, columnSchema);
}
private string RenderSubQuery(Builders.IDynamicSelectQueryBuilder query)
{
foreach (KeyValuePair<string, IParameter> item in query.Parameters)
if (!Parameters.ContainsKey(item.Key))
Parameters.Add(item.Key, item.Value);
return query.CommandText();
}
}
/// <summary>Update query builder.</summary>
internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
@@ -11996,12 +12073,14 @@ namespace DynamORM
private readonly Func<Type, string, string, Func<string, string>, string> _resolveColumn;
private readonly Func<object, string> _renderValue;
private readonly Func<string, string> _decorateName;
private readonly Func<IDynamicSelectQueryBuilder, string> _renderSubQuery;
public ModifyRenderContext(Func<Type, string, string, Func<string, string>, string> resolveColumn, Func<object, string> renderValue, Func<string, string> decorateName)
public ModifyRenderContext(Func<Type, string, string, Func<string, string>, string> resolveColumn, Func<object, string> renderValue, Func<string, string> decorateName, Func<IDynamicSelectQueryBuilder, string> renderSubQuery)
{
_resolveColumn = resolveColumn;
_renderValue = renderValue;
_decorateName = decorateName;
_renderSubQuery = renderSubQuery;
}
public string ResolveColumn(Type modelType, string memberName, string alias)
{
@@ -12015,6 +12094,10 @@ namespace DynamORM
{
return _decorateName(name);
}
public string RenderSubQuery(IDynamicSelectQueryBuilder query)
{
return _renderSubQuery(query);
}
}
public static string GetMappedColumn<T, TValue>(Expression<Func<T, TValue>> selector)
{
@@ -12036,24 +12119,26 @@ namespace DynamORM
Func<TypedTableContext<T>, TypedSqlPredicate> predicate,
string alias,
Func<object, string> renderValue,
Func<string, string> decorateName)
Func<string, string> decorateName,
Func<IDynamicSelectQueryBuilder, string> renderSubQuery)
{
if (predicate == null)
throw new ArgumentNullException("predicate");
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName);
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName, renderSubQuery);
return predicate(new TypedTableContext<T>(alias)).Render(context);
}
public static string RenderExpression<T>(
Func<TypedTableContext<T>, TypedSqlExpression> expression,
string alias,
Func<object, string> renderValue,
Func<string, string> decorateName)
Func<string, string> decorateName,
Func<IDynamicSelectQueryBuilder, string> renderSubQuery)
{
if (expression == null)
throw new ArgumentNullException("expression");
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName);
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName, renderSubQuery);
return expression(new TypedTableContext<T>(alias)).Render(context);
}
private static void ApplyWhereInternal(Type modelType, Action<string, DynamicColumn.CompareOperator, object> addCondition, Expression expression)
@@ -12120,7 +12205,7 @@ namespace DynamORM
? decorateName(mapped)
: string.Format("{0}.{1}", alias, decorateName(mapped));
}
private static string GetMappedColumnByName(Type modelType, string memberName)
internal static string GetMappedColumnByName(Type modelType, string memberName)
{
DynamicTypeMap mapper = DynamicMapperCache.GetMapper(modelType);
if (mapper == null)
@@ -15728,6 +15813,9 @@ namespace DynamORM
/// <summary>Decorate SQL identifier.</summary>
string DecorateName(string name);
/// <summary>Render subquery SQL and merge any parameters into current context.</summary>
string RenderSubQuery(Builders.IDynamicSelectQueryBuilder query);
}
/// <summary>Entry point for the typed SQL DSL.</summary>
public static class Sql
@@ -15767,6 +15855,71 @@ namespace DynamORM
{
return Func<T>("COALESCE", expressions);
}
/// <summary>Create SUM expression.</summary>
public static TypedSqlExpression<T> Sum<T>(TypedSqlExpression expression)
{
return Func<T>("SUM", expression);
}
/// <summary>Create AVG expression.</summary>
public static TypedSqlExpression<T> Avg<T>(TypedSqlExpression expression)
{
return Func<T>("AVG", expression);
}
/// <summary>Create MIN expression.</summary>
public static TypedSqlExpression<T> Min<T>(TypedSqlExpression expression)
{
return Func<T>("MIN", expression);
}
/// <summary>Create MAX expression.</summary>
public static TypedSqlExpression<T> Max<T>(TypedSqlExpression expression)
{
return Func<T>("MAX", expression);
}
/// <summary>Create ABS expression.</summary>
public static TypedSqlExpression<T> Abs<T>(TypedSqlExpression expression)
{
return Func<T>("ABS", expression);
}
/// <summary>Create UPPER expression.</summary>
public static TypedSqlExpression<string> Upper(TypedSqlExpression expression)
{
return Func<string>("UPPER", expression);
}
/// <summary>Create LOWER expression.</summary>
public static TypedSqlExpression<string> Lower(TypedSqlExpression expression)
{
return Func<string>("LOWER", expression);
}
/// <summary>Create TRIM expression.</summary>
public static TypedSqlExpression<string> Trim(TypedSqlExpression expression)
{
return Func<string>("TRIM", expression);
}
/// <summary>Create LENGTH expression.</summary>
public static TypedSqlExpression<int> Length(TypedSqlExpression expression)
{
return Func<int>("LENGTH", expression);
}
/// <summary>Create NULLIF expression.</summary>
public static TypedSqlExpression<T> NullIf<T>(TypedSqlExpression left, TypedSqlExpression right)
{
return Func<T>("NULLIF", left, right);
}
/// <summary>Create CURRENT_TIMESTAMP expression.</summary>
public static TypedSqlExpression<DateTime> CurrentTimestamp()
{
return Raw<DateTime>("CURRENT_TIMESTAMP");
}
/// <summary>Create scalar subquery expression.</summary>
public static TypedSqlExpression<T> SubQuery<T>(IDynamicSelectQueryBuilder query)
{
return new TypedSqlSubQueryExpression<T>(query);
}
/// <summary>Create EXISTS predicate.</summary>
public static TypedSqlPredicate Exists(IDynamicSelectQueryBuilder query)
{
return new TypedSqlExistsPredicate(query);
}
/// <summary>Create CASE expression builder.</summary>
public static TypedSqlCaseBuilder<T> Case<T>()
{
@@ -15861,6 +16014,26 @@ namespace DynamORM
{
return new TypedSqlUnaryPredicate(this, "IS NOT NULL");
}
/// <summary>LIKE predicate.</summary>
public TypedSqlPredicate Like(string pattern)
{
return new TypedSqlBinaryPredicate(this, "LIKE", Sql.Val(pattern));
}
/// <summary>IN predicate.</summary>
public TypedSqlPredicate In(params object[] values)
{
return new TypedSqlInPredicate(this, values);
}
/// <summary>IN predicate.</summary>
public TypedSqlPredicate In(IEnumerable values)
{
return new TypedSqlInPredicate(this, values);
}
/// <summary>BETWEEN predicate.</summary>
public TypedSqlPredicate Between(object lower, object upper)
{
return new TypedSqlBetweenPredicate(this, lower is TypedSqlExpression ? (TypedSqlExpression)lower : Sql.Val(lower), upper is TypedSqlExpression ? (TypedSqlExpression)upper : Sql.Val(upper));
}
}
/// <summary>Typed SQL expression.</summary>
public abstract class TypedSqlExpression<T> : TypedSqlExpression
@@ -15934,7 +16107,7 @@ namespace DynamORM
return context.ResolveColumn(_modelType, _memberName, _alias);
}
}
internal sealed class TypedSqlValueExpression<T> : TypedSqlExpression<T>
internal sealed class TypedSqlValueExpression<T> : TypedSqlExpression<T>, ITypedSqlNullValue
{
private readonly object _value;
@@ -15946,6 +16119,10 @@ namespace DynamORM
{
return context.RenderValue(_value);
}
public bool IsNullValue
{
get { return _value == null; }
}
}
internal sealed class TypedSqlRawExpression<T> : TypedSqlExpression<T>
{
@@ -16009,12 +16186,56 @@ namespace DynamORM
internal override string Render(ITypedSqlRenderContext context)
{
string op = _operator;
if (_right is TypedSqlValueExpression<object> && string.Equals(_right.Render(context), "NULL", StringComparison.OrdinalIgnoreCase))
TypedSqlValueExpression<object> objRight = _right as TypedSqlValueExpression<object>;
if ((objRight != null && objRight.IsNullValue) || (_right is ITypedSqlNullValue && ((ITypedSqlNullValue)_right).IsNullValue))
op = _operator == "=" ? "IS" : _operator == "<>" ? "IS NOT" : _operator;
return string.Format("({0} {1} {2})", _left.Render(context), op, _right.Render(context));
}
}
internal interface ITypedSqlNullValue
{
bool IsNullValue { get; }
}
internal sealed class TypedSqlInPredicate : TypedSqlPredicate
{
private readonly TypedSqlExpression _left;
private readonly IEnumerable _values;
internal TypedSqlInPredicate(TypedSqlExpression left, IEnumerable values)
{
_left = left;
_values = values;
}
internal override string Render(ITypedSqlRenderContext context)
{
List<string> rendered = new List<string>();
foreach (object value in _values)
rendered.Add((value as TypedSqlExpression ?? Sql.Val(value)).Render(context));
if (rendered.Count == 0)
return "(1 = 0)";
return string.Format("({0} IN({1}))", _left.Render(context), string.Join(", ", rendered.ToArray()));
}
}
internal sealed class TypedSqlBetweenPredicate : TypedSqlPredicate
{
private readonly TypedSqlExpression _left;
private readonly TypedSqlExpression _lower;
private readonly TypedSqlExpression _upper;
internal TypedSqlBetweenPredicate(TypedSqlExpression left, TypedSqlExpression lower, TypedSqlExpression upper)
{
_left = left;
_lower = lower;
_upper = upper;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("({0} BETWEEN {1} AND {2})", _left.Render(context), _lower.Render(context), _upper.Render(context));
}
}
internal sealed class TypedSqlCombinedPredicate : TypedSqlPredicate
{
private readonly TypedSqlPredicate _left;
@@ -16070,6 +16291,32 @@ namespace DynamORM
return string.Join(" ", items.ToArray());
}
}
internal sealed class TypedSqlSubQueryExpression<T> : TypedSqlExpression<T>
{
private readonly IDynamicSelectQueryBuilder _query;
internal TypedSqlSubQueryExpression(IDynamicSelectQueryBuilder query)
{
_query = query;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("({0})", context.RenderSubQuery(_query));
}
}
internal sealed class TypedSqlExistsPredicate : TypedSqlPredicate
{
private readonly IDynamicSelectQueryBuilder _query;
internal TypedSqlExistsPredicate(IDynamicSelectQueryBuilder query)
{
_query = query;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("(EXISTS ({0}))", context.RenderSubQuery(_query));
}
}
/// <summary>Typed table context used by the typed SQL DSL.</summary>
/// <typeparam name="T">Mapped entity type.</typeparam>
public sealed class TypedTableContext<T>