Explore typed fluent mapped properties with aliases and order markers
This commit is contained in:
69
DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs
Normal file
69
DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
79
DynamORM/Builders/TypedFluentExtensions.cs
Normal file
79
DynamORM/Builders/TypedFluentExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user