c# 擴展方法 奇思妙用 高級篇 九:OrderBy(string propertyName, bool desc)


下面是 Queryable 類 中最常用的兩個排序的擴展方法:

1
2
    public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
    public static IOrderedQueryable<TSource> OrderByDescending<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);

算上另外兩個復雜點的,一共是四個方法,都是強類型的。

雖然強類型優點多多,但有些情況下確顯得不夠靈活。

強類型的缺點

比如 web 應用中有如下 Url:

在代碼中我們如何寫出強類型的查詢?

1
2
3
4
IQueryable<Order> query = /**/;
string propertyName = /*從請求中獲取,OrderDate*/;
bool desc = /*從請求中獲取,true*/;
var data = query.Where(/*TODO: 如何寫*/).ToArray();

單憑 Queryable 類 中定義的 OrderBy 和 OrderByDescending, 是不可能簡單直接寫出來的,除非硬編碼。

那有如此做到靈活呢?我們從 Queryable 類 定義的 OrderBy 和 OrderByDescending 方法下手,它們均有一個 Expression<Func<TSource, TKey>> 類型的 keySelector 參數。

先來試下能不能動態構建一個 keySelector。

動態構建 keySelector 參數

此部分要求對表達式樹有一定了解,可查看:http://msdn.microsoft.com/zh-cn/library/bb397951(v=VS.100).aspx

代碼則相當簡單:

1
2
3
4
5
6
var type = typeof(Order);
var propertyName = "OrderDate";
//
var param = Expression.Parameter(type, type.Name);
var body = Expression.Property(param, propertyName);
var keySelector = Expression.Lambda(body, param);

最后三行代碼動態構造了一顆表達式樹:

image

和我們使用 lambda 表達式寫出的效果是完全一樣的:

image

這步比較順利,下面來看如何調用:

調用 OrderBy

直接傳入調用是不行的:

1
repository.OrderBy(keySelector);

因為前面構建的 keySelector 是 LambdaExpression 類型的,而 OrderBy 要求是 Expression<Func<Order, DateTime>> 。

但實質上 keySelector 就是 OrderBy 要求的類型:

image

因為強類型,居然不認自家人了!

可以通過強制類型轉換來解決,編譯運行都沒問題:

1
repository.OrderBy((Expression<Func<Order, DateTime>>)keySelector);

但這樣一來,又成了硬編碼。

我們期望靈活,解決方法有很多種,這里只介紹最簡單的一種,借助 .net 4 中 dynamic

1
var orderedQueryable = Queryable.OrderBy(repository, (dynamic)keySelector);

因為擴展方法是不能被動態調用的(Extension methods cannot be dynamically dispatched),所以寫成上面樣子。

或將 keySelector 聲明為 dynamic

1
2
dynamic keySelector = Expression.Lambda(body, param);
var orderedQueryable = Queryable.OrderBy(repository, keySelector);

OK,搞定!根據屬性名排序太常用了,遂提取成了擴展方法:

OrderBy 擴展方法

將上面代碼整理下,擴展方法就出來了:

1
2
3
4
5
6
7
8
9
10
11
public static class QueryableExtensions {
    public static IQueryable<T> OrderBy<T>(this IQueryable<T> queryable, string propertyName) {
        return OrderBy(queryable, propertyName, false);
    }
    public static IQueryable<T> OrderBy<T>(this IQueryable<T> queryable, string propertyName, bool desc) {
        var param = Expression.Parameter(typeof(T));
        var body = Expression.Property(param, propertyName);
        dynamic keySelector = Expression.Lambda(body, param);
        return desc ? Queryable.OrderByDescending(queryable, keySelector) : Queryable.OrderBy(queryable, keySelector);
    }
}

注意,上面代碼執行沒問題,但效率不好。因為每次都要動態生成表達式樹,另外動態調用也會造成一定性能損失。

想提高效率的話,可把動態生成的表達式樹緩存起來,參考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static class QueryableExtensions {
    public static IQueryable<T> OrderBy<T>(this IQueryable<T> queryable, string propertyName) {
        return QueryableHelper<T>.OrderBy(queryable, propertyName, false);
    }
    public static IQueryable<T> OrderBy<T>(this IQueryable<T> queryable, string propertyName, bool desc) {
        return QueryableHelper<T>.OrderBy(queryable, propertyName, desc);
    }
    static class QueryableHelper<T> {
        private static Dictionary<string, LambdaExpression> cache = new Dictionary<string, LambdaExpression>();
        public static IQueryable<T> OrderBy(IQueryable<T> queryable, string propertyName, bool desc) {
            dynamic keySelector = GetLambdaExpression(propertyName);
            return desc ? Queryable.OrderByDescending(queryable, keySelector) : Queryable.OrderBy(queryable, keySelector);
        }
        private static LambdaExpression GetLambdaExpression(string propertyName) {
            if (cache.ContainsKey(propertyName)) return cache[propertyName];
            var param = Expression.Parameter(typeof(T));
            var body = Expression.Property(param, propertyName);
            var keySelector = Expression.Lambda(body, param);
            cache[propertyName] = keySelector;
            return keySelector;
        }
    }
}

這里並發不是多大問題,如若考慮,可使用 ConcurrentDictionary<TKey, TValue> 類

使用

很方便的:

1
2
var data1 = productRepository.OrderBy("Name");
var data2 = orderRepository.OrderBy("OrderDate", true);

 

c#擴展方法奇思妙用》系列文章已有 25 篇,歡迎閱讀

PS:簡單編碼,快樂生活!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM