Quantcast
Channel: 黑暗執行緒
Viewing all articles
Browse latest Browse all 2311

組裝動態LINQ條件的利器-LINQKit

$
0
0

昨天提到實務上查詢條件多半由使用者動態決定,往往得靠SqlQuery或Dapper配合WHERE條件字串動態組裝搞定。(提醒:SQL語法可以用串接的,使用者輸入內容務必使用參數嵌入)但如此就得放棄LINQ .Where()的強型別優點。介紹一個好物,讓你可以兩者兼顧-LINQKit

老樣子,用NuGet就可安裝,查詢關鍵字請填"LinqKit":

最新版的LINQKit需要EntityFramework 6.1.3以上,但依其運作原理,EF5應該也適用,等遇到再說。

LINQKit在Github有詳細說明,這裡只簡單介紹如何用它取代自組SQL WHERE指令。LINQKit靠PredicateBuilder建立動態條件,起手式有兩種:

var pred = PredicateBuilder.False<MyEntity>();
pred = pred.Or(o => o.Col1 == 1);
pred = pred.Or(o => o.Col2 == "X");
ctx.MyEntity.AsExpandable().Where(pred);
等同於ctx.MyEntity.Where(o => o.Col1 == 1 || o.Clo2 == "X")

var pred = PredicateBuilder.True<MyEntity>();
pred = pred.And(o => o.Col1 == 1);
pred = pred.And(o => o.Col2 == "X");
ctx.MyEntity.AsExpandable().Where(pred);
等同於ctx.MyEntity.Where(o => o.Col1 == 1 && o.Clo2 == "X")

注意,OR比對一開始用False<T>,AND則是True<T>,而EF的DbSet<T>要先.AsExpandable()才能在Where()傳入Predicate,否則會出現 The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. 錯誤。另一種做法是不加.AsExpandable(),而是.Where(pred.Expand()),二者皆可。

如果WHERE條件又有AND又有OR怎麼辦?昨天的案例剛好拿來當範例:WHERE PeriodId='2021A' AND (CostCenter='100' AND JobType='A' OR CostCenter='101' AND JobType='S')。秘訣在於Predicate的And()或Or()的傳入參數也可接受其他Predicate,故前述需求可寫成:
var pred2 = PredicateBuilder.False<MyEntity>();
pred2 = pred2.Or(o => o.CostCenter == "100" && o.JobType == "A");
pred2 = pred2.Or(o => o.CostCenter == "101" && o.JobType == "S");
var pred = PredicateBuilder.True<MyEntity>();
pred = pred.And(o => o.PeriodId == "2021A");
pred = pred.And(pred2);
ctx.DataGroup.AsExpandable().Where(pred);

很簡單吧?最後來看看昨天的案例如何用LINQKit改寫:

staticstring[] keys = newstring[] { "100-A", "101-S" };
staticvoid Test3()
{
using (var ctx = new XDBEntities())
    {
        var pOr = PredicateBuilder.False<DataGroup>();
        keys.ForEach(o =>
        {
            var p = o.Split('-');
            var costCenter = p[0];
            var jobType = p[1];
            pOr = pOr.Or(q => q.CostCenter == costCenter && q.JobType == jobType);
        });
 
        var pWhere = PredicateBuilder.True<DataGroup>();
        pWhere = pWhere.And(o => o.PeriodId == "2021A");
        pWhere = pWhere.And(pOr);
 
        ctx.Database.Log = (m) =>
        {
            Console.WriteLine("**** EF Log ****");
            Console.WriteLine(m);
        };
        var dataGrps = ctx.DataGroup.AsExpandable().Where(pWhere).ToList();
//var dataGrps = ctx.DataGroup.Where(pWhere.Expand()).ToList();
foreach (var dg in dataGrps)
        {
            Console.WriteLine("DataGroup:{0}-{1}", dg.CostCenter, dg.JobType);
        }
    }
}

順便示範如何觀察EF6產生的SQL指令,ctx.Database有個Action<string> Log屬性,覆寫成自訂函式,就能接收到EF6的Log,其中包含SQL指令,測試結果如下:

**** EF Log ****
Opened connection at 2015/10/23 上午 05:33:11 +08:00

**** EF Log ****
SELECT
    [Extent1].[PeriodId] AS [PeriodId],
    [Extent1].[CostCenter] AS [CostCenter],
    [Extent1].[JobType] AS [JobType],
    [Extent1].[Status] AS [Status],
    [Extent1].[Tag] AS [Tag]
    FROM [dbo].[DataGroup] AS [Extent1]
    WHERE ('2021A' = [Extent1].[PeriodId]) AND ((([Extent1].[CostCenter] = @p__l
inq__0) AND ([Extent1].[JobType] = @p__linq__1)) OR (([Extent1].[CostCenter] = @
p__linq__2) AND ([Extent1].[JobType] = @p__linq__3)))
**** EF Log ****


**** EF Log ****
-- p__linq__0: '100' (Type = AnsiString, Size = 8000)

**** EF Log ****
-- p__linq__1: 'A' (Type = AnsiString, Size = 8000)

**** EF Log ****
-- p__linq__2: '101' (Type = AnsiString, Size = 8000)

**** EF Log ****
-- p__linq__3: 'S' (Type = AnsiString, Size = 8000)

**** EF Log ****
-- Executing at 2015/10/23 上午 05:33:11 +08:00

**** EF Log ****
-- Completed in 11 ms with result: SqlDataReader

**** EF Log ****


**** EF Log ****
Closed connection at 2015/10/23 上午 05:33:11 +08:00

DataGroup:100-A
DataGroup:101-S

Predicate產生的WHERE條件符合我們的期望:('2021A' = [Extent1].[PeriodId]) AND ((([Extent1].[CostCenter] = @p__l inq__0) AND ([Extent1].[JobType] = @p__linq__1)) OR (([Extent1].[CostCenter] = @p__linq__2) AND ([Extent1].[JobType] = @p__linq__3)))

與自組SQL指令相比,使用LINQKit Predicate寫法相對簡潔,省去為嵌入參數命名、管理參數值陣列的瑣碎手工,又可享受LINQ強型別優勢,不用擔心「打錯字」(呃,被刺傷了),決定將LINQKit收入常備工具箱,多加利用。


Viewing all articles
Browse latest Browse all 2311

Trending Articles