Add experimental typed SQL DSL for typed builders
This commit is contained in:
@@ -10,6 +10,7 @@ using DynamORM.Builders;
|
||||
using DynamORM.Helpers.Dynamics;
|
||||
using DynamORM.Helpers;
|
||||
using DynamORM.Mapper;
|
||||
using DynamORM.TypedSql;
|
||||
using DynamORM.Validation;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@@ -7203,6 +7204,9 @@ namespace DynamORM
|
||||
/// <param name="predicate">Predicate to parse.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedDeleteQueryBuilder<T> Where(Expression<Func<T, bool>> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL where predicate.</summary>
|
||||
IDynamicTypedDeleteQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
}
|
||||
/// <summary>Typed insert query builder for mapped entities.</summary>
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
@@ -7219,6 +7223,9 @@ namespace DynamORM
|
||||
/// <param name="value">Mapped object value.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedInsertQueryBuilder<T> Insert(T value);
|
||||
|
||||
/// <summary>Add typed SQL DSL insert assignment.</summary>
|
||||
IDynamicTypedInsertQueryBuilder<T> InsertSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory);
|
||||
}
|
||||
/// <summary>Typed select query builder for mapped entities.</summary>
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
@@ -7278,6 +7285,21 @@ namespace DynamORM
|
||||
/// <param name="selectors">Additional selectors to parse.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedSelectQueryBuilder<T> OrderBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors);
|
||||
|
||||
/// <summary>Add typed SQL DSL select items.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> SelectSql(Func<TypedTableContext<T>, TypedSqlSelectable> selector, params Func<TypedTableContext<T>, TypedSqlSelectable>[] selectors);
|
||||
|
||||
/// <summary>Add typed SQL DSL where predicate.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL having predicate.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> HavingSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL group by expressions.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> GroupBySql(Func<TypedTableContext<T>, TypedSqlExpression> selector, params Func<TypedTableContext<T>, TypedSqlExpression>[] selectors);
|
||||
|
||||
/// <summary>Add typed SQL DSL order by expressions.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> OrderBySql(Func<TypedTableContext<T>, TypedSqlOrderExpression> selector, params Func<TypedTableContext<T>, TypedSqlOrderExpression>[] selectors);
|
||||
}
|
||||
/// <summary>Typed update query builder for mapped entities.</summary>
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
@@ -7299,6 +7321,12 @@ namespace DynamORM
|
||||
/// <param name="value">Mapped object value.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedUpdateQueryBuilder<T> Values(T value);
|
||||
|
||||
/// <summary>Add typed SQL DSL where predicate.</summary>
|
||||
IDynamicTypedUpdateQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL assignment.</summary>
|
||||
IDynamicTypedUpdateQueryBuilder<T> SetSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory);
|
||||
}
|
||||
/// <summary>Dynamic update query builder interface.</summary>
|
||||
/// <remarks>This interface it publicly available. Implementation should be hidden.</remarks>
|
||||
@@ -7475,6 +7503,9 @@ namespace DynamORM
|
||||
/// <summary>Gets raw ON condition.</summary>
|
||||
public string OnRawCondition { get; private set; }
|
||||
|
||||
/// <summary>Gets typed SQL DSL ON specification.</summary>
|
||||
public Func<TypedTableContext<TLeft>, TypedTableContext<TRight>, TypedSqlPredicate> OnSqlPredicate { get; private set; }
|
||||
|
||||
/// <summary>Sets join alias.</summary>
|
||||
public TypedJoinBuilder<TLeft, TRight> As(string alias)
|
||||
{
|
||||
@@ -7560,6 +7591,7 @@ namespace DynamORM
|
||||
|
||||
OnPredicate = predicate;
|
||||
OnRawCondition = null;
|
||||
OnSqlPredicate = null;
|
||||
return this;
|
||||
}
|
||||
/// <summary>Sets raw ON clause (without the ON keyword).</summary>
|
||||
@@ -7570,6 +7602,18 @@ namespace DynamORM
|
||||
|
||||
OnRawCondition = condition.Trim();
|
||||
OnPredicate = null;
|
||||
OnSqlPredicate = null;
|
||||
return this;
|
||||
}
|
||||
/// <summary>Sets typed SQL DSL ON predicate.</summary>
|
||||
public TypedJoinBuilder<TLeft, TRight> OnSql(Func<TypedTableContext<TLeft>, TypedTableContext<TRight>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
OnSqlPredicate = predicate;
|
||||
OnPredicate = null;
|
||||
OnRawCondition = null;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -8229,8 +8273,8 @@ namespace DynamORM
|
||||
/// <summary>Implementation of dynamic insert query builder.</summary>
|
||||
internal class DynamicInsertQueryBuilder : DynamicModifyBuilder, IDynamicInsertQueryBuilder
|
||||
{
|
||||
private string _columns;
|
||||
private string _values;
|
||||
protected string _columns;
|
||||
protected string _values;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DynamicInsertQueryBuilder"/> class.
|
||||
@@ -9338,11 +9382,11 @@ namespace DynamORM
|
||||
private int? _offset = null;
|
||||
private bool _distinct = false;
|
||||
|
||||
private string _select;
|
||||
protected string _select;
|
||||
private string _from;
|
||||
protected string _join;
|
||||
private string _groupby;
|
||||
private string _orderby;
|
||||
protected string _groupby;
|
||||
protected string _orderby;
|
||||
|
||||
#region IQueryWithHaving
|
||||
|
||||
@@ -10678,6 +10722,16 @@ namespace DynamORM
|
||||
TypedModifyHelper.ApplyWhere<T>((c, o, v) => base.Where(c, o, v), predicate);
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedDeleteQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName);
|
||||
if (string.IsNullOrEmpty(WhereCondition))
|
||||
WhereCondition = condition;
|
||||
else
|
||||
WhereCondition = string.Format("{0} AND {1}", WhereCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
public new IDynamicTypedDeleteQueryBuilder<T> Where(Func<dynamic, object> func)
|
||||
{
|
||||
base.Where(func);
|
||||
@@ -10703,6 +10757,14 @@ namespace DynamORM
|
||||
base.Where(conditions, schema);
|
||||
return this;
|
||||
}
|
||||
private string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return ParseConstant(value, Parameters, columnSchema);
|
||||
}
|
||||
}
|
||||
/// <summary>Typed wrapper over <see cref="DynamicInsertQueryBuilder"/> with property-to-column translation.</summary>
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
@@ -10730,6 +10792,15 @@ namespace DynamORM
|
||||
base.Insert(value);
|
||||
return this;
|
||||
}
|
||||
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);
|
||||
|
||||
_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);
|
||||
@@ -10745,11 +10816,54 @@ namespace DynamORM
|
||||
base.Insert(o);
|
||||
return this;
|
||||
}
|
||||
private string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return ParseConstant(value, Parameters, columnSchema);
|
||||
}
|
||||
}
|
||||
/// <summary>Typed wrapper over <see cref="DynamicSelectQueryBuilder"/> with property-to-column translation.</summary>
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
internal class DynamicTypedSelectQueryBuilder<T> : DynamicSelectQueryBuilder, IDynamicTypedSelectQueryBuilder<T>
|
||||
{
|
||||
private sealed class TypedSqlRenderContext : ITypedSqlRenderContext
|
||||
{
|
||||
private readonly DynamicTypedSelectQueryBuilder<T> _builder;
|
||||
|
||||
public TypedSqlRenderContext(DynamicTypedSelectQueryBuilder<T> builder)
|
||||
{
|
||||
_builder = builder;
|
||||
}
|
||||
public string ResolveColumn(Type modelType, string memberName, string alias)
|
||||
{
|
||||
DynamicTypeMap mapper = DynamicMapperCache.GetMapper(modelType);
|
||||
if (mapper == null)
|
||||
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", modelType.FullName));
|
||||
|
||||
string mappedColumn = mapper.PropertyMap.TryGetValue(memberName)
|
||||
?? mapper.PropertyMap.Where(x => string.Equals(x.Key, memberName, StringComparison.OrdinalIgnoreCase)).Select(x => x.Value).FirstOrDefault()
|
||||
?? memberName;
|
||||
|
||||
return string.IsNullOrEmpty(alias)
|
||||
? _builder.Database.DecorateName(mappedColumn)
|
||||
: string.Format("{0}.{1}", alias, _builder.Database.DecorateName(mappedColumn));
|
||||
}
|
||||
public string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return _builder.ParseConstant(value, _builder.Parameters, columnSchema);
|
||||
}
|
||||
public string DecorateName(string name)
|
||||
{
|
||||
return _builder.Database.DecorateName(name);
|
||||
}
|
||||
}
|
||||
private readonly DynamicTypeMap _mapper;
|
||||
|
||||
public DynamicTypedSelectQueryBuilder(DynamicDatabase db)
|
||||
@@ -10846,7 +10960,12 @@ namespace DynamORM
|
||||
if (SupportNoLock && spec.UseNoLock)
|
||||
joinExpr += " WITH(NOLOCK)";
|
||||
|
||||
if (!string.IsNullOrEmpty(spec.OnRawCondition))
|
||||
if (spec.OnSqlPredicate != null)
|
||||
{
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
joinExpr += string.Format(" ON {0}", spec.OnSqlPredicate(new TypedTableContext<T>(GetRootAliasOrTableName()), new TypedTableContext<TRight>(rightAlias)).Render(context));
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(spec.OnRawCondition))
|
||||
joinExpr += string.Format(" ON {0}", spec.OnRawCondition);
|
||||
|
||||
AppendJoinClause(joinExpr);
|
||||
@@ -10894,6 +11013,18 @@ namespace DynamORM
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedSelectQueryBuilder<T> SelectSql(Func<TypedTableContext<T>, TypedSqlSelectable> selector, params Func<TypedTableContext<T>, TypedSqlSelectable>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
AddSelectSqlSelector(selector);
|
||||
if (selectors != null)
|
||||
foreach (Func<TypedTableContext<T>, TypedSqlSelectable> item in selectors)
|
||||
AddSelectSqlSelector(item);
|
||||
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedSelectQueryBuilder<T> GroupBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
@@ -10911,6 +11042,18 @@ namespace DynamORM
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedSelectQueryBuilder<T> GroupBySql(Func<TypedTableContext<T>, TypedSqlExpression> selector, params Func<TypedTableContext<T>, TypedSqlExpression>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
AddGroupBySqlSelector(selector);
|
||||
if (selectors != null)
|
||||
foreach (Func<TypedTableContext<T>, TypedSqlExpression> item in selectors)
|
||||
AddGroupBySqlSelector(item);
|
||||
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedSelectQueryBuilder<T> OrderBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
@@ -10928,6 +11071,18 @@ namespace DynamORM
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedSelectQueryBuilder<T> OrderBySql(Func<TypedTableContext<T>, TypedSqlOrderExpression> selector, params Func<TypedTableContext<T>, TypedSqlOrderExpression>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
AddOrderBySqlSelector(selector);
|
||||
if (selectors != null)
|
||||
foreach (Func<TypedTableContext<T>, TypedSqlOrderExpression> item in selectors)
|
||||
AddOrderBySqlSelector(item);
|
||||
|
||||
return this;
|
||||
}
|
||||
public new IDynamicTypedSelectQueryBuilder<T> Top(int? top)
|
||||
{
|
||||
base.Top(top);
|
||||
@@ -10962,6 +11117,32 @@ namespace DynamORM
|
||||
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedSelectQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
string condition = RenderSqlPredicate(predicate);
|
||||
if (string.IsNullOrEmpty(WhereCondition))
|
||||
WhereCondition = condition;
|
||||
else
|
||||
WhereCondition = string.Format("{0} AND {1}", WhereCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedSelectQueryBuilder<T> HavingSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
string condition = RenderSqlPredicate(predicate);
|
||||
if (string.IsNullOrEmpty(HavingCondition))
|
||||
HavingCondition = condition;
|
||||
else
|
||||
HavingCondition = string.Format("{0} AND {1}", HavingCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
public new IDynamicTypedSelectQueryBuilder<T> Having(DynamicColumn column)
|
||||
{
|
||||
base.Having(column);
|
||||
@@ -11000,6 +11181,16 @@ namespace DynamORM
|
||||
((IDynamicSelectQueryBuilder)this).Select(x => parsed);
|
||||
}
|
||||
}
|
||||
private void AddSelectSqlSelector(Func<TypedTableContext<T>, TypedSqlSelectable> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
TypedSqlSelectable item = selector(new TypedTableContext<T>(GetRootAliasOrTableName()));
|
||||
string rendered = item.Render(context);
|
||||
_select = string.IsNullOrEmpty(_select) ? rendered : string.Format("{0}, {1}", _select, rendered);
|
||||
}
|
||||
private void AddGroupBySelector<TResult>(Expression<Func<T, TResult>> selector)
|
||||
{
|
||||
var body = UnwrapConvert(selector.Body);
|
||||
@@ -11018,6 +11209,16 @@ namespace DynamORM
|
||||
((IDynamicSelectQueryBuilder)this).GroupBy(x => parsed);
|
||||
}
|
||||
}
|
||||
private void AddGroupBySqlSelector(Func<TypedTableContext<T>, TypedSqlExpression> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
TypedSqlExpression item = selector(new TypedTableContext<T>(GetRootAliasOrTableName()));
|
||||
string rendered = item.Render(context);
|
||||
_groupby = string.IsNullOrEmpty(_groupby) ? rendered : string.Format("{0}, {1}", _groupby, rendered);
|
||||
}
|
||||
private void AddOrderBySelector<TResult>(Expression<Func<T, TResult>> selector)
|
||||
{
|
||||
var body = UnwrapConvert(selector.Body);
|
||||
@@ -11032,6 +11233,21 @@ namespace DynamORM
|
||||
string parsed = string.Format("{0} {1}", main, ascending ? "ASC" : "DESC");
|
||||
((IDynamicSelectQueryBuilder)this).OrderBy(x => parsed);
|
||||
}
|
||||
private void AddOrderBySqlSelector(Func<TypedTableContext<T>, TypedSqlOrderExpression> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
TypedSqlOrderExpression item = selector(new TypedTableContext<T>(GetRootAliasOrTableName()));
|
||||
string rendered = item.Render(context);
|
||||
_orderby = string.IsNullOrEmpty(_orderby) ? rendered : string.Format("{0}, {1}", _orderby, rendered);
|
||||
}
|
||||
private string RenderSqlPredicate(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
return predicate(new TypedTableContext<T>(GetRootAliasOrTableName())).Render(context);
|
||||
}
|
||||
private string ParseTypedCondition(Expression expression)
|
||||
{
|
||||
expression = UnwrapConvert(expression);
|
||||
@@ -11415,6 +11631,24 @@ namespace DynamORM
|
||||
base.Values(value);
|
||||
return this;
|
||||
}
|
||||
public IDynamicTypedUpdateQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName);
|
||||
if (string.IsNullOrEmpty(WhereCondition))
|
||||
WhereCondition = condition;
|
||||
else
|
||||
WhereCondition = string.Format("{0} AND {1}", WhereCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
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 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);
|
||||
@@ -11465,11 +11699,19 @@ namespace DynamORM
|
||||
base.Where(conditions, schema);
|
||||
return this;
|
||||
}
|
||||
private string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return ParseConstant(value, Parameters, columnSchema);
|
||||
}
|
||||
}
|
||||
/// <summary>Update query builder.</summary>
|
||||
internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
|
||||
{
|
||||
private string _columns;
|
||||
protected string _columns;
|
||||
|
||||
internal DynamicUpdateQueryBuilder(DynamicDatabase db)
|
||||
: base(db)
|
||||
@@ -11749,6 +11991,31 @@ namespace DynamORM
|
||||
/// <summary>Helper methods for typed modify builders.</summary>
|
||||
internal static class TypedModifyHelper
|
||||
{
|
||||
private sealed class ModifyRenderContext : ITypedSqlRenderContext
|
||||
{
|
||||
private readonly Func<Type, string, string, Func<string, string>, string> _resolveColumn;
|
||||
private readonly Func<object, string> _renderValue;
|
||||
private readonly Func<string, string> _decorateName;
|
||||
|
||||
public ModifyRenderContext(Func<Type, string, string, Func<string, string>, string> resolveColumn, Func<object, string> renderValue, Func<string, string> decorateName)
|
||||
{
|
||||
_resolveColumn = resolveColumn;
|
||||
_renderValue = renderValue;
|
||||
_decorateName = decorateName;
|
||||
}
|
||||
public string ResolveColumn(Type modelType, string memberName, string alias)
|
||||
{
|
||||
return _resolveColumn(modelType, memberName, alias, _decorateName);
|
||||
}
|
||||
public string RenderValue(object value)
|
||||
{
|
||||
return _renderValue(value);
|
||||
}
|
||||
public string DecorateName(string name)
|
||||
{
|
||||
return _decorateName(name);
|
||||
}
|
||||
}
|
||||
public static string GetMappedColumn<T, TValue>(Expression<Func<T, TValue>> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
@@ -11765,6 +12032,30 @@ namespace DynamORM
|
||||
|
||||
ApplyWhereInternal(typeof(T), addCondition, predicate.Body);
|
||||
}
|
||||
public static string RenderPredicate<T>(
|
||||
Func<TypedTableContext<T>, TypedSqlPredicate> predicate,
|
||||
string alias,
|
||||
Func<object, string> renderValue,
|
||||
Func<string, string> decorateName)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName);
|
||||
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)
|
||||
{
|
||||
if (expression == null)
|
||||
throw new ArgumentNullException("expression");
|
||||
|
||||
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName);
|
||||
return expression(new TypedTableContext<T>(alias)).Render(context);
|
||||
}
|
||||
private static void ApplyWhereInternal(Type modelType, Action<string, DynamicColumn.CompareOperator, object> addCondition, Expression expression)
|
||||
{
|
||||
expression = UnwrapConvert(expression);
|
||||
@@ -11822,6 +12113,23 @@ namespace DynamORM
|
||||
.FirstOrDefault()
|
||||
?? member.Member.Name;
|
||||
}
|
||||
private static string ResolveColumn(Type modelType, string memberName, string alias, Func<string, string> decorateName)
|
||||
{
|
||||
string mapped = GetMappedColumnByName(modelType, memberName);
|
||||
return string.IsNullOrEmpty(alias)
|
||||
? decorateName(mapped)
|
||||
: string.Format("{0}.{1}", alias, decorateName(mapped));
|
||||
}
|
||||
private static string GetMappedColumnByName(Type modelType, string memberName)
|
||||
{
|
||||
DynamicTypeMap mapper = DynamicMapperCache.GetMapper(modelType);
|
||||
if (mapper == null)
|
||||
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", modelType.FullName));
|
||||
|
||||
return mapper.PropertyMap.TryGetValue(memberName)
|
||||
?? mapper.PropertyMap.Where(x => string.Equals(x.Key, memberName, StringComparison.OrdinalIgnoreCase)).Select(x => x.Value).FirstOrDefault()
|
||||
?? memberName;
|
||||
}
|
||||
private static Expression UnwrapConvert(Expression expression)
|
||||
{
|
||||
while (expression is UnaryExpression &&
|
||||
@@ -15407,6 +15715,389 @@ namespace DynamORM
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace TypedSql
|
||||
{
|
||||
/// <summary>Render context used by typed SQL DSL nodes.</summary>
|
||||
public interface ITypedSqlRenderContext
|
||||
{
|
||||
/// <summary>Resolve mapped column for given model member.</summary>
|
||||
string ResolveColumn(Type modelType, string memberName, string alias);
|
||||
|
||||
/// <summary>Render value as SQL parameter or literal fragment.</summary>
|
||||
string RenderValue(object value);
|
||||
|
||||
/// <summary>Decorate SQL identifier.</summary>
|
||||
string DecorateName(string name);
|
||||
}
|
||||
/// <summary>Entry point for the typed SQL DSL.</summary>
|
||||
public static class Sql
|
||||
{
|
||||
/// <summary>Create parameterized value expression.</summary>
|
||||
public static TypedSqlExpression<T> Val<T>(T value)
|
||||
{
|
||||
return new TypedSqlValueExpression<T>(value);
|
||||
}
|
||||
/// <summary>Create parameterized value expression.</summary>
|
||||
public static TypedSqlExpression<object> Val(object value)
|
||||
{
|
||||
return new TypedSqlValueExpression<object>(value);
|
||||
}
|
||||
/// <summary>Create raw SQL expression.</summary>
|
||||
public static TypedSqlExpression<T> Raw<T>(string sql)
|
||||
{
|
||||
return new TypedSqlRawExpression<T>(sql);
|
||||
}
|
||||
/// <summary>Create generic function call.</summary>
|
||||
public static TypedSqlExpression<T> Func<T>(string name, params TypedSqlExpression[] arguments)
|
||||
{
|
||||
return new TypedSqlFunctionExpression<T>(name, arguments);
|
||||
}
|
||||
/// <summary>Create COUNT(*) expression.</summary>
|
||||
public static TypedSqlExpression<int> Count()
|
||||
{
|
||||
return Raw<int>("COUNT(*)");
|
||||
}
|
||||
/// <summary>Create COUNT(expr) expression.</summary>
|
||||
public static TypedSqlExpression<int> Count(TypedSqlExpression expression)
|
||||
{
|
||||
return Func<int>("COUNT", expression);
|
||||
}
|
||||
/// <summary>Create COALESCE expression.</summary>
|
||||
public static TypedSqlExpression<T> Coalesce<T>(params TypedSqlExpression[] expressions)
|
||||
{
|
||||
return Func<T>("COALESCE", expressions);
|
||||
}
|
||||
/// <summary>Create CASE expression builder.</summary>
|
||||
public static TypedSqlCaseBuilder<T> Case<T>()
|
||||
{
|
||||
return new TypedSqlCaseBuilder<T>();
|
||||
}
|
||||
}
|
||||
/// <summary>Builder for CASE expressions.</summary>
|
||||
/// <typeparam name="T">Result type.</typeparam>
|
||||
public sealed class TypedSqlCaseBuilder<T>
|
||||
{
|
||||
private readonly IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> _cases = new List<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>>();
|
||||
|
||||
/// <summary>Add WHEN ... THEN ... clause.</summary>
|
||||
public TypedSqlCaseBuilder<T> When(TypedSqlPredicate predicate, object value)
|
||||
{
|
||||
_cases.Add(new KeyValuePair<TypedSqlPredicate, TypedSqlExpression>(
|
||||
predicate,
|
||||
value as TypedSqlExpression ?? Sql.Val(value)));
|
||||
return this;
|
||||
}
|
||||
/// <summary>Finalize CASE expression with ELSE clause.</summary>
|
||||
public TypedSqlExpression<T> Else(object value)
|
||||
{
|
||||
return new TypedSqlCaseExpression<T>(_cases, value as TypedSqlExpression ?? Sql.Val(value));
|
||||
}
|
||||
/// <summary>Finalize CASE expression without ELSE clause.</summary>
|
||||
public TypedSqlExpression<T> End()
|
||||
{
|
||||
return new TypedSqlCaseExpression<T>(_cases, null);
|
||||
}
|
||||
}
|
||||
/// <summary>Base selectable SQL fragment for the typed DSL.</summary>
|
||||
public abstract class TypedSqlSelectable
|
||||
{
|
||||
internal abstract string Render(ITypedSqlRenderContext context);
|
||||
}
|
||||
/// <summary>Base SQL expression for the typed DSL.</summary>
|
||||
public abstract class TypedSqlExpression : TypedSqlSelectable
|
||||
{
|
||||
/// <summary>Alias this expression in SELECT clause.</summary>
|
||||
public TypedSqlAliasedExpression As(string alias)
|
||||
{
|
||||
return new TypedSqlAliasedExpression(this, alias);
|
||||
}
|
||||
/// <summary>Order ascending.</summary>
|
||||
public TypedSqlOrderExpression Asc()
|
||||
{
|
||||
return new TypedSqlOrderExpression(this, true);
|
||||
}
|
||||
/// <summary>Order descending.</summary>
|
||||
public TypedSqlOrderExpression Desc()
|
||||
{
|
||||
return new TypedSqlOrderExpression(this, false);
|
||||
}
|
||||
/// <summary>Equality predicate.</summary>
|
||||
public TypedSqlPredicate Eq(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
/// <summary>Inequality predicate.</summary>
|
||||
public TypedSqlPredicate NotEq(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "<>", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
/// <summary>Greater-than predicate.</summary>
|
||||
public TypedSqlPredicate Gt(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, ">", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
/// <summary>Greater-than-or-equal predicate.</summary>
|
||||
public TypedSqlPredicate Gte(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, ">=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
/// <summary>Less-than predicate.</summary>
|
||||
public TypedSqlPredicate Lt(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "<", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
/// <summary>Less-than-or-equal predicate.</summary>
|
||||
public TypedSqlPredicate Lte(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "<=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
/// <summary>IS NULL predicate.</summary>
|
||||
public TypedSqlPredicate IsNull()
|
||||
{
|
||||
return new TypedSqlUnaryPredicate(this, "IS NULL");
|
||||
}
|
||||
/// <summary>IS NOT NULL predicate.</summary>
|
||||
public TypedSqlPredicate IsNotNull()
|
||||
{
|
||||
return new TypedSqlUnaryPredicate(this, "IS NOT NULL");
|
||||
}
|
||||
}
|
||||
/// <summary>Typed SQL expression.</summary>
|
||||
public abstract class TypedSqlExpression<T> : TypedSqlExpression
|
||||
{
|
||||
}
|
||||
/// <summary>Typed SQL predicate expression.</summary>
|
||||
public abstract class TypedSqlPredicate : TypedSqlExpression<bool>
|
||||
{
|
||||
/// <summary>Combine with AND.</summary>
|
||||
public TypedSqlPredicate And(TypedSqlPredicate right)
|
||||
{
|
||||
return new TypedSqlCombinedPredicate(this, "AND", right);
|
||||
}
|
||||
/// <summary>Combine with OR.</summary>
|
||||
public TypedSqlPredicate Or(TypedSqlPredicate right)
|
||||
{
|
||||
return new TypedSqlCombinedPredicate(this, "OR", right);
|
||||
}
|
||||
/// <summary>Negate predicate.</summary>
|
||||
public TypedSqlPredicate Not()
|
||||
{
|
||||
return new TypedSqlNegatedPredicate(this);
|
||||
}
|
||||
}
|
||||
/// <summary>Aliased SQL expression.</summary>
|
||||
public sealed class TypedSqlAliasedExpression : TypedSqlSelectable
|
||||
{
|
||||
private readonly TypedSqlExpression _expression;
|
||||
private readonly string _alias;
|
||||
|
||||
internal TypedSqlAliasedExpression(TypedSqlExpression expression, string alias)
|
||||
{
|
||||
_expression = expression;
|
||||
_alias = alias;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("{0} AS {1}", _expression.Render(context), context.DecorateName(_alias));
|
||||
}
|
||||
}
|
||||
/// <summary>Ordered SQL expression.</summary>
|
||||
public sealed class TypedSqlOrderExpression : TypedSqlSelectable
|
||||
{
|
||||
private readonly TypedSqlExpression _expression;
|
||||
private readonly bool _ascending;
|
||||
|
||||
internal TypedSqlOrderExpression(TypedSqlExpression expression, bool ascending)
|
||||
{
|
||||
_expression = expression;
|
||||
_ascending = ascending;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("{0} {1}", _expression.Render(context), _ascending ? "ASC" : "DESC");
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlColumnExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly Type _modelType;
|
||||
private readonly string _memberName;
|
||||
private readonly string _alias;
|
||||
|
||||
internal TypedSqlColumnExpression(Type modelType, string memberName, string alias)
|
||||
{
|
||||
_modelType = modelType;
|
||||
_memberName = memberName;
|
||||
_alias = alias;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return context.ResolveColumn(_modelType, _memberName, _alias);
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlValueExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly object _value;
|
||||
|
||||
internal TypedSqlValueExpression(object value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return context.RenderValue(_value);
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlRawExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly string _sql;
|
||||
|
||||
internal TypedSqlRawExpression(string sql)
|
||||
{
|
||||
_sql = sql;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return _sql;
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlFunctionExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly IList<TypedSqlExpression> _arguments;
|
||||
|
||||
internal TypedSqlFunctionExpression(string name, params TypedSqlExpression[] arguments)
|
||||
{
|
||||
_name = name;
|
||||
_arguments = arguments ?? new TypedSqlExpression[0];
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
List<string> rendered = new List<string>();
|
||||
foreach (TypedSqlExpression argument in _arguments)
|
||||
rendered.Add(argument.Render(context));
|
||||
|
||||
return string.Format("{0}({1})", _name, string.Join(", ", rendered.ToArray()));
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlUnaryPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlExpression _expression;
|
||||
private readonly string _operator;
|
||||
|
||||
internal TypedSqlUnaryPredicate(TypedSqlExpression expression, string op)
|
||||
{
|
||||
_expression = expression;
|
||||
_operator = op;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("({0} {1})", _expression.Render(context), _operator);
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlBinaryPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlExpression _left;
|
||||
private readonly string _operator;
|
||||
private readonly TypedSqlExpression _right;
|
||||
|
||||
internal TypedSqlBinaryPredicate(TypedSqlExpression left, string op, TypedSqlExpression right)
|
||||
{
|
||||
_left = left;
|
||||
_operator = op;
|
||||
_right = right;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
string op = _operator;
|
||||
if (_right is TypedSqlValueExpression<object> && string.Equals(_right.Render(context), "NULL", StringComparison.OrdinalIgnoreCase))
|
||||
op = _operator == "=" ? "IS" : _operator == "<>" ? "IS NOT" : _operator;
|
||||
|
||||
return string.Format("({0} {1} {2})", _left.Render(context), op, _right.Render(context));
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlCombinedPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlPredicate _left;
|
||||
private readonly string _operator;
|
||||
private readonly TypedSqlPredicate _right;
|
||||
|
||||
internal TypedSqlCombinedPredicate(TypedSqlPredicate left, string op, TypedSqlPredicate right)
|
||||
{
|
||||
_left = left;
|
||||
_operator = op;
|
||||
_right = right;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("({0} {1} {2})", _left.Render(context), _operator, _right.Render(context));
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlNegatedPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlPredicate _predicate;
|
||||
|
||||
internal TypedSqlNegatedPredicate(TypedSqlPredicate predicate)
|
||||
{
|
||||
_predicate = predicate;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("(NOT {0})", _predicate.Render(context));
|
||||
}
|
||||
}
|
||||
internal sealed class TypedSqlCaseExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> _cases;
|
||||
private readonly TypedSqlExpression _elseExpression;
|
||||
|
||||
internal TypedSqlCaseExpression(IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> cases, TypedSqlExpression elseExpression)
|
||||
{
|
||||
_cases = cases;
|
||||
_elseExpression = elseExpression;
|
||||
}
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
List<string> items = new List<string>();
|
||||
items.Add("CASE");
|
||||
|
||||
foreach (KeyValuePair<TypedSqlPredicate, TypedSqlExpression> item in _cases)
|
||||
items.Add(string.Format("WHEN {0} THEN {1}", item.Key.Render(context), item.Value.Render(context)));
|
||||
|
||||
if (_elseExpression != null)
|
||||
items.Add(string.Format("ELSE {0}", _elseExpression.Render(context)));
|
||||
|
||||
items.Add("END");
|
||||
return string.Join(" ", items.ToArray());
|
||||
}
|
||||
}
|
||||
/// <summary>Typed table context used by the typed SQL DSL.</summary>
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
public sealed class TypedTableContext<T>
|
||||
{
|
||||
internal TypedTableContext(string alias)
|
||||
{
|
||||
Alias = alias;
|
||||
}
|
||||
/// <summary>Gets table alias used by the current query.</summary>
|
||||
public string Alias { get; private set; }
|
||||
|
||||
/// <summary>Creates a mapped column expression.</summary>
|
||||
public TypedSqlExpression<TValue> Col<TValue>(Expression<Func<T, TValue>> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
MemberExpression member = selector.Body as MemberExpression;
|
||||
if (member == null && selector.Body is UnaryExpression)
|
||||
member = ((UnaryExpression)selector.Body).Operand as MemberExpression;
|
||||
|
||||
if (member == null)
|
||||
throw new NotSupportedException(string.Format("Column selector must target a mapped property: {0}", selector));
|
||||
|
||||
return new TypedSqlColumnExpression<TValue>(typeof(T), member.Member.Name, Alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Validation
|
||||
{
|
||||
/// <summary>Required attribute can be used to validate fields in objects using mapper class.</summary>
|
||||
|
||||
119
DynamORM.Tests/TypedSql/TypedSqlDslTests.cs
Normal file
119
DynamORM.Tests/TypedSql/TypedSqlDslTests.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using DynamORM.Tests.Helpers;
|
||||
using DynamORM.TypedSql;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace DynamORM.Tests.TypedSql
|
||||
{
|
||||
[TestFixture]
|
||||
public class TypedSqlDslTests : TestsBase
|
||||
{
|
||||
private static string NormalizeSql(string sql)
|
||||
{
|
||||
int index = 0;
|
||||
return Regex.Replace(sql, @"\[\$[^\]]+\]", m => string.Format("[${0}]", index++));
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
CreateTestDatabase();
|
||||
CreateDynamicDatabase(
|
||||
DynamicDatabaseOptions.SingleConnection |
|
||||
DynamicDatabaseOptions.SingleTransaction |
|
||||
DynamicDatabaseOptions.SupportLimitOffset |
|
||||
DynamicDatabaseOptions.SupportNoLock);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
DestroyDynamicDatabase();
|
||||
DestroyTestDatabase();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectSqlWithFunctionsAndOrdering()
|
||||
{
|
||||
var cmd = Database.FromTyped<TypedFluentUser>("u")
|
||||
.SelectSql(
|
||||
u => Sql.Count().As("cnt"),
|
||||
u => Sql.Coalesce<string>(u.Col(x => x.Code), Sql.Val("N/A")).As("code_value"))
|
||||
.GroupBySql(u => Sql.Coalesce<string>(u.Col(x => x.Code), Sql.Val("N/A")))
|
||||
.HavingSql(u => Sql.Count().Gt(1))
|
||||
.OrderBySql(u => Sql.Count().Desc());
|
||||
|
||||
Assert.AreEqual(
|
||||
"SELECT COUNT(*) AS \"cnt\", COALESCE(u.\"user_code\", [$0]) AS \"code_value\" FROM \"sample_users\" AS u GROUP BY COALESCE(u.\"user_code\", [$1]) HAVING (COUNT(*) > [$2]) ORDER BY COUNT(*) DESC",
|
||||
NormalizeSql(cmd.CommandText()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectSqlWithCase()
|
||||
{
|
||||
var cmd = Database.FromTyped<TypedFluentUser>("u")
|
||||
.SelectSql(u => Sql.Case<string>()
|
||||
.When(u.Col(x => x.Code).Eq("A"), "Alpha")
|
||||
.When(u.Col(x => x.Code).Eq("B"), "Beta")
|
||||
.Else("Other")
|
||||
.As("category"));
|
||||
|
||||
Assert.AreEqual(
|
||||
"SELECT CASE WHEN (u.\"user_code\" = [$0]) THEN [$1] WHEN (u.\"user_code\" = [$2]) THEN [$3] ELSE [$4] END AS \"category\" FROM \"sample_users\" AS u",
|
||||
NormalizeSql(cmd.CommandText()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJoinOnSql()
|
||||
{
|
||||
var cmd = Database.FromTyped<TypedFluentUser>("u")
|
||||
.Join<TypedFluentUser>(j => j.Left().As("x").OnSql((l, r) => l.Col(a => a.Id).Eq(r.Col(a => a.Id)).And(r.Col(a => a.Code).IsNotNull())))
|
||||
.SelectSql(u => u.Col(x => x.Id));
|
||||
|
||||
Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u LEFT JOIN \"sample_users\" AS x ON ((u.\"id_user\" = x.\"id_user\") AND (x.\"user_code\" IS NOT NULL))",
|
||||
cmd.CommandText());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUpdateSetSqlAndWhereSql()
|
||||
{
|
||||
var cmd = Database.UpdateTyped<Users>()
|
||||
.SetSql(u => u.Code, u => Sql.Coalesce<string>(Sql.Val("900"), u.Col(x => x.Code)))
|
||||
.WhereSql(u => u.Col(x => x.Id).Eq(1).And(u.Col(x => x.Code).IsNotNull()));
|
||||
|
||||
Assert.AreEqual(
|
||||
"UPDATE \"sample_users\" SET \"code\" = COALESCE([$0], \"code\") WHERE ((\"id\" = [$1]) AND (\"code\" IS NOT NULL))",
|
||||
NormalizeSql(cmd.CommandText()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInsertSqlSupportsCase()
|
||||
{
|
||||
var cmd = Database.InsertTyped<Users>()
|
||||
.InsertSql(u => u.Code, u => Sql.Coalesce<string>(Sql.Val("901"), Sql.Val("fallback")))
|
||||
.InsertSql(u => u.First, u => Sql.Case<string>().When(Sql.Val(1).Eq(1), "Typed").Else("Other"));
|
||||
|
||||
Assert.AreEqual(
|
||||
"INSERT INTO \"sample_users\" (\"code\", \"first\") VALUES (COALESCE([$0], [$1]), CASE WHEN ([$2] = [$3]) THEN [$4] ELSE [$5] END)",
|
||||
NormalizeSql(cmd.CommandText()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteWhereSql()
|
||||
{
|
||||
var cmd = Database.DeleteTyped<Users>()
|
||||
.WhereSql(u => u.Col(x => x.Id).Eq(2).And(u.Col(x => x.Code).NotEq("X")));
|
||||
|
||||
Assert.AreEqual(
|
||||
"DELETE FROM \"sample_users\" WHERE ((\"id\" = [$0]) AND (\"code\" <> [$1]))",
|
||||
NormalizeSql(cmd.CommandText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders
|
||||
{
|
||||
@@ -17,5 +18,8 @@ namespace DynamORM.Builders
|
||||
/// <param name="predicate">Predicate to parse.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedDeleteQueryBuilder<T> Where(Expression<Func<T, bool>> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL where predicate.</summary>
|
||||
IDynamicTypedDeleteQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders
|
||||
{
|
||||
@@ -24,5 +25,8 @@ namespace DynamORM.Builders
|
||||
/// <param name="value">Mapped object value.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedInsertQueryBuilder<T> Insert(T value);
|
||||
|
||||
/// <summary>Add typed SQL DSL insert assignment.</summary>
|
||||
IDynamicTypedInsertQueryBuilder<T> InsertSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders
|
||||
{
|
||||
@@ -89,5 +90,20 @@ namespace DynamORM.Builders
|
||||
/// <param name="selectors">Additional selectors to parse.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedSelectQueryBuilder<T> OrderBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors);
|
||||
|
||||
/// <summary>Add typed SQL DSL select items.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> SelectSql(Func<TypedTableContext<T>, TypedSqlSelectable> selector, params Func<TypedTableContext<T>, TypedSqlSelectable>[] selectors);
|
||||
|
||||
/// <summary>Add typed SQL DSL where predicate.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL having predicate.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> HavingSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL group by expressions.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> GroupBySql(Func<TypedTableContext<T>, TypedSqlExpression> selector, params Func<TypedTableContext<T>, TypedSqlExpression>[] selectors);
|
||||
|
||||
/// <summary>Add typed SQL DSL order by expressions.</summary>
|
||||
IDynamicTypedSelectQueryBuilder<T> OrderBySql(Func<TypedTableContext<T>, TypedSqlOrderExpression> selector, params Func<TypedTableContext<T>, TypedSqlOrderExpression>[] selectors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders
|
||||
{
|
||||
@@ -29,5 +30,11 @@ namespace DynamORM.Builders
|
||||
/// <param name="value">Mapped object value.</param>
|
||||
/// <returns>Builder instance.</returns>
|
||||
IDynamicTypedUpdateQueryBuilder<T> Values(T value);
|
||||
|
||||
/// <summary>Add typed SQL DSL where predicate.</summary>
|
||||
IDynamicTypedUpdateQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate);
|
||||
|
||||
/// <summary>Add typed SQL DSL assignment.</summary>
|
||||
IDynamicTypedUpdateQueryBuilder<T> SetSql<TValue>(Expression<Func<T, TValue>> selector, Func<TypedTableContext<T>, TypedSqlExpression> valueFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace DynamORM.Builders.Implementation
|
||||
/// <summary>Implementation of dynamic insert query builder.</summary>
|
||||
internal class DynamicInsertQueryBuilder : DynamicModifyBuilder, IDynamicInsertQueryBuilder
|
||||
{
|
||||
private string _columns;
|
||||
private string _values;
|
||||
protected string _columns;
|
||||
protected string _values;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DynamicInsertQueryBuilder"/> class.
|
||||
@@ -221,4 +221,4 @@ namespace DynamORM.Builders.Implementation
|
||||
|
||||
#endregion IExtendedDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ namespace DynamORM.Builders.Implementation
|
||||
private int? _offset = null;
|
||||
private bool _distinct = false;
|
||||
|
||||
private string _select;
|
||||
private string _from;
|
||||
protected string _select;
|
||||
private string _from;
|
||||
protected string _join;
|
||||
private string _groupby;
|
||||
private string _orderby;
|
||||
protected string _groupby;
|
||||
protected string _orderby;
|
||||
|
||||
#region IQueryWithHaving
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.Builders.Extensions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders.Implementation
|
||||
{
|
||||
@@ -34,6 +35,17 @@ namespace DynamORM.Builders.Implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedDeleteQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName);
|
||||
if (string.IsNullOrEmpty(WhereCondition))
|
||||
WhereCondition = condition;
|
||||
else
|
||||
WhereCondition = string.Format("{0} AND {1}", WhereCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public new IDynamicTypedDeleteQueryBuilder<T> Where(Func<dynamic, object> func)
|
||||
{
|
||||
base.Where(func);
|
||||
@@ -63,5 +75,14 @@ namespace DynamORM.Builders.Implementation
|
||||
base.Where(conditions, schema);
|
||||
return this;
|
||||
}
|
||||
|
||||
private string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return ParseConstant(value, Parameters, columnSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.Builders.Extensions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders.Implementation
|
||||
{
|
||||
@@ -40,6 +41,16 @@ namespace DynamORM.Builders.Implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
_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);
|
||||
@@ -57,5 +68,14 @@ namespace DynamORM.Builders.Implementation
|
||||
base.Insert(o);
|
||||
return this;
|
||||
}
|
||||
|
||||
private string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return ParseConstant(value, Parameters, columnSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using DynamORM.Helpers;
|
||||
using DynamORM.Mapper;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders.Implementation
|
||||
{
|
||||
@@ -41,6 +42,45 @@ namespace DynamORM.Builders.Implementation
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
internal class DynamicTypedSelectQueryBuilder<T> : DynamicSelectQueryBuilder, IDynamicTypedSelectQueryBuilder<T>
|
||||
{
|
||||
private sealed class TypedSqlRenderContext : ITypedSqlRenderContext
|
||||
{
|
||||
private readonly DynamicTypedSelectQueryBuilder<T> _builder;
|
||||
|
||||
public TypedSqlRenderContext(DynamicTypedSelectQueryBuilder<T> builder)
|
||||
{
|
||||
_builder = builder;
|
||||
}
|
||||
|
||||
public string ResolveColumn(Type modelType, string memberName, string alias)
|
||||
{
|
||||
DynamicTypeMap mapper = DynamicMapperCache.GetMapper(modelType);
|
||||
if (mapper == null)
|
||||
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", modelType.FullName));
|
||||
|
||||
string mappedColumn = mapper.PropertyMap.TryGetValue(memberName)
|
||||
?? mapper.PropertyMap.Where(x => string.Equals(x.Key, memberName, StringComparison.OrdinalIgnoreCase)).Select(x => x.Value).FirstOrDefault()
|
||||
?? memberName;
|
||||
|
||||
return string.IsNullOrEmpty(alias)
|
||||
? _builder.Database.DecorateName(mappedColumn)
|
||||
: string.Format("{0}.{1}", alias, _builder.Database.DecorateName(mappedColumn));
|
||||
}
|
||||
|
||||
public string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return _builder.ParseConstant(value, _builder.Parameters, columnSchema);
|
||||
}
|
||||
|
||||
public string DecorateName(string name)
|
||||
{
|
||||
return _builder.Database.DecorateName(name);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly DynamicTypeMap _mapper;
|
||||
|
||||
public DynamicTypedSelectQueryBuilder(DynamicDatabase db)
|
||||
@@ -141,7 +181,12 @@ namespace DynamORM.Builders.Implementation
|
||||
if (SupportNoLock && spec.UseNoLock)
|
||||
joinExpr += " WITH(NOLOCK)";
|
||||
|
||||
if (!string.IsNullOrEmpty(spec.OnRawCondition))
|
||||
if (spec.OnSqlPredicate != null)
|
||||
{
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
joinExpr += string.Format(" ON {0}", spec.OnSqlPredicate(new TypedTableContext<T>(GetRootAliasOrTableName()), new TypedTableContext<TRight>(rightAlias)).Render(context));
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(spec.OnRawCondition))
|
||||
joinExpr += string.Format(" ON {0}", spec.OnRawCondition);
|
||||
|
||||
AppendJoinClause(joinExpr);
|
||||
@@ -197,6 +242,19 @@ namespace DynamORM.Builders.Implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedSelectQueryBuilder<T> SelectSql(Func<TypedTableContext<T>, TypedSqlSelectable> selector, params Func<TypedTableContext<T>, TypedSqlSelectable>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
AddSelectSqlSelector(selector);
|
||||
if (selectors != null)
|
||||
foreach (Func<TypedTableContext<T>, TypedSqlSelectable> item in selectors)
|
||||
AddSelectSqlSelector(item);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedSelectQueryBuilder<T> GroupBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
@@ -216,6 +274,19 @@ namespace DynamORM.Builders.Implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedSelectQueryBuilder<T> GroupBySql(Func<TypedTableContext<T>, TypedSqlExpression> selector, params Func<TypedTableContext<T>, TypedSqlExpression>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
AddGroupBySqlSelector(selector);
|
||||
if (selectors != null)
|
||||
foreach (Func<TypedTableContext<T>, TypedSqlExpression> item in selectors)
|
||||
AddGroupBySqlSelector(item);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedSelectQueryBuilder<T> OrderBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
@@ -235,6 +306,19 @@ namespace DynamORM.Builders.Implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedSelectQueryBuilder<T> OrderBySql(Func<TypedTableContext<T>, TypedSqlOrderExpression> selector, params Func<TypedTableContext<T>, TypedSqlOrderExpression>[] selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
AddOrderBySqlSelector(selector);
|
||||
if (selectors != null)
|
||||
foreach (Func<TypedTableContext<T>, TypedSqlOrderExpression> item in selectors)
|
||||
AddOrderBySqlSelector(item);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public new IDynamicTypedSelectQueryBuilder<T> Top(int? top)
|
||||
{
|
||||
base.Top(top);
|
||||
@@ -274,6 +358,34 @@ namespace DynamORM.Builders.Implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedSelectQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
string condition = RenderSqlPredicate(predicate);
|
||||
if (string.IsNullOrEmpty(WhereCondition))
|
||||
WhereCondition = condition;
|
||||
else
|
||||
WhereCondition = string.Format("{0} AND {1}", WhereCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedSelectQueryBuilder<T> HavingSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
string condition = RenderSqlPredicate(predicate);
|
||||
if (string.IsNullOrEmpty(HavingCondition))
|
||||
HavingCondition = condition;
|
||||
else
|
||||
HavingCondition = string.Format("{0} AND {1}", HavingCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public new IDynamicTypedSelectQueryBuilder<T> Having(DynamicColumn column)
|
||||
{
|
||||
base.Having(column);
|
||||
@@ -317,6 +429,17 @@ namespace DynamORM.Builders.Implementation
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSelectSqlSelector(Func<TypedTableContext<T>, TypedSqlSelectable> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
TypedSqlSelectable item = selector(new TypedTableContext<T>(GetRootAliasOrTableName()));
|
||||
string rendered = item.Render(context);
|
||||
_select = string.IsNullOrEmpty(_select) ? rendered : string.Format("{0}, {1}", _select, rendered);
|
||||
}
|
||||
|
||||
private void AddGroupBySelector<TResult>(Expression<Func<T, TResult>> selector)
|
||||
{
|
||||
var body = UnwrapConvert(selector.Body);
|
||||
@@ -336,6 +459,17 @@ namespace DynamORM.Builders.Implementation
|
||||
}
|
||||
}
|
||||
|
||||
private void AddGroupBySqlSelector(Func<TypedTableContext<T>, TypedSqlExpression> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
TypedSqlExpression item = selector(new TypedTableContext<T>(GetRootAliasOrTableName()));
|
||||
string rendered = item.Render(context);
|
||||
_groupby = string.IsNullOrEmpty(_groupby) ? rendered : string.Format("{0}, {1}", _groupby, rendered);
|
||||
}
|
||||
|
||||
private void AddOrderBySelector<TResult>(Expression<Func<T, TResult>> selector)
|
||||
{
|
||||
var body = UnwrapConvert(selector.Body);
|
||||
@@ -352,6 +486,23 @@ namespace DynamORM.Builders.Implementation
|
||||
((IDynamicSelectQueryBuilder)this).OrderBy(x => parsed);
|
||||
}
|
||||
|
||||
private void AddOrderBySqlSelector(Func<TypedTableContext<T>, TypedSqlOrderExpression> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
TypedSqlOrderExpression item = selector(new TypedTableContext<T>(GetRootAliasOrTableName()));
|
||||
string rendered = item.Render(context);
|
||||
_orderby = string.IsNullOrEmpty(_orderby) ? rendered : string.Format("{0}, {1}", _orderby, rendered);
|
||||
}
|
||||
|
||||
private string RenderSqlPredicate(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
TypedSqlRenderContext context = new TypedSqlRenderContext(this);
|
||||
return predicate(new TypedTableContext<T>(GetRootAliasOrTableName())).Render(context);
|
||||
}
|
||||
|
||||
private string ParseTypedCondition(Expression expression)
|
||||
{
|
||||
expression = UnwrapConvert(expression);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.Builders.Extensions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders.Implementation
|
||||
{
|
||||
@@ -46,6 +47,26 @@ namespace DynamORM.Builders.Implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDynamicTypedUpdateQueryBuilder<T> WhereSql(Func<TypedTableContext<T>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
string condition = TypedModifyHelper.RenderPredicate(predicate, null, RenderValue, Database.DecorateName);
|
||||
if (string.IsNullOrEmpty(WhereCondition))
|
||||
WhereCondition = condition;
|
||||
else
|
||||
WhereCondition = string.Format("{0} AND {1}", WhereCondition, condition);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
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 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);
|
||||
@@ -105,5 +126,14 @@ namespace DynamORM.Builders.Implementation
|
||||
base.Where(conditions, schema);
|
||||
return this;
|
||||
}
|
||||
|
||||
private string RenderValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "NULL";
|
||||
|
||||
DynamicSchemaColumn? columnSchema = null;
|
||||
return ParseConstant(value, Parameters, columnSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace DynamORM.Builders.Implementation
|
||||
/// <summary>Update query builder.</summary>
|
||||
internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
|
||||
{
|
||||
private string _columns;
|
||||
protected string _columns;
|
||||
|
||||
internal DynamicUpdateQueryBuilder(DynamicDatabase db)
|
||||
: base(db)
|
||||
@@ -339,4 +339,4 @@ namespace DynamORM.Builders.Implementation
|
||||
|
||||
#endregion IExtendedDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,46 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.Mapper;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders.Implementation
|
||||
{
|
||||
/// <summary>Helper methods for typed modify builders.</summary>
|
||||
internal static class TypedModifyHelper
|
||||
{
|
||||
private sealed class ModifyRenderContext : ITypedSqlRenderContext
|
||||
{
|
||||
private readonly Func<Type, string, string, Func<string, string>, string> _resolveColumn;
|
||||
private readonly Func<object, string> _renderValue;
|
||||
private readonly Func<string, string> _decorateName;
|
||||
|
||||
public ModifyRenderContext(Func<Type, string, string, Func<string, string>, string> resolveColumn, Func<object, string> renderValue, Func<string, string> decorateName)
|
||||
{
|
||||
_resolveColumn = resolveColumn;
|
||||
_renderValue = renderValue;
|
||||
_decorateName = decorateName;
|
||||
}
|
||||
|
||||
public string ResolveColumn(Type modelType, string memberName, string alias)
|
||||
{
|
||||
return _resolveColumn(modelType, memberName, alias, _decorateName);
|
||||
}
|
||||
|
||||
public string RenderValue(object value)
|
||||
{
|
||||
return _renderValue(value);
|
||||
}
|
||||
|
||||
public string DecorateName(string name)
|
||||
{
|
||||
return _decorateName(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetMappedColumn<T, TValue>(Expression<Func<T, TValue>> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
@@ -32,6 +63,32 @@ namespace DynamORM.Builders.Implementation
|
||||
ApplyWhereInternal(typeof(T), addCondition, predicate.Body);
|
||||
}
|
||||
|
||||
public static string RenderPredicate<T>(
|
||||
Func<TypedTableContext<T>, TypedSqlPredicate> predicate,
|
||||
string alias,
|
||||
Func<object, string> renderValue,
|
||||
Func<string, string> decorateName)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName);
|
||||
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)
|
||||
{
|
||||
if (expression == null)
|
||||
throw new ArgumentNullException("expression");
|
||||
|
||||
ModifyRenderContext context = new ModifyRenderContext(ResolveColumn, renderValue, decorateName);
|
||||
return expression(new TypedTableContext<T>(alias)).Render(context);
|
||||
}
|
||||
|
||||
private static void ApplyWhereInternal(Type modelType, Action<string, DynamicColumn.CompareOperator, object> addCondition, Expression expression)
|
||||
{
|
||||
expression = UnwrapConvert(expression);
|
||||
@@ -93,6 +150,25 @@ namespace DynamORM.Builders.Implementation
|
||||
?? member.Member.Name;
|
||||
}
|
||||
|
||||
private static string ResolveColumn(Type modelType, string memberName, string alias, Func<string, string> decorateName)
|
||||
{
|
||||
string mapped = GetMappedColumnByName(modelType, memberName);
|
||||
return string.IsNullOrEmpty(alias)
|
||||
? decorateName(mapped)
|
||||
: string.Format("{0}.{1}", alias, decorateName(mapped));
|
||||
}
|
||||
|
||||
private static string GetMappedColumnByName(Type modelType, string memberName)
|
||||
{
|
||||
DynamicTypeMap mapper = DynamicMapperCache.GetMapper(modelType);
|
||||
if (mapper == null)
|
||||
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", modelType.FullName));
|
||||
|
||||
return mapper.PropertyMap.TryGetValue(memberName)
|
||||
?? mapper.PropertyMap.Where(x => string.Equals(x.Key, memberName, StringComparison.OrdinalIgnoreCase)).Select(x => x.Value).FirstOrDefault()
|
||||
?? memberName;
|
||||
}
|
||||
|
||||
private static Expression UnwrapConvert(Expression expression)
|
||||
{
|
||||
while (expression is UnaryExpression &&
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using DynamORM.TypedSql;
|
||||
|
||||
namespace DynamORM.Builders
|
||||
{
|
||||
@@ -37,6 +38,9 @@ namespace DynamORM.Builders
|
||||
/// <summary>Gets raw ON condition.</summary>
|
||||
public string OnRawCondition { get; private set; }
|
||||
|
||||
/// <summary>Gets typed SQL DSL ON specification.</summary>
|
||||
public Func<TypedTableContext<TLeft>, TypedTableContext<TRight>, TypedSqlPredicate> OnSqlPredicate { get; private set; }
|
||||
|
||||
/// <summary>Sets join alias.</summary>
|
||||
public TypedJoinBuilder<TLeft, TRight> As(string alias)
|
||||
{
|
||||
@@ -133,6 +137,7 @@ namespace DynamORM.Builders
|
||||
|
||||
OnPredicate = predicate;
|
||||
OnRawCondition = null;
|
||||
OnSqlPredicate = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -144,6 +149,19 @@ namespace DynamORM.Builders
|
||||
|
||||
OnRawCondition = condition.Trim();
|
||||
OnPredicate = null;
|
||||
OnSqlPredicate = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Sets typed SQL DSL ON predicate.</summary>
|
||||
public TypedJoinBuilder<TLeft, TRight> OnSql(Func<TypedTableContext<TLeft>, TypedTableContext<TRight>, TypedSqlPredicate> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException("predicate");
|
||||
|
||||
OnSqlPredicate = predicate;
|
||||
OnPredicate = null;
|
||||
OnRawCondition = null;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
23
DynamORM/TypedSql/ITypedSqlRenderContext.cs
Normal file
23
DynamORM/TypedSql/ITypedSqlRenderContext.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace DynamORM.TypedSql
|
||||
{
|
||||
/// <summary>Render context used by typed SQL DSL nodes.</summary>
|
||||
public interface ITypedSqlRenderContext
|
||||
{
|
||||
/// <summary>Resolve mapped column for given model member.</summary>
|
||||
string ResolveColumn(Type modelType, string memberName, string alias);
|
||||
|
||||
/// <summary>Render value as SQL parameter or literal fragment.</summary>
|
||||
string RenderValue(object value);
|
||||
|
||||
/// <summary>Decorate SQL identifier.</summary>
|
||||
string DecorateName(string name);
|
||||
}
|
||||
}
|
||||
91
DynamORM/TypedSql/Sql.cs
Normal file
91
DynamORM/TypedSql/Sql.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DynamORM.TypedSql
|
||||
{
|
||||
/// <summary>Entry point for the typed SQL DSL.</summary>
|
||||
public static class Sql
|
||||
{
|
||||
/// <summary>Create parameterized value expression.</summary>
|
||||
public static TypedSqlExpression<T> Val<T>(T value)
|
||||
{
|
||||
return new TypedSqlValueExpression<T>(value);
|
||||
}
|
||||
|
||||
/// <summary>Create parameterized value expression.</summary>
|
||||
public static TypedSqlExpression<object> Val(object value)
|
||||
{
|
||||
return new TypedSqlValueExpression<object>(value);
|
||||
}
|
||||
|
||||
/// <summary>Create raw SQL expression.</summary>
|
||||
public static TypedSqlExpression<T> Raw<T>(string sql)
|
||||
{
|
||||
return new TypedSqlRawExpression<T>(sql);
|
||||
}
|
||||
|
||||
/// <summary>Create generic function call.</summary>
|
||||
public static TypedSqlExpression<T> Func<T>(string name, params TypedSqlExpression[] arguments)
|
||||
{
|
||||
return new TypedSqlFunctionExpression<T>(name, arguments);
|
||||
}
|
||||
|
||||
/// <summary>Create COUNT(*) expression.</summary>
|
||||
public static TypedSqlExpression<int> Count()
|
||||
{
|
||||
return Raw<int>("COUNT(*)");
|
||||
}
|
||||
|
||||
/// <summary>Create COUNT(expr) expression.</summary>
|
||||
public static TypedSqlExpression<int> Count(TypedSqlExpression expression)
|
||||
{
|
||||
return Func<int>("COUNT", expression);
|
||||
}
|
||||
|
||||
/// <summary>Create COALESCE expression.</summary>
|
||||
public static TypedSqlExpression<T> Coalesce<T>(params TypedSqlExpression[] expressions)
|
||||
{
|
||||
return Func<T>("COALESCE", expressions);
|
||||
}
|
||||
|
||||
/// <summary>Create CASE expression builder.</summary>
|
||||
public static TypedSqlCaseBuilder<T> Case<T>()
|
||||
{
|
||||
return new TypedSqlCaseBuilder<T>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builder for CASE expressions.</summary>
|
||||
/// <typeparam name="T">Result type.</typeparam>
|
||||
public sealed class TypedSqlCaseBuilder<T>
|
||||
{
|
||||
private readonly IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> _cases = new List<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>>();
|
||||
|
||||
/// <summary>Add WHEN ... THEN ... clause.</summary>
|
||||
public TypedSqlCaseBuilder<T> When(TypedSqlPredicate predicate, object value)
|
||||
{
|
||||
_cases.Add(new KeyValuePair<TypedSqlPredicate, TypedSqlExpression>(
|
||||
predicate,
|
||||
value as TypedSqlExpression ?? Sql.Val(value)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Finalize CASE expression with ELSE clause.</summary>
|
||||
public TypedSqlExpression<T> Else(object value)
|
||||
{
|
||||
return new TypedSqlCaseExpression<T>(_cases, value as TypedSqlExpression ?? Sql.Val(value));
|
||||
}
|
||||
|
||||
/// <summary>Finalize CASE expression without ELSE clause.</summary>
|
||||
public TypedSqlExpression<T> End()
|
||||
{
|
||||
return new TypedSqlCaseExpression<T>(_cases, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
321
DynamORM/TypedSql/TypedSqlExpression.cs
Normal file
321
DynamORM/TypedSql/TypedSqlExpression.cs
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DynamORM.TypedSql
|
||||
{
|
||||
/// <summary>Base selectable SQL fragment for the typed DSL.</summary>
|
||||
public abstract class TypedSqlSelectable
|
||||
{
|
||||
internal abstract string Render(ITypedSqlRenderContext context);
|
||||
}
|
||||
|
||||
/// <summary>Base SQL expression for the typed DSL.</summary>
|
||||
public abstract class TypedSqlExpression : TypedSqlSelectable
|
||||
{
|
||||
/// <summary>Alias this expression in SELECT clause.</summary>
|
||||
public TypedSqlAliasedExpression As(string alias)
|
||||
{
|
||||
return new TypedSqlAliasedExpression(this, alias);
|
||||
}
|
||||
|
||||
/// <summary>Order ascending.</summary>
|
||||
public TypedSqlOrderExpression Asc()
|
||||
{
|
||||
return new TypedSqlOrderExpression(this, true);
|
||||
}
|
||||
|
||||
/// <summary>Order descending.</summary>
|
||||
public TypedSqlOrderExpression Desc()
|
||||
{
|
||||
return new TypedSqlOrderExpression(this, false);
|
||||
}
|
||||
|
||||
/// <summary>Equality predicate.</summary>
|
||||
public TypedSqlPredicate Eq(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
|
||||
/// <summary>Inequality predicate.</summary>
|
||||
public TypedSqlPredicate NotEq(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "<>", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
|
||||
/// <summary>Greater-than predicate.</summary>
|
||||
public TypedSqlPredicate Gt(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, ">", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
|
||||
/// <summary>Greater-than-or-equal predicate.</summary>
|
||||
public TypedSqlPredicate Gte(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, ">=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
|
||||
/// <summary>Less-than predicate.</summary>
|
||||
public TypedSqlPredicate Lt(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "<", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
|
||||
/// <summary>Less-than-or-equal predicate.</summary>
|
||||
public TypedSqlPredicate Lte(object value)
|
||||
{
|
||||
return new TypedSqlBinaryPredicate(this, "<=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
|
||||
}
|
||||
|
||||
/// <summary>IS NULL predicate.</summary>
|
||||
public TypedSqlPredicate IsNull()
|
||||
{
|
||||
return new TypedSqlUnaryPredicate(this, "IS NULL");
|
||||
}
|
||||
|
||||
/// <summary>IS NOT NULL predicate.</summary>
|
||||
public TypedSqlPredicate IsNotNull()
|
||||
{
|
||||
return new TypedSqlUnaryPredicate(this, "IS NOT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Typed SQL expression.</summary>
|
||||
public abstract class TypedSqlExpression<T> : TypedSqlExpression
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Typed SQL predicate expression.</summary>
|
||||
public abstract class TypedSqlPredicate : TypedSqlExpression<bool>
|
||||
{
|
||||
/// <summary>Combine with AND.</summary>
|
||||
public TypedSqlPredicate And(TypedSqlPredicate right)
|
||||
{
|
||||
return new TypedSqlCombinedPredicate(this, "AND", right);
|
||||
}
|
||||
|
||||
/// <summary>Combine with OR.</summary>
|
||||
public TypedSqlPredicate Or(TypedSqlPredicate right)
|
||||
{
|
||||
return new TypedSqlCombinedPredicate(this, "OR", right);
|
||||
}
|
||||
|
||||
/// <summary>Negate predicate.</summary>
|
||||
public TypedSqlPredicate Not()
|
||||
{
|
||||
return new TypedSqlNegatedPredicate(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Aliased SQL expression.</summary>
|
||||
public sealed class TypedSqlAliasedExpression : TypedSqlSelectable
|
||||
{
|
||||
private readonly TypedSqlExpression _expression;
|
||||
private readonly string _alias;
|
||||
|
||||
internal TypedSqlAliasedExpression(TypedSqlExpression expression, string alias)
|
||||
{
|
||||
_expression = expression;
|
||||
_alias = alias;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("{0} AS {1}", _expression.Render(context), context.DecorateName(_alias));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ordered SQL expression.</summary>
|
||||
public sealed class TypedSqlOrderExpression : TypedSqlSelectable
|
||||
{
|
||||
private readonly TypedSqlExpression _expression;
|
||||
private readonly bool _ascending;
|
||||
|
||||
internal TypedSqlOrderExpression(TypedSqlExpression expression, bool ascending)
|
||||
{
|
||||
_expression = expression;
|
||||
_ascending = ascending;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("{0} {1}", _expression.Render(context), _ascending ? "ASC" : "DESC");
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlColumnExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly Type _modelType;
|
||||
private readonly string _memberName;
|
||||
private readonly string _alias;
|
||||
|
||||
internal TypedSqlColumnExpression(Type modelType, string memberName, string alias)
|
||||
{
|
||||
_modelType = modelType;
|
||||
_memberName = memberName;
|
||||
_alias = alias;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return context.ResolveColumn(_modelType, _memberName, _alias);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlValueExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly object _value;
|
||||
|
||||
internal TypedSqlValueExpression(object value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return context.RenderValue(_value);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlRawExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly string _sql;
|
||||
|
||||
internal TypedSqlRawExpression(string sql)
|
||||
{
|
||||
_sql = sql;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return _sql;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlFunctionExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly IList<TypedSqlExpression> _arguments;
|
||||
|
||||
internal TypedSqlFunctionExpression(string name, params TypedSqlExpression[] arguments)
|
||||
{
|
||||
_name = name;
|
||||
_arguments = arguments ?? new TypedSqlExpression[0];
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
List<string> rendered = new List<string>();
|
||||
foreach (TypedSqlExpression argument in _arguments)
|
||||
rendered.Add(argument.Render(context));
|
||||
|
||||
return string.Format("{0}({1})", _name, string.Join(", ", rendered.ToArray()));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlUnaryPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlExpression _expression;
|
||||
private readonly string _operator;
|
||||
|
||||
internal TypedSqlUnaryPredicate(TypedSqlExpression expression, string op)
|
||||
{
|
||||
_expression = expression;
|
||||
_operator = op;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("({0} {1})", _expression.Render(context), _operator);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlBinaryPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlExpression _left;
|
||||
private readonly string _operator;
|
||||
private readonly TypedSqlExpression _right;
|
||||
|
||||
internal TypedSqlBinaryPredicate(TypedSqlExpression left, string op, TypedSqlExpression right)
|
||||
{
|
||||
_left = left;
|
||||
_operator = op;
|
||||
_right = right;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
string op = _operator;
|
||||
if (_right is TypedSqlValueExpression<object> && string.Equals(_right.Render(context), "NULL", StringComparison.OrdinalIgnoreCase))
|
||||
op = _operator == "=" ? "IS" : _operator == "<>" ? "IS NOT" : _operator;
|
||||
|
||||
return string.Format("({0} {1} {2})", _left.Render(context), op, _right.Render(context));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlCombinedPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlPredicate _left;
|
||||
private readonly string _operator;
|
||||
private readonly TypedSqlPredicate _right;
|
||||
|
||||
internal TypedSqlCombinedPredicate(TypedSqlPredicate left, string op, TypedSqlPredicate right)
|
||||
{
|
||||
_left = left;
|
||||
_operator = op;
|
||||
_right = right;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("({0} {1} {2})", _left.Render(context), _operator, _right.Render(context));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlNegatedPredicate : TypedSqlPredicate
|
||||
{
|
||||
private readonly TypedSqlPredicate _predicate;
|
||||
|
||||
internal TypedSqlNegatedPredicate(TypedSqlPredicate predicate)
|
||||
{
|
||||
_predicate = predicate;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
return string.Format("(NOT {0})", _predicate.Render(context));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypedSqlCaseExpression<T> : TypedSqlExpression<T>
|
||||
{
|
||||
private readonly IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> _cases;
|
||||
private readonly TypedSqlExpression _elseExpression;
|
||||
|
||||
internal TypedSqlCaseExpression(IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> cases, TypedSqlExpression elseExpression)
|
||||
{
|
||||
_cases = cases;
|
||||
_elseExpression = elseExpression;
|
||||
}
|
||||
|
||||
internal override string Render(ITypedSqlRenderContext context)
|
||||
{
|
||||
List<string> items = new List<string>();
|
||||
items.Add("CASE");
|
||||
|
||||
foreach (KeyValuePair<TypedSqlPredicate, TypedSqlExpression> item in _cases)
|
||||
items.Add(string.Format("WHEN {0} THEN {1}", item.Key.Render(context), item.Value.Render(context)));
|
||||
|
||||
if (_elseExpression != null)
|
||||
items.Add(string.Format("ELSE {0}", _elseExpression.Render(context)));
|
||||
|
||||
items.Add("END");
|
||||
return string.Join(" ", items.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
40
DynamORM/TypedSql/TypedTableContext.cs
Normal file
40
DynamORM/TypedSql/TypedTableContext.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DynamORM.TypedSql
|
||||
{
|
||||
/// <summary>Typed table context used by the typed SQL DSL.</summary>
|
||||
/// <typeparam name="T">Mapped entity type.</typeparam>
|
||||
public sealed class TypedTableContext<T>
|
||||
{
|
||||
internal TypedTableContext(string alias)
|
||||
{
|
||||
Alias = alias;
|
||||
}
|
||||
|
||||
/// <summary>Gets table alias used by the current query.</summary>
|
||||
public string Alias { get; private set; }
|
||||
|
||||
/// <summary>Creates a mapped column expression.</summary>
|
||||
public TypedSqlExpression<TValue> Col<TValue>(Expression<Func<T, TValue>> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException("selector");
|
||||
|
||||
MemberExpression member = selector.Body as MemberExpression;
|
||||
if (member == null && selector.Body is UnaryExpression)
|
||||
member = ((UnaryExpression)selector.Body).Operand as MemberExpression;
|
||||
|
||||
if (member == null)
|
||||
throw new NotSupportedException(string.Format("Column selector must target a mapped property: {0}", selector));
|
||||
|
||||
return new TypedSqlColumnExpression<TValue>(typeof(T), member.Member.Name, Alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user