【问题标题】:Complex LINQ sorting using Lambda Expressions使用 Lambda 表达式的复杂 LINQ 排序
【发布时间】:2013-03-08 20:14:15
【问题描述】:

是否有人拥有/知道采用表达式(例如,通过反射检索)的 IQueryable.OrderBy 扩展?我相信这个函数看起来像这样:

public static IQueryable<TEntity> OrderBy<TEntity>
    (this IQueryable<TEntity> source, Expression sortExpression)

表达式将被假定为 Expression&lt;Func&lt;TEntity, T&gt;&gt;,其中 TEntity 是被排序的同一对象,而 T 是需要确定的类型才能创建新的 IQueryable。

我发现了许多采用字符串的扩展示例,包括 Dynamic Linq,如下所示:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, string sortExpression) 

如果可以获取字符串并使用反射从相关对象中查找类型,那么也应该可以获取表达式,并获取表达式中的值类型。


以下是我为什么想要这个的详细解释,你可能需要也可能不需要。

我有一个相当大的复杂记录列表要排序。因为列表很长,我更喜欢在数据库端进行排序。为了处理更复杂的属性,我创建了提供排序功能的表达式,如下所示:

if (model.sortExpression == "PlannedValue")
{
    Expression<Func<BDopp, decimal>> sorter = BDopp.PlannedValueSorter;         
    if (model.sortDirection == "DESC")
        opps = opps.OrderByDescending(sorter).AsQueryable();
    else
        opps = opps.OrderBy(sorter).AsQueryable();
}

BDOpp.PlannedValueSorter 从对象中检索一个静态表达式,该表达式允许在没有 opps 的情况下进行排序仍然是 IQueryable 类型:

public static Expression<Func<BDopp, decimal>> PlannedValueSorter
    {
        get
        {
            return z => z.BudgetSchedules
                .Where(s => s.Type == 1)
                .Sum(s => s.Value * s.Workshare * z.valueFactor / 100 / 100);
        }
    }

简单属性的排序是使用扩展方法完成的,这些方法使用反射来构建基于作为字符串传递的属性名称的表达式。

这很好用,但是对于复杂的类型,我仍然需要分支逻辑,我宁愿不这样做。我宁愿检查包含表达式的静态属性,然后简单地应用它。我可以得到这样的表达:

PropertyInfo info = typeof(BDopp).GetProperty(model.sortExpression + "Sorter",
    BindingFlags.Static | BindingFlags.Public);
Expression expr = (Expression)info.GetValue(null, null);

对于 PlannedValue 属性,这让我得到了在 PlannedValueSorter 中排序的表达式,我已经知道它有效。


更新:

各种挖掘让我得到了我认为可能取得一些进展的东西:

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression)
    {
        var unary = sortExpression.Body as UnaryExpression;
        Type actualExpressionType = unary.Operand.Type;

actualExpressionType 实际上是表达式的返回类型(对于这个特定的属性,它是十进制)。

不幸的是,我主要是通过反复试验来工作,因为我还没有完全理解这一切是如何工作的,所以我尝试像这样更新查询是行不通的:

        MethodCallExpression resultExp = Expression.Call(typeof(Queryable), 
            "OrderBy",
            new Type[] { typeof(TEntity), actualExpressionType },
            source.Expression, sortExpression);
        return source.Provider.CreateQuery<TEntity>(resultExp);

编译正常,但运行时抛出如下错误:

“System.Linq.Queryable”类型上没有通用方法“OrderBy” 与提供的类型参数和参数兼容。无类型 如果方法是非泛型的,则应提供参数。

【问题讨论】:

  • 普通的OrderBy() 已经接受了一个表达式。为什么你不能使用它?
  • 普通的OrderBy需要强类型的表达式,但是不设置一长串的分支条件,编译的时候是不知道类型的。

标签: c# .net linq expression-trees


【解决方案1】:

试试这个:

public static IEnumerable<T> OrderBy<T>(
       this IEnumerable<T> collection, 
       string columnName
       //, SortDirection direction
)
{
    ParameterExpression param = Expression.Parameter(typeof(T), "x");   // x
    Expression property = Expression.Property(param, columnName);       // x.ColumnName
    Func<T, object> lambda = Expression.Lambda<Func<T, object>>(        // x => x.ColumnName
            Expression.Convert(property, typeof(object)),
            param)
        .Compile();

    Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression = (c, f) => c.OrderBy(f); // here you can use OrderByDescending basing on SortDirection

    IEnumerable<T> sorted = expression(collection, lambda);
    return sorted;
}

用法:

IEnumerable<T> collection = ...
IEnumerable<T> ordered = collection.OrderBy("PropName");

参见Code Projectsandbox

【讨论】:

  • 我已经有一个覆盖可以做到这一点 - 接受一个字符串,在 T 中找到该属性,确定类型,并创建一个排序表达式。但是,在这种情况下,“PlannedValue”不是 T 的简单属性。它是一个表达式。如果我知道表达式的返回值,它可以很容易地转换为 Linq,但我试图弄清楚如何在运行时找到表达式的返回值。
【解决方案2】:

据我了解,您有一个 Expression 并希望按它订购/过滤。它的简单程度可能会让您感到惊讶:

Expression<Func<TEntity, T>> sortExpr = ...; //you already have this
var ordered = myQuery.OrderBy(sortExpr);

OrderBy always 使用Expression,因此您可以直接使用它。如果你只有一个Expression 类型的变量(它指向一个Expression&lt;Func&lt;TEntity, T&gt;&gt; 类型的对象)并且静态地不知道T 是什么,你可以这样做:

Expression sortExpr = ...; //you already have this
var ordered = myQuery.OrderBy((dynamic)sortExpr);

dynamic 将在运行时使用反射解决这个问题。无需自己进行反思。这里只需要重载解析(由 C# 运行时绑定器执行)。

【讨论】:

  • 我实际上已经尝试过这个,并得到一个错误,抱怨“无法动态调度扩展方法。考虑强制转换动态参数或调用没有扩展方法语法的扩展方法。”也许我错过了参考?
  • 啊,是的。使用Queryable.OrderBy(myQuery, (dynamic)sortExpr)。解决方法是不使用扩展方法语法!
  • 唉。无法将类型“System.Decimal”转换为类型“System.Object”。 LINQ to Entities 仅支持转换 EDM 基元或枚举类型。
  • 但是,那个错误让我得到了这个答案,这导致了解决方案:stackoverflow.com/a/9155222/135105
  • 嗯,我觉得我们还没有找到问题的根源(这总是让我感到困扰),因为我不明白为什么会出现该错误消息,但我很高兴你的问题是解决了。​​
【解决方案3】:

好的,我有办法了:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression, 
    bool descending)
    {
        var unary = sortExpression.Body as UnaryExpression;
        var operand = unary.Operand;
        Type actualExpressionType = operand.Type;

        MethodCallExpression resultExp = 
            Expression.Call(typeof(Queryable), 
                descending? "OrderByDescending" : "OrderBy",
                new Type[] { typeof(TEntity), actualExpressionType },
                source.Expression, 
                Expression.Lambda(operand, sortExpression.Parameters));
        return source.Provider.CreateQuery<TEntity>(resultExp);
    }

bool 降序是为了允许标准的 OrderBy 和 OrderByDescending 重载。这里有两个重大突破,至少对我来说:

  1. 从表达式中取出操作数。
  2. 使用 Expression.Call 和 Expression.Lambda 创建新表达式 - 这允许我使用实际的“类型”变量,而 Expression&lt;Func&lt;TEntity, T&gt;&gt; 语法要求您使用在编译时已知的类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2013-07-12
    相关资源
    最近更新 更多