表达式列表<Func<T, TProperty>>
Posted
技术标签:
【中文标题】表达式列表<Func<T, TProperty>>【英文标题】:List of Expression<Func<T, TProperty>> 【发布时间】:2013-05-16 16:41:39 【问题描述】:我正在寻找一种方法来存储用于对元素排序的Expression<Func<T, TProperty>>
集合,然后针对IQueryable<T>
对象(底层提供程序是实体框架)执行存储的列表。
例如,我想做这样的事情(这是伪代码):
public class Program
public static void Main(string[] args)
OrderClause<User> orderBys = new OrderClause<User>();
orderBys.AddOrderBy(u => u.Firstname);
orderBys.AddOrderBy(u => u.Lastname);
orderBys.AddOrderBy(u => u.Age);
Repository<User> userRepository = new Repository<User>();
IEnumerable<User> result = userRepository.Query(orderBys.OrderByClauses);
一个 order by 子句(订购的属性):
public class OrderClause<T>
public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
_list.Add(orderBySelector);
public IEnumerable<Expression<Func<T, ???>>> OrderByClauses
get return _list;
带有我的查询方法的存储库:
public class Repository<T>
public IEnumerable<T> Query(IEnumerable<OrderClause<T>> clauses)
foreach (OrderClause<T, ???> clause in clauses)
_query = _query.OrderBy(clause);
return _query.ToList();
我的第一个想法是将Expression<Func<T, TProperty>>
转换为字符串(要排序的属性名称)。所以基本上,我不是存储一个类型化的列表(这是不可能的,因为 TProperty 不是常量),而是存储一个字符串列表以及要排序的属性。
但这不起作用,因为那时我无法重建 Expression
回来(我需要它,因为 IQueryable.OrderBy 将 Expression<Func<T, TKey>>
作为参数)。
我还尝试动态创建表达式(在 Expression.Convert 的帮助下),以获得 Expression<Func<T, object>>
但后来我从实体框架中得到一个异常,它说它无法处理 Expression.Convert 语句.
如果可能,我不想使用像 Dynamic Linq Library 这样的外部库。
【问题讨论】:
顺便说一句,您的代码不起作用,您需要调用一次OrderBy()
并使用ThenBy()
进行后续调用。
正如我在问题中所说,这只是伪代码......事实上,我已经有了解决问题的方法,但是使用了我想避免的动态 Linq 库。所以你提到的订购问题已经解决了:),但还是谢谢你!
【参考方案1】:
这是dynamic
/反射解决方案可能适用的少数情况之一。
我想你想要这样的东西? (我已经阅读了字里行间,并在我认为必要的地方对您的结构进行了一些更改)。
public class OrderClauseList<T>
private readonly List<LambdaExpression> _list = new List<LambdaExpression>();
public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
_list.Add(orderBySelector);
public IEnumerable<LambdaExpression> OrderByClauses
get return _list;
public class Repository<T>
private IQueryable<T> _source = ... // Don't know how this works
public IEnumerable<T> Query(OrderClause<T> clauseList)
// Needs validation, e.g. null-reference or empty clause-list.
var clauses = clauseList.OrderByClauses;
IOrderedQueryable<T> result = Queryable.OrderBy(_source,
(dynamic)clauses.First());
foreach (var clause in clauses.Skip(1))
result = Queryable.ThenBy(result, (dynamic)clause);
return result.ToList();
关键技巧是让 C# dynamic
为我们执行可怕的重载解析和类型推断。更重要的是,我相信上面的内容,尽管使用了dynamic
,实际上是类型安全的!
【讨论】:
【参考方案2】:这样做的一种方法是将所有排序子句“存储”在类似Func<IQueryable<T>, IOrderedQueryable<T>>
(即调用排序方法的函数)中:
public class OrderClause<T>
private Func<IQueryable<T>, IOrderedQueryable<T>> m_orderingFunction;
public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
if (m_orderingFunction == null)
m_orderingFunction = q => q.OrderBy(orderBySelector);
else
// required so that m_orderingFunction doesn't reference itself
var orderingFunction = m_orderingFunction;
m_orderingFunction = q => orderingFunction(q).ThenBy(orderBySelector);
public IQueryable<T> Order(IQueryable<T> source)
if (m_orderingFunction == null)
return source;
return m_orderingFunction(source);
这样,你不必处理反射或dynamic
,所有这些代码都是类型安全的并且相对容易理解。
【讨论】:
是的,这很简单,但是这个解决方案的问题是您需要直接访问 IQueryable。我的 OrderBy 列表旨在供 GUI 使用(基本上是为了抽象查询),所以我不想在这里有一个 Queryable 对象......我更愿意在业务逻辑中管理它! @Bidou 我真的不明白你想说什么。如果您想在伪代码中保留Repository
,只需更改其代码以获取整个OrderClause
,然后执行return orderBys.Order(_query).ToList();
。
+1:这是一个非常好的解决方案,正如您所说,巧妙地避开了动态/反射。
确实是一个不错的解决方案!与dynamic
解决方案相比,它可能更难调试。我的意思是,没有简单的方法来检查 OrderClause
的各个组件(排序表达式),您必须将其应用于 IQueryable
以查看其中的内容。
@svick 用几句话来解释一切有点困难......但事实上,OrderClause您可以将 lambda 表达式作为 LambdaExpression
类型的实例存储在集合中。
或者更好的是,存储排序定义,每个定义,除了一个表达式,还存储一个排序方向。
假设你有以下扩展方法
public static IQueryable<T> OrderBy<T>(
this IQueryable<T> source,
SortDefinition sortDefinition) where T : class
MethodInfo method;
Type sortKeyType = sortDefinition.Expression.ReturnType;
if (sortDefinition.Direction == SortDirection.Ascending)
method = MethodHelper.OrderBy.MakeGenericMethod(
typeof(T),
sortKeyType);
else
method = MethodHelper.OrderByDescending.MakeGenericMethod(
typeof(T),
sortKeyType);
var result = (IQueryable<T>)method.Invoke(
null,
new object[] source, sortDefinition.Expression );
return result;
和ThenBy
的类似方法。然后你可以做类似的事情
myQueryable = myQueryable.OrderBy(sortDefinitions.First());
myQueryable = sortDefinitions.Skip(1).Aggregate(
myQueryable,
(current, sortDefinition) => current.ThenBy(sortDefinition));
这里是SortDefinition
和MethodHelper
的定义
public class SortDefinition
public SortDirection Direction
get;
set;
public LambdaExpression Expression
get;
set;
internal static class MethodHelper
static MethodHelper()
OrderBy = GetOrderByMethod();
ThenBy = GetThenByMethod();
OrderByDescending = GetOrderByDescendingMethod();
ThenByDescending = GetThenByDescendingMethod();
public static MethodInfo OrderBy
get;
private set;
public static MethodInfo ThenBy
get;
private set;
public static MethodInfo OrderByDescending
get;
private set;
public static MethodInfo ThenByDescending
get;
private set;
private static MethodInfo GetOrderByMethod()
Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.OrderBy((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
private static MethodInfo GetThenByMethod()
Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.ThenBy((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
private static MethodInfo GetOrderByDescendingMethod()
Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.OrderByDescending((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
private static MethodInfo GetThenByDescendingMethod()
Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.ThenByDescending((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
【讨论】:
效果很好,谢谢!但是,聚合可能不起作用(我没有测试它),因为如果你想进行多重排序,你需要调用 ThenBy/ThenByDescending... 其他:做查询,使用 Ani 提出的动态键更容易。基本上,它取代了您的 MethodHelper 类。但无论如何感谢您的有用回答! @Bidou:我测试了它,发现用OrderBy
聚合不起作用。将编辑答案。【参考方案4】:
在 E.F. Core 中,您可以使用以下帮助程序类
public static class OrderBuilder
public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> queryable, params Tuple<Expression<Func<TSource, object>>, bool>[] keySelectors)
if (keySelectors == null || keySelectors.Length == 0) return queryable;
return keySelectors.Aggregate(queryable, (current, keySelector) => keySelector.Item2 ? current.OrderDescending(keySelector.Item1) : current.Order(keySelector.Item1));
private static bool IsOrdered<TSource>(this IQueryable<TSource> queryable)
if (queryable == null) throw new ArgumentNullException(nameof(queryable));
return queryable.Expression.Type == typeof(IOrderedQueryable<TSource>);
private static IQueryable<TSource> Order<TSource, TKey>(this IQueryable<TSource> queryable, Expression<Func<TSource, TKey>> keySelector)
if (!queryable.IsOrdered()) return queryable.OrderBy(keySelector);
var orderedQuery = queryable as IOrderedQueryable<TSource>;
return (orderedQuery ?? throw new InvalidOperationException()).ThenBy(keySelector);
private static IQueryable<TSource> OrderDescending<TSource, TKey>(this IQueryable<TSource> queryable, Expression<Func<TSource, TKey>> keySelector)
if (!queryable.IsOrdered()) return queryable.OrderByDescending(keySelector);
var orderedQuery = queryable as IOrderedQueryable<TSource>;
return (orderedQuery ?? throw new InvalidOperationException()).ThenByDescending(keySelector);
然后将其用作.. 下面的示例带有一个名为 Player 的类,该类具有以下成员..(为简洁起见,代码已简化)
public class Player
...
public string FirstName get; set;
public int GenderTypeId get; set;
public int PlayingExperience get; set;
您可以随意组合排序、按性别排序、演奏经验降序(注意元组的 true 值)和名字..
var combinedOrder = new[]
new Tuple<Expression<Func<Player, object>>, bool>(p => p.GenderTypeId, false),
new Tuple<Expression<Func<Player, object>>, bool>(p => p.PlayingExperience, true),
new Tuple<Expression<Func<Player, object>>, bool>(p => p.FirstName, false),
;
只需按以下顺序进行操作
var data = context.Set<Player>()
.OrderBy(combinedOrder)
.ToArray();
【讨论】:
以上是关于表达式列表<Func<T, TProperty>>的主要内容,如果未能解决你的问题,请参考以下文章
Expression<Func<T,TResult>>和Func<T,TResult>
接受 Expression<Func<T>> 表达式作为参数的扩展方法
将 .net Func<T> 转换为 .net Expression<Func<T>>