天天看點

[C#.NET 拾遺補漏]13:動态建構LINQ查詢表達式

最近工作中遇到一個這樣的需求:在某個清單查詢功能中,可以選擇某個數字列(如商品單價、當天銷售額、當月銷售額等),再選擇

小于或等于

大于或等于

,再填寫一個待比較的數值,對資料進行查詢過濾。

如果隻有一兩個這樣的數字列,那麼使用 Entity Framework Core 可以這麼寫 LINQ 查詢:

public Task<List<Product>> GetProductsAsync(string propertyToFilter, MathOperator mathOperator, decimal value)
{
    var query =  _context.Products.AsNoTracking();

    query = propertyToFilter switch
    {
        "Amount1" when mathOperator == MathOperator.LessThanOrEqual => query.Where(x => x.Amount1 <= value),
        "Amount1" when mathOperator == MathOperator.GreaterThanOrEqual => query.Where(x => x.Amount1 >= value),

        "Amount2" when mathOperator == MathOperator.LessThanOrEqual => query.Where(x => x.Amount2 <= value),
        "Amount2" when mathOperator == MathOperator.GreaterThanOrEqual => query.Where(x => x.Amount2 >= value),

        _ => throw new ArgumentException($"不支援 {propertyToFilter} 列作為數字列查詢", nameof(propertyToFilter))
    };

    return query.ToListAsync();
}
           

如果固定隻有一兩個數字列且将來也不會再擴充,這樣寫簡單粗暴,也沒什麼問題。

但如果有幾十個數字列,這樣使用

swith

模式比對的寫法就太恐怖了,代碼大量重複。很自然地,我們得想辦法根據屬性名動态建立

Where

方法的參數。它的參數類型是:

Expression<Func<T, bool>>

,是一個表達式參數。

要知道如何動态建立一個類似

Expression<Func<T, bool>>

類型的表達式執行個體,就要知道如何拆解表達式樹。

對于本示例,以

x => x.Amount1 <= value

表達式執行個體為例,它的表達式樹是這樣的:

然後我們可以按照此表達式樹結構來建構我們的 LINQ 表達式:

public Task<List<Product>> GetProductsAsyncV2(string propertyToFilter, MathOperator mathOperator, decimal value)
{
    var query = _context.Products.AsNoTracking();

    var paramExp = Expression.Parameter(typeof(Product));
    var memberExp = Expression.PropertyOrField(paramExp, propertyToFilter);
    var valueExp = Expression.Constant(value);
    var compareExp = mathOperator == MathOperator.LessThanOrEqual ?
        Expression.LessThanOrEqual(memberExp, valueExp) :
        Expression.GreaterThanOrEqual(memberExp, valueExp);
    var lambda = Expression.Lambda<Func<Product, bool>>(compareExp, paramExp);

    return query.Where(lambda).ToListAsync();
}
           

每個

Expression.XXX

靜态方法傳回的都是一個以

Expression

為基類的執行個體,代表一個表達式。不同的表達式又可以組成一個新的表達式,直到得到我們需要的 Lambda 表達式。這樣就形成了一種樹形結構,我們稱為表達式樹。知道如何把一個最終的查詢表達式拆解成表達式樹,我們就容易動态建構此查詢表達式。

得到一個表達式後,我們還可以動态編譯并調用該表達式,比如上面示例得到的

lambda

變量,是一個

Expression<Func<Product, bool>>

類型,調用其

Compile

方法,可以得到

Func<Product, bool>

類型的委托。

...

var toTestProduct = new Product { Amount1 = 100, Amount2 = 200 };

Func<Product, bool> func = lambda.Compile();
var result = func(toTestProduct);

Console.WriteLine($"The product's {propertyToFilter} is to {mathOperator} {value}.");

// Output: The product's Amount1 is LessThanOrEqual to 150.
           

你可以通過研究

Expression

類來了解更多動态建構表達式的方法。

動态建構 LINQ 表達式對于不能在編譯時建立查詢,隻能在運作時建立查詢的場景很有用。但它的缺點也很明顯,不易維護、不易閱讀、不易調試。如果最終的表達式執行出錯,很難通過調試來發現具體是建構中的那一步寫錯了,隻能憑自己的了解和經驗查找錯誤。是以,如非必須,一般不推薦動态建構 LINQ 查詢表達式。

作者:精緻碼農-王亮

出處:http://cnblogs.com/willick

聯系:[email protected]

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。

繼續閱讀