[Expression Tree] MSDN學習系列 3-使用運算式樹狀結構建置動態查詢

   本系列的最後一篇文章,此篇要講的是如何使用運算式樹狀架構來建立動態 (Dynamic) 的 LINQ 查詢,並在執行時期產生對應的查詢方法。在開始先來講為何需要動態查詢? 假設我們有一個GridView呈現產品清單,但要提供使用者直接在每個欄位的標題點選作排序、飾選的功能。若要依使用者點選的欄位來查詢的話,程式需依欄位名稱來各別寫Where或Orderby,如下程式
private void TraditionQuery()
{
    string sortColumn = "ProductName";
    string filterColumn = "CategoryName";
    string filterValue = "HTC";
    IQueryable<ProductModel> data = ProductModel.GetDatas();
    switch (sortColumn)
    {
        case "CategoryName":
            data = data.OrderBy(m => m.CategoryName);
            break;
        case "ProductName":
            data = data.OrderBy(m => m.ProductName);
            break;
    }

    switch (filterColumn)
    {
        case "CategoryName":
            data = data.Where(m => m.CategoryName == filterValue);
            break;
        case "ProductName":
            data = data.Where(m => m.ProductName == filterValue);
            break;
    }

}

目前只針對2個欄位作查詢,若有n個欄位.....不就...switch到天長地久..=.=。當然不可能這樣作,我們可以使用更有效率的方法!



使用CreateQuery建立動態查詢


以下示範透過Expression.Call叫用IQueryable本身實作的OrderBy及Where方法,再透過IQueryable 資料來源提供者的 CreateQuery<TElement>(Expression)來建立查詢結果。

OrderBy

private static IQueryable<ProductModel> OrderBy(IQueryable<ProductModel> source, string sortColumn)
{
    //傳入參數 如:m
    ParameterExpression pe = Expression.Parameter(source.ElementType, "m");
    //回傳欄位 如:m.ProductName
    Expression sortExp = Expression.Property(pe, typeof(ProductModel).GetProperty(sortColumn));
    //Lamba運算式 如:m=>m.ProductName
    Expression body = Expression.Lambda<Func<ProductModel, string>>(sortExp, new ParameterExpression[] { pe });
    //呼叫OrderBy方法
    MethodCallExpression OrderByCallExpression = Expression.Call(
                    typeof(Queryable),//來源型別
                    "OrderBy",//指定方法名稱
                    new Type[] { typeof(ProductModel), typeof(string) },//body lamba使用到Expression型別,例:sortExp及pe
                    source.Expression,//來源運算式
                    body);
    var result = (IQueryable<ProductModel>)source.Provider.CreateQuery(OrderByCallExpression);
    return result;
}

Where

private static IQueryable<ProductModel> Where(IQueryable<ProductModel> source, string filterColumn, string filterValue)
{
    //傳入參數 如:m
    ParameterExpression pe = Expression.Parameter(source.ElementType, "m");
    //回傳欄位 如:m.CategoryName
    Expression filterExp = Expression.Property(pe, typeof(ProductModel).GetProperty(filterColumn));
    //條件值 如:"HTC"
    ConstantExpression valueExp = Expression.Constant(filterValue, typeof(string));
    //等於條件主體 如:m.CategoryName=="HTC"
    Expression predicateBody = Expression.Equal(filterExp, valueExp);
    //Lamba運算式 
    Expression body = Expression.Lambda<Func<ProductModel, bool>>(predicateBody, new ParameterExpression[] { pe });
    //呼叫Where方法
    MethodCallExpression whereCallExpression = Expression.Call(
                    typeof(Queryable),//來源型別
                    "Where",//指定方法名稱
                    new Type[] { typeof(ProductModel) },//body lamba使用到Expression型別,例:pe
                    source.Expression,//來源運算式
                    body);
    var result = (IQueryable<ProductModel>)source.Provider.CreateQuery(whereCallExpression);
    return result;
}


測試程式

static void Main(string[] args)
{
    DynamicQuery();
    Console.Read();
}
private static void DynamicQuery()
{
    string sortColumn = "ProductName";
    string filterColumn = "CategoryName";
    string filterValue = "HTC";
    IQueryable<ProductModel> data = ProductModel.GetDatas();
    data = OrderBy(data, sortColumn);
    data = Where(data, filterColumn, filterValue);
    foreach (var item in data)
    {
        Console.WriteLine(item.ProductName);
    }
}

結論


透過動態查詢可以幫我們撰寫更精簡、效率的程式。而此範例還有更多改善空間,例如:使用泛型或使用解析字串.等等。剩下的就由您自行發揮嘿
ps:實務上是更複雜滴....

參考來源


http://msdn.microsoft.com/zh-tw/library/bb882637.aspx

範例程式


https://github.com/kimx/ExpressionLab

這個網誌中的熱門文章

[.NET Core] 將專案發行至IIS

[TFS] 分支與合併