表达式列表<Func<T, TProperty>>

Posted

技术标签:

【中文标题】表达式列表<Func<T, TProperty>>【英文标题】:List of Expression<Func<T, TProperty>> 【发布时间】:2013-05-16 16:41:39 【问题描述】:

我正在寻找一种方法来存储用于对元素排序的Expression&lt;Func&lt;T, TProperty&gt;&gt; 集合,然后针对IQueryable&lt;T&gt; 对象(底层提供程序是实体框架)执行存储的列表。

例如,我想做这样的事情(这是伪代码):

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&lt;Func&lt;T, TProperty&gt;&gt; 转换为字符串(要排序的属性名称)。所以基本上,我不是存储一个类型化的列表(这是不可能的,因为 TProperty 不是常量),而是存储一个字符串列表以及要排序的属性。

但这不起作用,因为那时我无法重建 Expression 回来(我需要它,因为 IQueryable.OrderBy 将 Expression&lt;Func&lt;T, TKey&gt;&gt; 作为参数)。

我还尝试动态创建表达式(在 Expression.Convert 的帮助下),以获得 Expression&lt;Func&lt;T, object&gt;&gt; 但后来我从实体框架中得到一个异常,它说它无法处理 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&lt;IQueryable&lt;T&gt;, IOrderedQueryable&lt;T&gt;&gt;(即调用排序方法的函数)中:

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 更像是一个 QueryOption 我有 OrderBy 以及其他类似的东西where predicate, paging, ... 这样做的目的是向 GUI 提供创建查询的可能性,而无需知道它在业务逻辑中的工作原理(QueryOption 与 DTO 一起使用,例如 QueryOption 但是我的 Repository 带有一个实体对象,例如 QueytionOption。这意味着我正在两者之间的某个地方进行映射...)【参考方案3】:

您可以将 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));

这里是SortDefinitionMethodHelper的定义

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>>

Expression<Func<T,bool>> 声明是啥意思?

如何设置属性选择器的值 Expression<Func<T,TResult>>