diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs
index e9ebcd5..a59403d 100644
--- a/AmalgamationTool/DynamORM.Amalgamation.cs
+++ b/AmalgamationTool/DynamORM.Amalgamation.cs
@@ -1957,23 +1957,26 @@ namespace DynamORM
/// Table alias.
/// use no lock.
/// This instance to permit chaining.
- public virtual IDynamicSelectQueryBuilder From(string alias = null, bool noLock = false)
+ public virtual IDynamicTypedSelectQueryBuilder From(string alias = null, bool noLock = false)
{
// TODO: Make it more readable and maitainable
+ DynamicTypedSelectQueryBuilder builder = new DynamicTypedSelectQueryBuilder(this);
+
if (noLock)
{
if (string.IsNullOrEmpty(alias))
- return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).NoLock());
+ builder.From(x => x(typeof(T)).NoLock());
else
- return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias).NoLock());
+ builder.From(x => x(typeof(T)).As(alias).NoLock());
}
else
{
if (string.IsNullOrEmpty(alias))
- return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)));
+ builder.From(x => x(typeof(T)));
else
- return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias));
+ builder.From(x => x(typeof(T)).As(alias));
}
+ return builder;
}
/// Adds to the FROM clause using .
/// Type which can be represented in database.
@@ -7161,6 +7164,41 @@ namespace DynamORM
#endregion Top/Limit/Offset/Distinct
}
+ /// Typed select query builder for mapped entities.
+ /// Mapped entity type.
+ public interface IDynamicTypedSelectQueryBuilder : IDynamicSelectQueryBuilder
+ {
+ /// Add typed where predicate using mapped properties.
+ /// Predicate to parse.
+ /// Builder instance.
+ IDynamicTypedSelectQueryBuilder Where(Expression> predicate);
+
+ /// Add typed having predicate using mapped properties.
+ /// Predicate to parse.
+ /// Builder instance.
+ IDynamicTypedSelectQueryBuilder Having(Expression> predicate);
+
+ /// Add typed selected columns using mapped properties.
+ /// Projection type.
+ /// Selector to parse.
+ /// Additional selectors to parse.
+ /// Builder instance.
+ IDynamicTypedSelectQueryBuilder Select(Expression> selector, params Expression>[] selectors);
+
+ /// Add typed group by columns using mapped properties.
+ /// Projection type.
+ /// Selector to parse.
+ /// Additional selectors to parse.
+ /// Builder instance.
+ IDynamicTypedSelectQueryBuilder GroupBy(Expression> selector, params Expression>[] selectors);
+
+ /// Add typed order by columns using mapped properties. Supports Asc()/Desc().
+ /// Projection type.
+ /// Selector to parse.
+ /// Additional selectors to parse.
+ /// Builder instance.
+ IDynamicTypedSelectQueryBuilder OrderBy(Expression> selector, params Expression>[] selectors);
+ }
/// Dynamic update query builder interface.
/// This interface it publicly available. Implementation should be hidden.
public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder
@@ -7290,6 +7328,49 @@ namespace DynamORM
/// Gets table schema.
Dictionary Schema { get; }
}
+ /// Marker extensions for typed fluent builder expressions.
+ public static class TypedFluentExtensions
+ {
+ /// Typed select helper that avoids overload resolution with dynamic methods.
+ public static IDynamicTypedSelectQueryBuilder SelectTyped(
+ this IDynamicTypedSelectQueryBuilder builder,
+ System.Linq.Expressions.Expression> selector,
+ params System.Linq.Expressions.Expression>[] selectors)
+ {
+ return builder.Select(selector, selectors);
+ }
+ /// Typed group by helper that avoids overload resolution with dynamic methods.
+ public static IDynamicTypedSelectQueryBuilder GroupByTyped(
+ this IDynamicTypedSelectQueryBuilder builder,
+ System.Linq.Expressions.Expression> selector,
+ params System.Linq.Expressions.Expression>[] selectors)
+ {
+ return builder.GroupBy(selector, selectors);
+ }
+ /// Typed order by helper that avoids overload resolution with dynamic methods.
+ public static IDynamicTypedSelectQueryBuilder OrderByTyped(
+ this IDynamicTypedSelectQueryBuilder builder,
+ System.Linq.Expressions.Expression> selector,
+ params System.Linq.Expressions.Expression>[] selectors)
+ {
+ return builder.OrderBy(selector, selectors);
+ }
+ /// Marks select projection alias in typed expressions.
+ public static T As(this T source, string alias)
+ {
+ return source;
+ }
+ /// Marks ascending order in typed order expressions.
+ public static T Asc(this T source)
+ {
+ return source;
+ }
+ /// Marks descending order in typed order expressions.
+ public static T Desc(this T source)
+ {
+ return source;
+ }
+ }
namespace Extensions
{
internal static class DynamicHavingQueryExtensions
@@ -10304,6 +10385,437 @@ namespace DynamORM
}
#endregion IExtendedDisposable
}
+ /// Typed wrapper over with property-to-column translation.
+ /// Mapped entity type.
+ internal class DynamicTypedSelectQueryBuilder : DynamicSelectQueryBuilder, IDynamicTypedSelectQueryBuilder
+ {
+ private readonly DynamicTypeMap _mapper;
+
+ public DynamicTypedSelectQueryBuilder(DynamicDatabase db)
+ : base(db)
+ {
+ _mapper = DynamicMapperCache.GetMapper()
+ ?? throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(T).FullName));
+ }
+ public IDynamicTypedSelectQueryBuilder Where(Expression> predicate)
+ {
+ if (predicate == null)
+ throw new ArgumentNullException("predicate");
+
+ string condition = ParseTypedCondition(predicate.Body);
+
+ if (string.IsNullOrEmpty(WhereCondition))
+ WhereCondition = condition;
+ else
+ WhereCondition = string.Format("{0} AND {1}", WhereCondition, condition);
+
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Join(params Func[] func)
+ {
+ base.Join(func);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Where(DynamicColumn column)
+ {
+ base.Where(column);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value)
+ {
+ base.Where(column, op, value);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Where(string column, object value)
+ {
+ base.Where(column, value);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Where(object conditions, bool schema = false)
+ {
+ base.Where(conditions, schema);
+ return this;
+ }
+ public IDynamicTypedSelectQueryBuilder Select(Expression> selector, params Expression>[] selectors)
+ {
+ if (selector == null)
+ throw new ArgumentNullException("selector");
+
+ AddSelectSelector(selector);
+
+ if (selectors != null)
+ foreach (var s in selectors)
+ {
+ if (s == null)
+ throw new ArgumentNullException("selectors", "Array of selectors cannot contain null.");
+
+ AddSelectSelector(s);
+ }
+ return this;
+ }
+ public IDynamicTypedSelectQueryBuilder GroupBy(Expression> selector, params Expression>[] selectors)
+ {
+ if (selector == null)
+ throw new ArgumentNullException("selector");
+
+ AddGroupBySelector(selector);
+
+ if (selectors != null)
+ foreach (var s in selectors)
+ {
+ if (s == null)
+ throw new ArgumentNullException("selectors", "Array of selectors cannot contain null.");
+
+ AddGroupBySelector(s);
+ }
+ return this;
+ }
+ public IDynamicTypedSelectQueryBuilder OrderBy(Expression> selector, params Expression>[] selectors)
+ {
+ if (selector == null)
+ throw new ArgumentNullException("selector");
+
+ AddOrderBySelector(selector);
+
+ if (selectors != null)
+ foreach (var s in selectors)
+ {
+ if (s == null)
+ throw new ArgumentNullException("selectors", "Array of selectors cannot contain null.");
+
+ AddOrderBySelector(s);
+ }
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Top(int? top)
+ {
+ base.Top(top);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Limit(int? limit)
+ {
+ base.Limit(limit);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Offset(int? offset)
+ {
+ base.Offset(offset);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Distinct(bool distinct = true)
+ {
+ base.Distinct(distinct);
+ return this;
+ }
+ public IDynamicTypedSelectQueryBuilder Having(Expression> predicate)
+ {
+ if (predicate == null)
+ throw new ArgumentNullException("predicate");
+
+ string condition = ParseTypedCondition(predicate.Body);
+
+ if (string.IsNullOrEmpty(HavingCondition))
+ HavingCondition = condition;
+ else
+ HavingCondition = string.Format("{0} AND {1}", HavingCondition, condition);
+
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Having(DynamicColumn column)
+ {
+ base.Having(column);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value)
+ {
+ base.Having(column, op, value);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Having(string column, object value)
+ {
+ base.Having(column, value);
+ return this;
+ }
+ public new IDynamicTypedSelectQueryBuilder Having(object conditions, bool schema = false)
+ {
+ base.Having(conditions, schema);
+ return this;
+ }
+ private void AddSelectSelector(Expression> selector)
+ {
+ var body = UnwrapConvert(selector.Body);
+
+ if (body is NewExpression ne)
+ {
+ foreach (var argument in ne.Arguments)
+ {
+ var parsed = ParseTypedSelectExpression(argument);
+ ((IDynamicSelectQueryBuilder)this).Select(x => parsed);
+ }
+ }
+ else
+ {
+ var parsed = ParseTypedSelectExpression(body);
+ ((IDynamicSelectQueryBuilder)this).Select(x => parsed);
+ }
+ }
+ private void AddGroupBySelector(Expression> selector)
+ {
+ var body = UnwrapConvert(selector.Body);
+
+ if (body is NewExpression ne)
+ {
+ foreach (var argument in ne.Arguments)
+ {
+ var parsed = ParseTypedMemberAccess(argument);
+ ((IDynamicSelectQueryBuilder)this).GroupBy(x => parsed);
+ }
+ }
+ else
+ {
+ var parsed = ParseTypedMemberAccess(body);
+ ((IDynamicSelectQueryBuilder)this).GroupBy(x => parsed);
+ }
+ }
+ private void AddOrderBySelector(Expression> selector)
+ {
+ var body = UnwrapConvert(selector.Body);
+ bool ascending = true;
+
+ if (body is MethodCallExpression call && IsAscOrDesc(call))
+ {
+ ascending = call.Method.Name.ToUpper() != "DESC";
+ body = UnwrapConvert(call.Object ?? call.Arguments.FirstOrDefault());
+ }
+ string main = ParseTypedMemberAccess(body);
+ string parsed = string.Format("{0} {1}", main, ascending ? "ASC" : "DESC");
+ ((IDynamicSelectQueryBuilder)this).OrderBy(x => parsed);
+ }
+ private string ParseTypedCondition(Expression expression)
+ {
+ expression = UnwrapConvert(expression);
+
+ if (expression is BinaryExpression binary)
+ {
+ switch (binary.NodeType)
+ {
+ case ExpressionType.AndAlso:
+ case ExpressionType.And:
+ return string.Format("({0} AND {1})", ParseTypedCondition(binary.Left), ParseTypedCondition(binary.Right));
+ case ExpressionType.OrElse:
+ case ExpressionType.Or:
+ return string.Format("({0} OR {1})", ParseTypedCondition(binary.Left), ParseTypedCondition(binary.Right));
+ case ExpressionType.Equal:
+ case ExpressionType.NotEqual:
+ case ExpressionType.GreaterThan:
+ case ExpressionType.GreaterThanOrEqual:
+ case ExpressionType.LessThan:
+ case ExpressionType.LessThanOrEqual:
+ {
+ DynamicSchemaColumn? columnSchema = null;
+ string left = ParseTypedValue(binary.Left, ref columnSchema);
+ string right = ParseTypedValue(binary.Right, ref columnSchema);
+ string op = GetBinaryOperator(binary.NodeType, IsNullConstant(binary.Right));
+ return string.Format("({0} {1} {2})", left, op, right);
+ }
+ }
+ }
+ if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Not)
+ return string.Format("(NOT {0})", ParseTypedCondition(unary.Operand));
+
+ if (expression is MethodCallExpression call && IsEnumerableContains(call))
+ {
+ DynamicSchemaColumn? col = null;
+ return ParseTypedContains(call, ref col);
+ }
+ throw new NotSupportedException(string.Format("Typed fluent where expression is not supported: {0}", expression));
+ }
+ private string ParseTypedValue(Expression expression, ref DynamicSchemaColumn? columnSchema)
+ {
+ expression = UnwrapConvert(expression);
+
+ if (IsMemberFromTypedParameter(expression))
+ {
+ string col = ParseTypedMemberAccess(expression);
+ columnSchema = GetColumnFromSchema(col);
+ return col;
+ }
+ if (expression is MethodCallExpression call && IsEnumerableContains(call))
+ return ParseTypedContains(call, ref columnSchema);
+
+ object value = EvaluateExpression(expression);
+ return ParseConstant(value, Parameters, columnSchema);
+ }
+ private string ParseTypedContains(MethodCallExpression call, ref DynamicSchemaColumn? columnSchema)
+ {
+ // Supports: list.Contains(x.Property) and Enumerable.Contains(list, x.Property)
+ Expression collection;
+ Expression candidate;
+
+ if (call.Object != null)
+ {
+ collection = call.Object;
+ candidate = call.Arguments[0];
+ }
+ else
+ {
+ collection = call.Arguments[0];
+ candidate = call.Arguments[1];
+ }
+ candidate = UnwrapConvert(candidate);
+ if (!IsMemberFromTypedParameter(candidate))
+ throw new NotSupportedException(string.Format("Typed Contains() must target a mapped member: {0}", call));
+
+ string left = ParseTypedMemberAccess(candidate);
+ columnSchema = GetColumnFromSchema(left);
+
+ var values = EvaluateExpression(collection) as IEnumerable;
+ if (values == null && collection is MethodCallExpression implicitCall &&
+ string.Equals(implicitCall.Method.Name, "op_Implicit", StringComparison.Ordinal) &&
+ implicitCall.Arguments.Count > 0)
+ {
+ values = EvaluateExpression(implicitCall.Arguments[0]) as IEnumerable;
+ }
+ if (values == null)
+ throw new NotSupportedException(string.Format("Typed Contains() source is not enumerable: {0}", call));
+
+ var inList = new List();
+ foreach (var item in values.Cast