2014年12月30日 星期二

LINQ 動態查詢

在傳統資料庫存取的程式中,若我們要篩選不同條件的資料,或者使用不同欄位的排序條件,我們都可以很簡單的直接使用串接 SQL 的方式來達到目的。 而在 LINQ 語法中,你也可以直接在 Linq 表示式中直接去組成,如下面程式碼片段:

var qry = from Ob in dc.Observe
            join S in dc.Stocks on Ob.StockID equals S.StockID
            where Ob.ObserveID == "A2" && ( S.StockName.StartsWith("台") || S.StockName.EndsWith("台") )
            orderby S.StockName

不過這樣子的做法,最大的問題就在於沒有辨法動態組成,當你條件變動時就必須更改程式碼。 在 .NET 中,雖然你也可以使用 Lambda 運算式所提供的 Where方法,來動態串接 where 條件。 如下面程式碼,你可以方便的使用 if 判斷式去動態決定想要的篩選條件:

var qry = from Ob in dc.Observe
                      join S in dc.Stocks on Ob.StockID equals S.StockID
                      select new MyObserve
                      {
                          ObserveID = Ob.ObserveID,
                          OrderNum = Ob.OrderNum,
                          StockID = Ob.StockID,
                          StockName = S.StockName
                      };

            qry = qry.Where(X => X.ObserveID.Equals("A2"));
            qry = qry.Where(X => X.StockName.StartsWith("台"));
            qry = qry.Where(X => X.StockName.EndsWith("電"));
            qry = qry.OrderBy(X => X.StockName);

但是上面這個做法最大的問題在於,它所轉譯後的 SQL 語言都是使用 And 方式串接所有的條件。下面SQL內容就是轉譯後的結果:

SELECT [t0].[StockID], [t0].[OrderNum], [t0].[ObserveID], [t1].[StockName]
FROM [dbo].[Observe] AS [t0]
INNER JOIN [dbo].[Stocks] AS [t1] ON [t0].[StockID] = [t1].[StockID]
WHERE ([t1].[StockName] LIKE '%電') AND ([t1].[StockName] LIKE '台%') AND ([t0].[ObserveID] = 'A2')
ORDER BY [t1].[StockName]

為了解決以上問題,你可以使用以下解決方法:

方法一:使用 Dynamic Query library

要處理上述問題,可以使用 Dynamic Query library ,這個類別庫擴充了許多 LINQ 的功能,而且原始碼就包含在 VS2008 的範例程式碼中( \LinqSamples\DynamicQuery\ 目錄)。 你可以在自已的專案中參考該專案,或者在網路上找到 System.Linq.Dynamic.dll 組件加入參考即可。 最後就可以將以上程式碼改寫成:

var qry = from Ob in dc.Observe
                join S in dc.Stocks on Ob.StockID equals S.StockID
                select new MyObserve
                {
                    ObserveID = Ob.ObserveID,
                    OrderNum = Ob.OrderNum,
                    StockID = Ob.StockID,
                    StockName = S.StockName
                };

    qry = qry.Where("ObserveID.Equals(@0) or StockName.StartsWith(@1) or StockName.EndsWith(@2)", "A2", "台", "台");
    qry = qry.OrderBy(S => S.StockName);
    qry = qry.OrderByDescending(Ob => Ob.ObserveID);

    dataGridView.DataSource = qry.ToList();

要使用這些擴充功能,記得要先引用 System.Linq.Dynamic 命名空間。 更多關於 Dynamic Query Library 的使用方法,可以參考範例程式碼中的例子,或者參考 ScottGu 的部落格介紹。

方法二:使用 PredicateBuilder 類別

要解決LINQ動態查詢問題,另一個方法就是使用 PredicateBuilder 類別,這是由 C# In a Nutshell 作者寫的一個類別庫,你可以參考他的這篇文章介紹:Dynamically Composing Expression Predicates

首先你必須將 PredicateBuilder 類別加到你的專案中,程式碼如下:

public static class PredicateBuilder
{
  public static Expression<Func<T, bool>> True<T> ()  { return f => true;  }
  public static Expression<Func<T, bool>> False<T> () { return f => false; }
 
  public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
                                                      Expression<Func<T, bool>> expr2)
  {
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
          (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
  }
 
  public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
                                                       Expression<Func<T, bool>> expr2)
  {
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
          (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
  }
}

接著你就可以利用這個靜態類別的方法來串接你所需要的 where 條件:

var predicate = PredicateBuilder.False<MyObserve>();

    predicate = predicate.Or(mo => mo.ObserveID.Equals("A2"));
    predicate = predicate.Or(mo => mo.StockName.StartsWith("台"));
    predicate = predicate.Or(mo => mo.StockName.EndsWith("台"));

最後只要建立的 PredicateBuilder 物件,當做 Where 方法的參數即可。

var qry = (from Ob in dc.Observe
                join S in dc.Stocks on Ob.StockID equals S.StockID
                select new MyObserve
                {
                    ObserveID = Ob.ObserveID,
                    OrderNum = Ob.OrderNum,
                    StockID = Ob.StockID,
                    StockName = S.StockName
                })
                .Where(predicate);

    dataGridView.DataSource = qry.ToList();

沒有留言:

張貼留言