【问题标题】:List of Expression<Func<T, TProperty>>表达式列表<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 库。所以你提到的订购问题已经解决了:),但还是谢谢你!

标签: c# .net entity-framework lambda expression-trees


【解决方案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();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多