簡體   English   中英

使用Lambda表達式的復雜LINQ排序

[英]Complex LINQ sorting using Lambda Expressions

是否有人擁有/知道采用表達式的IQueryable.OrderBy擴展(例如,通過反射進行檢索)? 我相信函數看起來像這樣:

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

假定Expression<Func<TEntity, T>>Expression<Func<TEntity, T>> ,其中TEntity是正在排序的同一對象,而T是創建新IQueryable時需要確定的類型。

我發現了很多采用字符串的擴展示例,包括Dynamic Linq,如下所示:

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

如果可以使用字符串並使用Reflection從相關對象中查找類型,則還可以使用Expression,並獲取Expression中正確的值類型。


以下是可能或不需要的我為什么想要這個的詳細說明。

我有很多要排序的復雜記錄。 因為列表很長,所以我更喜歡在數據庫端進行排序。 為了處理更復雜的屬性,我創建了提供排序功能的Expressions,如下所示:

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

簡單屬性的排序是使用Extension方法完成的,這些方法使用Reflection基於字符串形式傳遞的屬性的名稱來構建表達式。

這很好用,但是對於復雜的類型,我仍然需要分支邏輯,而我寧願不這樣做。 我想做的是檢查包含表達式的靜態屬性,然后簡單地應用它。 我可以得到這樣的表達式:

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'與提供的類型實參和實參不兼容。 如果方法是非泛型的,則不應該提供任何類型參數。

據我了解,您有一個Expression並希望對其進行排序/過濾。 這可能會讓您感到驚訝,它是如此簡單:

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

OrderBy 始終使用Expression因此您可以直接使用它。 如果您只有一個Expression類型的變量(指向一個Expression<Func<TEntity, T>>類型的對象)並且不知道T是什么,則可以執行以下操作:

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

dynamic將在運行時使用反射來解決這一問題。 無需自己反思。 這里需要的只是過載解析(由C#運行時綁定程序執行)。

好的,我有一個解決方案:

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創建新表達式-這使我可以使用實際的“ Type”變量,而Expression<Func<TEntity, T>>語法要求您使用編譯時已知的類型。

嘗試這個:

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

請參閱代碼項目沙箱

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM