Explore typed fluent mapped properties with aliases and order markers

This commit is contained in:
root
2026-02-26 16:07:23 +01:00
parent be90495e93
commit 5bdccedf42
8 changed files with 1347 additions and 25 deletions

View File

@@ -1957,23 +1957,26 @@ namespace DynamORM
/// <param name="alias">Table alias.</param>
/// <param name="noLock">use no lock.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicSelectQueryBuilder From<T>(string alias = null, bool noLock = false)
public virtual IDynamicTypedSelectQueryBuilder<T> From<T>(string alias = null, bool noLock = false)
{
// TODO: Make it more readable and maitainable
DynamicTypedSelectQueryBuilder<T> builder = new DynamicTypedSelectQueryBuilder<T>(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;
}
/// <summary>Adds to the <code>FROM</code> clause using <see cref="Type"/>.</summary>
/// <param name="t">Type which can be represented in database.</param>
@@ -7161,6 +7164,41 @@ namespace DynamORM
#endregion Top/Limit/Offset/Distinct
}
/// <summary>Typed select query builder for mapped entities.</summary>
/// <typeparam name="T">Mapped entity type.</typeparam>
public interface IDynamicTypedSelectQueryBuilder<T> : IDynamicSelectQueryBuilder
{
/// <summary>Add typed where predicate using mapped properties.</summary>
/// <param name="predicate">Predicate to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Where(Expression<Func<T, bool>> predicate);
/// <summary>Add typed having predicate using mapped properties.</summary>
/// <param name="predicate">Predicate to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Having(Expression<Func<T, bool>> predicate);
/// <summary>Add typed selected columns using mapped properties.</summary>
/// <typeparam name="TResult">Projection type.</typeparam>
/// <param name="selector">Selector to parse.</param>
/// <param name="selectors">Additional selectors to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Select<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors);
/// <summary>Add typed group by columns using mapped properties.</summary>
/// <typeparam name="TResult">Projection type.</typeparam>
/// <param name="selector">Selector to parse.</param>
/// <param name="selectors">Additional selectors to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> GroupBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors);
/// <summary>Add typed order by columns using mapped properties. Supports <c>Asc()</c>/<c>Desc()</c>.</summary>
/// <typeparam name="TResult">Projection type.</typeparam>
/// <param name="selector">Selector to parse.</param>
/// <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>Dynamic update query builder interface.</summary>
/// <remarks>This interface it publicly available. Implementation should be hidden.</remarks>
public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder
@@ -7290,6 +7328,49 @@ namespace DynamORM
/// <summary>Gets table schema.</summary>
Dictionary<string, DynamicSchemaColumn> Schema { get; }
}
/// <summary>Marker extensions for typed fluent builder expressions.</summary>
public static class TypedFluentExtensions
{
/// <summary>Typed select helper that avoids overload resolution with dynamic methods.</summary>
public static IDynamicTypedSelectQueryBuilder<TModel> SelectTyped<TModel, TResult>(
this IDynamicTypedSelectQueryBuilder<TModel> builder,
System.Linq.Expressions.Expression<System.Func<TModel, TResult>> selector,
params System.Linq.Expressions.Expression<System.Func<TModel, TResult>>[] selectors)
{
return builder.Select(selector, selectors);
}
/// <summary>Typed group by helper that avoids overload resolution with dynamic methods.</summary>
public static IDynamicTypedSelectQueryBuilder<TModel> GroupByTyped<TModel, TResult>(
this IDynamicTypedSelectQueryBuilder<TModel> builder,
System.Linq.Expressions.Expression<System.Func<TModel, TResult>> selector,
params System.Linq.Expressions.Expression<System.Func<TModel, TResult>>[] selectors)
{
return builder.GroupBy(selector, selectors);
}
/// <summary>Typed order by helper that avoids overload resolution with dynamic methods.</summary>
public static IDynamicTypedSelectQueryBuilder<TModel> OrderByTyped<TModel, TResult>(
this IDynamicTypedSelectQueryBuilder<TModel> builder,
System.Linq.Expressions.Expression<System.Func<TModel, TResult>> selector,
params System.Linq.Expressions.Expression<System.Func<TModel, TResult>>[] selectors)
{
return builder.OrderBy(selector, selectors);
}
/// <summary>Marks select projection alias in typed expressions.</summary>
public static T As<T>(this T source, string alias)
{
return source;
}
/// <summary>Marks ascending order in typed order expressions.</summary>
public static T Asc<T>(this T source)
{
return source;
}
/// <summary>Marks descending order in typed order expressions.</summary>
public static T Desc<T>(this T source)
{
return source;
}
}
namespace Extensions
{
internal static class DynamicHavingQueryExtensions
@@ -10304,6 +10385,437 @@ namespace DynamORM
}
#endregion IExtendedDisposable
}
/// <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 readonly DynamicTypeMap _mapper;
public DynamicTypedSelectQueryBuilder(DynamicDatabase db)
: base(db)
{
_mapper = DynamicMapperCache.GetMapper<T>()
?? throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(T).FullName));
}
public IDynamicTypedSelectQueryBuilder<T> Where(Expression<Func<T, bool>> 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<T> Join(params Func<dynamic, object>[] func)
{
base.Join(func);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(DynamicColumn column)
{
base.Where(column);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(string column, DynamicColumn.CompareOperator op, object value)
{
base.Where(column, op, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(string column, object value)
{
base.Where(column, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(object conditions, bool schema = false)
{
base.Where(conditions, schema);
return this;
}
public IDynamicTypedSelectQueryBuilder<T> Select<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] 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<T> GroupBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] 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<T> OrderBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] 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<T> Top(int? top)
{
base.Top(top);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Limit(int? limit)
{
base.Limit(limit);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Offset(int? offset)
{
base.Offset(offset);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Distinct(bool distinct = true)
{
base.Distinct(distinct);
return this;
}
public IDynamicTypedSelectQueryBuilder<T> Having(Expression<Func<T, bool>> 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<T> Having(DynamicColumn column)
{
base.Having(column);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Having(string column, DynamicColumn.CompareOperator op, object value)
{
base.Having(column, op, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Having(string column, object value)
{
base.Having(column, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Having(object conditions, bool schema = false)
{
base.Having(conditions, schema);
return this;
}
private void AddSelectSelector<TResult>(Expression<Func<T, TResult>> 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<TResult>(Expression<Func<T, TResult>> 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<TResult>(Expression<Func<T, TResult>> 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<string>();
foreach (var item in values.Cast<object>())
inList.Add(ParseConstant(item, Parameters, columnSchema));
if (!inList.Any())
return "(1 = 0)";
return string.Format("({0} IN({1}))", left, string.Join(", ", inList));
}
private string ParseTypedMemberAccess(Expression expression)
{
expression = UnwrapConvert(expression);
if (!(expression is MemberExpression member) || !IsMemberFromTypedParameter(member))
throw new NotSupportedException(string.Format("Typed fluent member access is not supported: {0}", expression));
string mappedColumn = null;
var property = member.Member as PropertyInfo;
if (property != null)
{
var attrs = property.GetCustomAttributes(typeof(ColumnAttribute), true);
var colAttr = attrs == null ? null : attrs.Cast<ColumnAttribute>().FirstOrDefault();
if (colAttr != null && !string.IsNullOrEmpty(colAttr.Name))
mappedColumn = colAttr.Name;
}
if (string.IsNullOrEmpty(mappedColumn))
{
mappedColumn = _mapper.PropertyMap.TryGetValue(member.Member.Name)
?? _mapper.PropertyMap
.Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Value)
.FirstOrDefault()
?? _mapper.ColumnsMap
.Where(x => string.Equals(x.Value.Name, member.Member.Name, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Key)
.FirstOrDefault()
?? member.Member.Name;
}
string tablePrefix = GetRootAliasOrTableName();
string qualified = string.IsNullOrEmpty(tablePrefix)
? mappedColumn
: string.Format("{0}.{1}", tablePrefix, mappedColumn);
return FixObjectName(qualified);
}
private string ParseTypedSelectExpression(Expression expression)
{
expression = UnwrapConvert(expression);
if (expression is MethodCallExpression call && IsAsCall(call))
{
string left = ParseTypedMemberAccess(call.Object ?? call.Arguments.FirstOrDefault());
var alias = EvaluateExpression(call.Arguments.Last()) == null ? null : EvaluateExpression(call.Arguments.Last()).ToString();
alias = alias.Validated("Alias");
return string.Format("{0} AS {1}", left, Database.DecorateName(alias));
}
return ParseTypedMemberAccess(expression);
}
private string GetRootAliasOrTableName()
{
var mappedTable = _mapper.Table == null || string.IsNullOrEmpty(_mapper.Table.Name)
? _mapper.Type.Name
: _mapper.Table.Name;
var table = Tables.FirstOrDefault(t => t.Name == mappedTable || t.Name == Database.StripName(mappedTable));
if (table == null)
table = Tables.FirstOrDefault();
if (table == null)
return null;
return string.IsNullOrEmpty(table.Alias) ? table.Name : table.Alias;
}
private static bool IsMemberFromTypedParameter(Expression expression)
{
var member = expression as MemberExpression;
if (member == null)
return false;
var parameter = member.Expression as ParameterExpression;
return parameter != null && parameter.Type == typeof(T);
}
private static Expression UnwrapConvert(Expression expression)
{
while (expression is UnaryExpression unary &&
(unary.NodeType == ExpressionType.Convert || unary.NodeType == ExpressionType.ConvertChecked))
expression = unary.Operand;
return expression;
}
private static bool IsNullConstant(Expression expression)
{
expression = UnwrapConvert(expression);
return expression is ConstantExpression constant && constant.Value == null;
}
private static string GetBinaryOperator(ExpressionType type, bool rightIsNull)
{
switch (type)
{
case ExpressionType.Equal: return rightIsNull ? "IS" : "=";
case ExpressionType.NotEqual: return rightIsNull ? "IS NOT" : "<>";
case ExpressionType.GreaterThan: return ">";
case ExpressionType.GreaterThanOrEqual: return ">=";
case ExpressionType.LessThan: return "<";
case ExpressionType.LessThanOrEqual: return "<=";
default: throw new NotSupportedException(string.Format("Expression operation is not supported: {0}", type));
}
}
private static bool IsEnumerableContains(MethodCallExpression call)
{
if (!string.Equals(call.Method.Name, "Contains", StringComparison.Ordinal))
return false;
if (call.Object != null && call.Arguments.Count == 1)
return true;
return call.Object == null && call.Arguments.Count == 2;
}
private static bool IsAsCall(MethodCallExpression call)
{
return string.Equals(call.Method.Name, "As", StringComparison.Ordinal)
&& (call.Arguments.Count == 1 || call.Arguments.Count == 2);
}
private static bool IsAscOrDesc(MethodCallExpression call)
{
string name = call.Method.Name.ToUpper();
return (name == "ASC" || name == "DESC")
&& (call.Arguments.Count == 0 || call.Arguments.Count == 1);
}
private static object EvaluateExpression(Expression expression)
{
var objectMember = Expression.Convert(expression, typeof(object));
var getter = Expression.Lambda<Func<object>>(objectMember).Compile();
return getter();
}
}
/// <summary>Update query builder.</summary>
internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{

View File

@@ -0,0 +1,42 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using DynamORM.Mapper;
namespace DynamORM.Tests.Helpers
{
[Table(Name = "sample_users")]
public class TypedFluentUser
{
[Column("id_user", true)]
public long Id { get; set; }
[Column("user_code")]
public string Code { get; set; }
}
}

View File

@@ -0,0 +1,99 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Linq;
using DynamORM.Builders;
using DynamORM.Tests.Helpers;
using NUnit.Framework;
namespace DynamORM.Tests.Select
{
[TestFixture]
public class TypedFluentBuilderTests : TestsBase
{
[SetUp]
public void SetUp()
{
CreateTestDatabase();
CreateDynamicDatabase(
DynamicDatabaseOptions.SingleConnection |
DynamicDatabaseOptions.SingleTransaction |
DynamicDatabaseOptions.SupportLimitOffset);
}
[TearDown]
public void TearDown()
{
DestroyDynamicDatabase();
DestroyTestDatabase();
}
[Test]
public void TestTypedWhereAndSelectUsesPropertyMap()
{
var cmd = Database.From<TypedFluentUser>("u")
.Where(u => u.Id == 1)
.SelectTyped(u => u.Code.As("CodeAlias"))
.OrderByTyped(u => u.Code.Desc());
Assert.AreEqual(
string.Format("SELECT u.\"user_code\" AS \"CodeAlias\" FROM \"sample_users\" AS u WHERE (u.\"id_user\" = [${0}]) ORDER BY u.\"user_code\" DESC", cmd.Parameters.Keys.First()),
cmd.CommandText());
}
[Test]
public void TestTypedWhereSupportsContains()
{
var ids = new[] { 1L, 2L, 3L }.ToList();
var cmd = Database.From<TypedFluentUser>("u")
.Where(u => ids.Contains(u.Id))
.SelectTyped(u => u.Id);
Assert.AreEqual(
string.Format("SELECT u.\"id_user\" FROM \"sample_users\" AS u WHERE (u.\"id_user\" IN([${0}], [${1}], [${2}]))",
cmd.Parameters.Keys.ElementAt(0),
cmd.Parameters.Keys.ElementAt(1),
cmd.Parameters.Keys.ElementAt(2)),
cmd.CommandText());
}
[Test]
public void TestTypedGroupByHavingOrderBy()
{
var cmd = Database.From<TypedFluentUser>("u")
.SelectTyped(u => u.Code)
.GroupByTyped(u => u.Code)
.Having(u => u.Code != null)
.OrderByTyped(u => u.Code.Asc());
Assert.AreEqual("SELECT u.\"user_code\" FROM \"sample_users\" AS u GROUP BY u.\"user_code\" HAVING (u.\"user_code\" IS NOT NULL) ORDER BY u.\"user_code\" ASC",
cmd.CommandText());
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Linq.Expressions;
namespace DynamORM.Builders
{
/// <summary>Typed select query builder for mapped entities.</summary>
/// <typeparam name="T">Mapped entity type.</typeparam>
public interface IDynamicTypedSelectQueryBuilder<T> : IDynamicSelectQueryBuilder
{
/// <summary>Add typed where predicate using mapped properties.</summary>
/// <param name="predicate">Predicate to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Where(Expression<Func<T, bool>> predicate);
/// <summary>Add typed having predicate using mapped properties.</summary>
/// <param name="predicate">Predicate to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Having(Expression<Func<T, bool>> predicate);
/// <summary>Add typed selected columns using mapped properties.</summary>
/// <typeparam name="TResult">Projection type.</typeparam>
/// <param name="selector">Selector to parse.</param>
/// <param name="selectors">Additional selectors to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Select<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors);
/// <summary>Add typed group by columns using mapped properties.</summary>
/// <typeparam name="TResult">Projection type.</typeparam>
/// <param name="selector">Selector to parse.</param>
/// <param name="selectors">Additional selectors to parse.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> GroupBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] selectors);
/// <summary>Add typed order by columns using mapped properties. Supports <c>Asc()</c>/<c>Desc()</c>.</summary>
/// <typeparam name="TResult">Projection type.</typeparam>
/// <param name="selector">Selector to parse.</param>
/// <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);
}
}

View File

@@ -42,7 +42,7 @@ using DynamORM.Mapper;
namespace DynamORM.Builders.Implementation
{
/// <summary>Implementation of dynamic select query builder.</summary>
internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving
internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving
{
private int? _limit = null;
private int? _offset = null;
@@ -1452,4 +1452,4 @@ namespace DynamORM.Builders.Implementation
#endregion IExtendedDisposable
}
}
}

View File

@@ -0,0 +1,517 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using DynamORM.Helpers;
using DynamORM.Mapper;
namespace DynamORM.Builders.Implementation
{
/// <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 readonly DynamicTypeMap _mapper;
public DynamicTypedSelectQueryBuilder(DynamicDatabase db)
: base(db)
{
_mapper = DynamicMapperCache.GetMapper<T>()
?? throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(T).FullName));
}
public IDynamicTypedSelectQueryBuilder<T> Where(Expression<Func<T, bool>> 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<T> Join(params Func<dynamic, object>[] func)
{
base.Join(func);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(DynamicColumn column)
{
base.Where(column);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(string column, DynamicColumn.CompareOperator op, object value)
{
base.Where(column, op, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(string column, object value)
{
base.Where(column, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Where(object conditions, bool schema = false)
{
base.Where(conditions, schema);
return this;
}
public IDynamicTypedSelectQueryBuilder<T> Select<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] 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<T> GroupBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] 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<T> OrderBy<TResult>(Expression<Func<T, TResult>> selector, params Expression<Func<T, TResult>>[] 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<T> Top(int? top)
{
base.Top(top);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Limit(int? limit)
{
base.Limit(limit);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Offset(int? offset)
{
base.Offset(offset);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Distinct(bool distinct = true)
{
base.Distinct(distinct);
return this;
}
public IDynamicTypedSelectQueryBuilder<T> Having(Expression<Func<T, bool>> 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<T> Having(DynamicColumn column)
{
base.Having(column);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Having(string column, DynamicColumn.CompareOperator op, object value)
{
base.Having(column, op, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Having(string column, object value)
{
base.Having(column, value);
return this;
}
public new IDynamicTypedSelectQueryBuilder<T> Having(object conditions, bool schema = false)
{
base.Having(conditions, schema);
return this;
}
private void AddSelectSelector<TResult>(Expression<Func<T, TResult>> 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<TResult>(Expression<Func<T, TResult>> 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<TResult>(Expression<Func<T, TResult>> 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<string>();
foreach (var item in values.Cast<object>())
inList.Add(ParseConstant(item, Parameters, columnSchema));
if (!inList.Any())
return "(1 = 0)";
return string.Format("({0} IN({1}))", left, string.Join(", ", inList));
}
private string ParseTypedMemberAccess(Expression expression)
{
expression = UnwrapConvert(expression);
if (!(expression is MemberExpression member) || !IsMemberFromTypedParameter(member))
throw new NotSupportedException(string.Format("Typed fluent member access is not supported: {0}", expression));
string mappedColumn = null;
var property = member.Member as PropertyInfo;
if (property != null)
{
var attrs = property.GetCustomAttributes(typeof(ColumnAttribute), true);
var colAttr = attrs == null ? null : attrs.Cast<ColumnAttribute>().FirstOrDefault();
if (colAttr != null && !string.IsNullOrEmpty(colAttr.Name))
mappedColumn = colAttr.Name;
}
if (string.IsNullOrEmpty(mappedColumn))
{
mappedColumn = _mapper.PropertyMap.TryGetValue(member.Member.Name)
?? _mapper.PropertyMap
.Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Value)
.FirstOrDefault()
?? _mapper.ColumnsMap
.Where(x => string.Equals(x.Value.Name, member.Member.Name, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Key)
.FirstOrDefault()
?? member.Member.Name;
}
string tablePrefix = GetRootAliasOrTableName();
string qualified = string.IsNullOrEmpty(tablePrefix)
? mappedColumn
: string.Format("{0}.{1}", tablePrefix, mappedColumn);
return FixObjectName(qualified);
}
private string ParseTypedSelectExpression(Expression expression)
{
expression = UnwrapConvert(expression);
if (expression is MethodCallExpression call && IsAsCall(call))
{
string left = ParseTypedMemberAccess(call.Object ?? call.Arguments.FirstOrDefault());
var alias = EvaluateExpression(call.Arguments.Last()) == null ? null : EvaluateExpression(call.Arguments.Last()).ToString();
alias = alias.Validated("Alias");
return string.Format("{0} AS {1}", left, Database.DecorateName(alias));
}
return ParseTypedMemberAccess(expression);
}
private string GetRootAliasOrTableName()
{
var mappedTable = _mapper.Table == null || string.IsNullOrEmpty(_mapper.Table.Name)
? _mapper.Type.Name
: _mapper.Table.Name;
var table = Tables.FirstOrDefault(t => t.Name == mappedTable || t.Name == Database.StripName(mappedTable));
if (table == null)
table = Tables.FirstOrDefault();
if (table == null)
return null;
return string.IsNullOrEmpty(table.Alias) ? table.Name : table.Alias;
}
private static bool IsMemberFromTypedParameter(Expression expression)
{
var member = expression as MemberExpression;
if (member == null)
return false;
var parameter = member.Expression as ParameterExpression;
return parameter != null && parameter.Type == typeof(T);
}
private static Expression UnwrapConvert(Expression expression)
{
while (expression is UnaryExpression unary &&
(unary.NodeType == ExpressionType.Convert || unary.NodeType == ExpressionType.ConvertChecked))
expression = unary.Operand;
return expression;
}
private static bool IsNullConstant(Expression expression)
{
expression = UnwrapConvert(expression);
return expression is ConstantExpression constant && constant.Value == null;
}
private static string GetBinaryOperator(ExpressionType type, bool rightIsNull)
{
switch (type)
{
case ExpressionType.Equal: return rightIsNull ? "IS" : "=";
case ExpressionType.NotEqual: return rightIsNull ? "IS NOT" : "<>";
case ExpressionType.GreaterThan: return ">";
case ExpressionType.GreaterThanOrEqual: return ">=";
case ExpressionType.LessThan: return "<";
case ExpressionType.LessThanOrEqual: return "<=";
default: throw new NotSupportedException(string.Format("Expression operation is not supported: {0}", type));
}
}
private static bool IsEnumerableContains(MethodCallExpression call)
{
if (!string.Equals(call.Method.Name, "Contains", StringComparison.Ordinal))
return false;
if (call.Object != null && call.Arguments.Count == 1)
return true;
return call.Object == null && call.Arguments.Count == 2;
}
private static bool IsAsCall(MethodCallExpression call)
{
return string.Equals(call.Method.Name, "As", StringComparison.Ordinal)
&& (call.Arguments.Count == 1 || call.Arguments.Count == 2);
}
private static bool IsAscOrDesc(MethodCallExpression call)
{
string name = call.Method.Name.ToUpper();
return (name == "ASC" || name == "DESC")
&& (call.Arguments.Count == 0 || call.Arguments.Count == 1);
}
private static object EvaluateExpression(Expression expression)
{
var objectMember = Expression.Convert(expression, typeof(object));
var getter = Expression.Lambda<Func<object>>(objectMember).Compile();
return getter();
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace DynamORM.Builders
{
/// <summary>Marker extensions for typed fluent builder expressions.</summary>
public static class TypedFluentExtensions
{
/// <summary>Typed select helper that avoids overload resolution with dynamic methods.</summary>
public static IDynamicTypedSelectQueryBuilder<TModel> SelectTyped<TModel, TResult>(
this IDynamicTypedSelectQueryBuilder<TModel> builder,
System.Linq.Expressions.Expression<System.Func<TModel, TResult>> selector,
params System.Linq.Expressions.Expression<System.Func<TModel, TResult>>[] selectors)
{
return builder.Select(selector, selectors);
}
/// <summary>Typed group by helper that avoids overload resolution with dynamic methods.</summary>
public static IDynamicTypedSelectQueryBuilder<TModel> GroupByTyped<TModel, TResult>(
this IDynamicTypedSelectQueryBuilder<TModel> builder,
System.Linq.Expressions.Expression<System.Func<TModel, TResult>> selector,
params System.Linq.Expressions.Expression<System.Func<TModel, TResult>>[] selectors)
{
return builder.GroupBy(selector, selectors);
}
/// <summary>Typed order by helper that avoids overload resolution with dynamic methods.</summary>
public static IDynamicTypedSelectQueryBuilder<TModel> OrderByTyped<TModel, TResult>(
this IDynamicTypedSelectQueryBuilder<TModel> builder,
System.Linq.Expressions.Expression<System.Func<TModel, TResult>> selector,
params System.Linq.Expressions.Expression<System.Func<TModel, TResult>>[] selectors)
{
return builder.OrderBy(selector, selectors);
}
/// <summary>Marks select projection alias in typed expressions.</summary>
public static T As<T>(this T source, string alias)
{
return source;
}
/// <summary>Marks ascending order in typed order expressions.</summary>
public static T Asc<T>(this T source)
{
return source;
}
/// <summary>Marks descending order in typed order expressions.</summary>
public static T Desc<T>(this T source)
{
return source;
}
}
}

View File

@@ -453,24 +453,28 @@ namespace DynamORM
/// <param name="alias">Table alias.</param>
/// <param name="noLock">use no lock.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicSelectQueryBuilder From<T>(string alias = null, bool noLock = false)
{
// TODO: Make it more readable and maitainable
if (noLock)
{
if (string.IsNullOrEmpty(alias))
return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).NoLock());
else
return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias).NoLock());
}
else
{
if (string.IsNullOrEmpty(alias))
return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)));
else
return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias));
}
}
public virtual IDynamicTypedSelectQueryBuilder<T> From<T>(string alias = null, bool noLock = false)
{
// TODO: Make it more readable and maitainable
DynamicTypedSelectQueryBuilder<T> builder = new DynamicTypedSelectQueryBuilder<T>(this);
if (noLock)
{
if (string.IsNullOrEmpty(alias))
builder.From(x => x(typeof(T)).NoLock());
else
builder.From(x => x(typeof(T)).As(alias).NoLock());
}
else
{
if (string.IsNullOrEmpty(alias))
builder.From(x => x(typeof(T)));
else
builder.From(x => x(typeof(T)).As(alias));
}
return builder;
}
/// <summary>Adds to the <code>FROM</code> clause using <see cref="Type"/>.</summary>
/// <param name="t">Type which can be represented in database.</param>