最近工做中遇到一個這樣的需求:在某個列表查詢功能中,能夠選擇某個數字列(如商品單價、當天銷售額、當月銷售額等),再選擇 小於或等於
和 大於或等於
,再填寫一個待比較的數值,對數據進行查詢過濾。調試
若是隻有一兩個這樣的數字列,那麼使用 Entity Framework Core 能夠這麼寫 LINQ 查詢:code
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(); }
若是固定只有一兩個數字列且未來也不會再擴展,這樣寫簡單粗暴,也沒什麼問題。blog
但若是有幾十個數字列,這樣使用 swith
模式匹配的寫法就太恐怖了,代碼大量重複。很天然地,咱們得想辦法根據屬性名動態建立 Where
方法的參數。它的參數類型是:Expression<Func<T, bool>>
,是一個表達式參數。ci
要知道如何動態建立一個相似 Expression<Func<T, bool>>
類型的表達式實例,就要知道如何拆解表達式樹。string
對於本示例,以 x => x.Amount1 <= value
表達式實例爲例,它的表達式樹是這樣的:it
而後咱們能夠按照此表達式樹結構來構建咱們的 LINQ 表達式:io
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>
類型的委託。class
... 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 查詢表達式。