[LINQ] 使用 LINQKit PredicateBuilder 解決動態OR條件查詢窘境

  • 17526
  • 0
  • LINQ
  • 2017-04-21

使用 LINQKit PredicateBuilder 解決動態OR條件查詢窘境

問題簡述

 

在使用傳統SQL語法方式來進行DB資料撈取時,當面對搜尋條件是動態且不固定時,我們往往會使用SQL語法拼裝的方式來解決;當進入Entity Framework世界後就必須與LINQ搏鬥,以下將舉例說明此篇文章所面臨之問題,以及要如何利用LINQKit PredicateBuilder來排除。

 

以商品搜尋功能為例,使用者可透過商品名稱與生產工廠(AND)來搜尋指定商品

image

 

搜尋功能邏輯大致如下,可直接使用Where方法串接來達到AND累加條件作用

public ActionResult Index(string productName, string manufactory)
{
	// 搜尋條件為 = 產品名稱 AND 生產工廠
	
    IQueryable<Product> products = db.Products;

    // 如果有輸入產品名稱作為搜尋條件時
    if (!string.IsNullOrWhiteSpace(productName))
    {products = products.Where(p => p.Name.Contains(productName)); }

	// 如果有輸入生產工廠作為搜尋條件時
    if (!string.IsNullOrWhiteSpace(manufactory))
    { products = products.Where(p => p.Manufactory.Contains(manufactory)); }


    // 回傳搜尋結果
    return View(products.Include(p => p.ProductType));
}

 

此時需求加入,希望能以商品類別作為搜尋條件(OR),畫面如下

image

 

由於產品類型條件將是以OR作為搜尋條件的累加,而LINQ中WHERE串連是以AND作為條件結合,明顯的不符合我們的需求(瓶頸出現),此時就是考驗工程師解決問題(Google)能力啦! 所幸小弟遇到的問題其實前輩們早已突破,這盞明燈就是LINQKit中提供的PredicateBuilder類別工具,以下將對此進行問題排除實作。

 

 

解決方案

 

LINQKit提供了許多LINQ to SQL / EF的擴充功能(Extensions),可以讓使用者在面對LINQ先天缺憾的時候有一道曙光出現。在此我們僅使用到PredicateBuilder來解決問題,有興趣的朋友可以進一步探索其他功能。

 

首先,了解用法最快方式就是將原本的Code調整套用PredicateBuilder作為搜尋條件,請先加入LinqKit命名空間。其中PredicateBuilder.True<Product>()表示搜尋初始條件為True,通常用於AND累加條件上(若是用於OR累加條件上,無論其他條件為何皆成立);反之若是使用OR累加條件時,則會使用PredicateBuilder.False<Product>()作為初始條件。此用意在於無任何搜尋條件時仍可回傳正確資料。

image

 

再來就是處理本篇重點OR條件累加。做法與AND條件差不多,不同的部分就是給予False為初始條件,因此在未勾選任何商品類別情況下,由於初始值是False故不回傳任何資料。最後在透過原有Where串接特性來結合搜尋條件1(predicate)及搜尋條件2(predicateProductType),依照輸入值來撈取正確資料,做法如下。

image

 

public ActionResult Index(string productName, string manufactory, int[] productTypes)
{
   
    IQueryable<Product> products = db.Products;

    // #1# 搜尋條件為 = True AND 產品名稱 AND 生產工廠
    var predicate = PredicateBuilder.True<Product>();

    // 如果有輸入產品名稱作為搜尋條件時
    if (!string.IsNullOrWhiteSpace(productName))
    { predicate = predicate.And(p => p.Name.Contains(productName)); }

    // 如果有輸入生產工廠作為搜尋條件時
    if (!string.IsNullOrWhiteSpace(manufactory))
    { predicate = predicate.And(p => p.Manufactory.Contains(manufactory)); }


    // #2# 搜尋條件為 = False OR 產品類型1 OR 產品類型2 ...
    var predicateProductType = PredicateBuilder.False<Product>();

    // 如果有輸入產品類型作為搜尋條件時
    if (productTypes!=null)
    {
        foreach (var type in productTypes)
        { predicateProductType = predicateProductType.Or(p => p.ProductTypeId == type); }
    }


    // #3# 搜尋條件為 = True AND 產品名稱 AND 生產工廠 AND (False OR 產品類型1 OR 產品類型2 ...)
    // 回傳搜尋結果
    return View(products.AsExpandable().Where(predicate)
                                       .Where(predicateProductType).Include(p => p.ProductType));
 
}

 

最後進行驗證,預設搜尋條件下取得所有資料

image

 

[測試一] 正確撈出名稱包含外套的所有商品

image

 

[測試二] 正確撈出中國製的服飾或家電商品

image

 

 

LINQPad中使用PredicateBuilder方法

 

相信許多人都會使用LINQPad這套工具來測試自己的LINQ語法,當然我們也可以在LINQPad中使用PredicateBuilder所帶來的方便性,話不多說兩個步驟就解決了,請參考以下說明。

 

首先,按下F4鍵叫出Query Properties設定畫面,勾選Include PredicateBuilder選項

image

 

加入LinqKit.dll 參考 (請自行至LINQKit官網下載)

image

 

最後切換頁面至Additional Namespace Imports後,新增命名空間LinqKit並設為預設值即可

image

 

如此即可在LINQPad中使用LINQKit PredicateBuilder

image

 

參考資要

 

http://www.albahari.com/nutshell/linqkit.aspx

http://www.albahari.com/nutshell/predicatebuilder.aspx

http://stackoverflow.com/questions/16462692/nesting-predicatebuilder-predicates-the-parameter-f-was-not-bound-in-the-sp

http://kelp.phate.org/2011/12/linq-to-object-linqkitpredicatebuilder.html


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !