C#表達式樹Expression基礎講解
什麼是表達式樹
表達式樹以樹形數據結構表示代碼,其中每一個節點都是一種表達式,比如方法調用和 x < y 這樣的二元運算等。可以對表達式樹中的代碼進行編輯和運算。 這樣能夠動態修改可執行代碼、在不同數據庫中執行 LINQ 查詢以及創建動態查詢。 表達式樹還能用於動態語言運行時 (DLR) 以提供動態語言和 .NET 之間的互操作性,同時保證編譯器編寫員能夠發射表達式樹而非 Microsoft 中間語言 (MSIL)。 這段話是來自官網( [表達式樹 (C#) | Microsoft Docs](表達式樹 (C#) | Microsoft Docs) )的定義。
在 C# 中,我們可以通過 Expression 的方式來手動創建表達式樹,比如:
[HttpGet] public IActionResult Expression() { // 查詢 年齡Age 大於 18 的元素 Expression<Func<User,bool>> expression1 = x => x.Age > 18; return Ok(); }
那麼,x.Age > 18 這一表達式,它的樹狀結構是這樣的:
通過 Visual Studio 自帶的查看變量或添加監視的方式,我們可以發現其中 樹的根節點(NodeType)是 GreaterThan,左節點(Left)是 x.Age,右節點(Right)是 18。所以由此就可以大概畫出樹狀結構。
最後,通過這種樹狀結構,C# 就可以幫我們將表達式編譯成具體的 SQL 執行語句。
如果想更清晰的查看表達式樹的結構,可以 nuget 一個包( ExpressionTreeToString ),將表達式結構轉換成字符串
PM> Install-Package ZSpitz.Util -Version 0.1.116
Expression<Func<User, bool>> expression = u => u.Age >= 18; var treeStr = expression.ToString("Object notation", "C#"); // 輸出為下面字符串 var u = new ParameterExpression { Type = typeof(User), IsByRef = false, Name = "u" }; new Expression<Func<User, bool>> { NodeType = ExpressionType.Lambda, Type = typeof(Func<User, bool>), Parameters = new ReadOnlyCollection<ParameterExpression> { u }, Body = new BinaryExpression { NodeType = ExpressionType.GreaterThanOrEqual, Type = typeof(bool), Left = new MemberExpression { Type = typeof(int), Expression = u, Member = typeof(User).GetProperty("Age") }, Right = new ConstantExpression { Type = typeof(int), Value = 18 } }, ReturnType = typeof(bool) }
Expression 和 Func 的區別
- Expression 存儲瞭運算邏輯,可以將其保存成抽象語法樹(AST),可以在運行時動態獲取運算邏輯。
- Func 隻是存儲瞭結果,無法保存成語法樹,也無法動態獲取運算邏輯。
所以,在 EFCore 中,使用表達式對數據庫數據進行查詢中,我們應該選擇 Expression 而不是 Func,因為使用瞭 Func ,實際上並無法將 Func 中的表達式轉換成 SQL,而是在將所有數據加載到內存後,在內存中在過濾 Func 中的條件。
簡單來說就是,此時要篩選 User 表中年齡大於18的數據,可以有這兩種寫法
// 這種寫法,實際生成的 SQL 語句, 大概是這樣的 SELECT * FROM User as T WHERE T.age > 18 Expression<Func<User,bool>> expression1 = x => x.Age > 18; dbContext.User.Where(expression1).toList(); // 而這種, 生成的語句是這樣的 SELECT * FROM User, 然後將 User 表中所有數據加載到內存中後, 在進行 age > 18 的過濾 Func<User, bool> func1 = x => x.Age > 18; dbContext.User.Where(func1).toList();
通過代碼創建表達式樹
- ParameterExpression
- BinaryExpression
- MethodCallExpression
- ConstantExpression
這些類幾乎都沒有提供構造方法,而且所有的屬性都幾乎隻是隻讀。因此我們一般不會直接創建這些類的實例,而是調用 Expression 類的 Parameter、MakeBinary、Call、Constant等靜態方法來生成,這些靜態方法我們一般稱作創建表達式樹的工廠方法,而屬性則通過方法參數類設置。
動態將表達式:u => u.Age >= 18; 通過代碼構建出來
一般構建步驟:
- 先創建 ParameterExpression
- 接著由裡到外逐步構建
- 先左節點(Left)
- 後右節點(Right)
- 接著Body節點
- 將其拼接成 Expression
public IActionResult GetUserByManualExpression() { ParameterExpression parameterExpression = Expression.Parameter(type:typeof(User), name: "u"); ConstantExpression right = Expression.Constant(18); MemberExpression left = Expression.MakeMemberAccess(parameterExpression, member: typeof(User).GetProperty("Age")); BinaryExpression body = Expression.GreaterThanOrEqual(left, right); Expression<Func<User, bool>> expression = Expression.Lambda<Func<User, bool>>(body, parameters: parameterExpression); var data = _userService.GetUsers(expression); return Ok(new { code = 200, msg = "OK", data }); }
到此這篇關於C#表達式樹Expression基礎講解的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- C# IQueryable<T>揭開表達式樹的神秘面紗
- C#根據前臺傳入實體名稱實現動態查詢數據
- C#表達式樹Expression動態創建表達式
- C#值類型、引用類型、泛型、集合、調用函數的表達式樹實踐
- 關於C#10 新特性 Lambda 優化