0

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

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

将假定表达式是Expression<Func<TEntity, T>>其中 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 实际上是 Expression 的返回类型(对于这个特定的属性,它是十进制)。

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

        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”与提供的类型参数和参数兼容。如果方法是非泛型的,则不应提供类型参数。

4

3 回答 3

1

好的,我有一个解决方案:

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);
    }

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

  1. 从表达式中取出操作数。
  2. 使用 Expression.Call 和 Expression.Lambda 创建新表达式 - 这允许我使用实际的“类型”变量,而Expression<Func<TEntity, T>>语法要求您使用在编译时已知的类型。
于 2013-03-20T20:18:46.570 回答
1

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

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

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

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

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

于 2013-03-19T22:36:41.857 回答
0

尝试这个:

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");

请参阅代码项目沙箱

于 2013-03-19T22:34:54.253 回答