文章目錄
- 前言
- 一、表達式樹與委托的區別
- 二、動態構建表達式樹
- 示例1
- 示例2
- 示例3
- 高級技巧:表達式合并
- 三、ExpressionTreeToString
- 安裝方法
- 基本用法
- 支持的格式化風格
- 四、注意事項
- 總結
前言
在 Entity Framework Core 中,表達式樹(Expression Tree) 是 LINQ 查詢的核心機制,它允許將 C# 代碼中的查詢邏輯轉換為 SQL 語句,從而在數據庫服務器端高效執行。
一、表達式樹與委托的區別
-
委托(如 Func<T, bool>)
直接編譯為可執行的代碼,運行時在內存中過濾數據(客戶端評估)。Func<House, bool> func = p => p.Owner.Contains("tom"); var res=dbContext.Houses.Where(exp1).ToList(); // 在客戶端過濾!
-
表達式樹(如 Expression<Func<T, bool>>)
保持查詢邏輯的抽象語法樹結構,EF Core 可將其轉換為 SQL(服務器端評估)。Expression<Func<House, bool>> exp1 = b => b.Owner.Contains("tom"); var res=dbContext.Houses.Where(exp1).ToList();//生成 SQL:WHERE Owner like '%tom%'
二、動態構建表達式樹
- 當需要根據運行時條件動態生成查詢時,手動構建表達式樹非常有用。
- ParameterExpression、BinaryExpression、MethodCallExpression、ConstantExpression等類幾乎都沒有提供構造方法,而且所有屬性也幾乎都是只讀,因此我們一般不會直接創建這些類的實例,而是調用Expression類的Parameter、MakeBinary、Call、Constant等靜態方法來生成,這些靜態方法我們一般稱作創建表達式樹的工廠方法,而屬性則是通過方法參數類設置。
- 工廠方法:
加法:Add
短路與運算:AndAlso
數組元素訪問:ArraryAccess
方法訪問:Call
三元條件運算符:Condition
常量表達式:Constant
類型轉換:Convert
大于運算符:GreaterThan
小于運算:LessThan
大于或等于運算符:GreaterThanOrEqual
創建二元運算:MakeBinary
不等于運算:NotEqual
短路或運算:OrElse
表達式的參數:Parameter
示例1
-
動態過濾Owner包含關鍵字
using System.Linq.Expressions; using (MyDBContext dbContext=new MyDBContext()) {string name = Console.ReadLine();// 參數表達式:代表實體對象(如 p => ... 中的 p)ParameterExpression param = Expression.Parameter(typeof(House), "p");// 屬性訪問:p.OwnerMemberExpression nameProperty = Expression.Property(param, "Owner");MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });// 參數值ConstantExpression keywordConstant = Expression.Constant(name);MethodCallExpression nameCondition = Expression.Call(nameProperty, containsMethod, keywordConstant);// 組合為 Lambda 表達式Expression<Func<House, bool>> expr =Expression.Lambda<Func<House, bool>>(nameCondition, param);// 應用查詢var query = dbContext.Houses.Where(expr);foreach (var house in query){Console.WriteLine(house.Owner);} }
-
生成的 SQL:
SELECT * FROM Houses WHERE Owner like '%tom%'
示例2
-
動態過濾價格
using System.Linq.Expressions;// 參數表達式:代表實體對象(如 p => ... 中的 p) ParameterExpression param = Expression.Parameter(typeof(House), "p");// 屬性訪問:p.Price MemberExpression priceProperty = Expression.Property(param, "Price");// 常量值:100 ConstantExpression constant = Expression.Constant(100.0);// 比較表達式:p.Price > 100 BinaryExpression priceComparison = Expression.GreaterThan(priceProperty, constant);// 組合為 Lambda 表達式 Expression<Func<House, bool>> expr = Expression.Lambda<Func<House, bool>>(priceComparison, param);// 應用查詢 var query = dbContext.Houses.Where(expr);
-
生成的SQL
SELECT * FROM T_Houses WHERE Price > 100
示例3
-
組合多個表達式(動態查詢中,常需要組合多個條件(如 AND/OR))
public static Expression<Func<House, bool>> BuildDynamicFilter(string paramOwnerStr,string paramPriceStr, string nameKeyword, double? minPrice) {ParameterExpression param = Expression.Parameter(typeof(House), "p");Expression finalExpr = Expression.Constant(true); // 初始條件:true// 條件1:名稱包含關鍵字if (!string.IsNullOrEmpty(nameKeyword)){MemberExpression nameProperty = Expression.Property(param, paramOwnerStr);MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });ConstantExpression keywordConstant = Expression.Constant(nameKeyword);MethodCallExpression nameCondition = Expression.Call(nameProperty, containsMethod, keywordConstant);finalExpr = Expression.AndAlso(finalExpr, nameCondition);}// 條件2:價格 >= minPriceif (minPrice.HasValue){MemberExpression priceProperty = Expression.Property(param, paramPriceStr);ConstantExpression minPriceConstant = Expression.Constant(minPrice.Value);BinaryExpression priceCondition = Expression.GreaterThanOrEqual(priceProperty, minPriceConstant);finalExpr = Expression.AndAlso(finalExpr, priceCondition);}return Expression.Lambda<Func<House, bool>>(finalExpr, param); }
// 使用:using (MyDBContext dbContext =new MyDBContext()){var filter = BuildDynamicFilter("Owner","Price","Tom", 2000.0);var query = dbContext.Houses.Where(filter);foreach (var house in query){Console.WriteLine(house.Owner);}}
-
生成的SQL
SELECT [t].[Id], [t].[Name], [t].[Owner], [t].[Price], [t].[RowVersion]FROM [T_Houses] AS [t]WHERE [t].[Owner] LIKE N'%Tom%' AND [t].[Price] >= 2000.0E0
高級技巧:表達式合并
-
如果需要組合兩個已有的表達式(如 expr1 && expr2),需統一參數。
-
示例:合并兩個表達式
public static Expression<Func<T, bool>> CombineAnd<T>(Expression<Func<T, bool>> expr1,Expression<Func<T, bool>> expr2){var param = Expression.Parameter(typeof(T));var body1 = ReplaceParameter(expr1.Body, expr1.Parameters[0], param);var body2 = ReplaceParameter(expr2.Body, expr2.Parameters[0], param);return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(body1, body2),param);}private static Expression ReplaceParameter(Expression expression,ParameterExpression oldParam,ParameterExpression newParam){return new ParameterReplacer(oldParam, newParam).Visit(expression);}class ParameterReplacer : ExpressionVisitor{private readonly ParameterExpression _oldParam;private readonly ParameterExpression _newParam;public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam){_oldParam = oldParam;_newParam = newParam;}protected override Expression VisitParameter(ParameterExpression node){return node == _oldParam ? _newParam : node;}}
使用: Expression<Func<House, bool>> expr1 = p => p.Owner.Contains("Tom"); Expression<Func<House, bool>> expr2 = p => p.Price > 2000; var combinedExpr = CombineAnd(expr1, expr2); var query2 = dbContext.Houses.Where(combinedExpr); foreach (var house in query2) {Console.WriteLine(house.Owner); }
-
生成的SQL
SELECT [t].[Id], [t].[Name], [t].[Owner], [t].[Price], [t].[RowVersion]FROM [T_Houses] AS [t]WHERE [t].[Owner] LIKE N'%Tom%' AND [t].[Price] > 2000.0E0
三、ExpressionTreeToString
ExpressionTreeToString 是一個第三方庫,用于將 LINQ 表達式樹(Expression)轉換為可讀的字符串形式,幫助開發者調試和分析表達式樹的結構。
輸出的所有代碼都是對于工廠方法的調用,且調用工廠方法的時候都省略了Expression類,手動添加Expression或者using static System.Linq.Expressions.Expression;
安裝方法
-
通過 NuGet 包管理器安裝
Install-Package ExpressionTreeToString
基本用法
-
示例
Expression<Func<House, bool>> exp1 = b => b.Owner.Contains("tom"); Expression<Func<House, bool>> exp2 = b => b.Price > 2000; //轉換為字符串(支持多種格式化選項) //string exprString = expr.ToString("C#", "Dynamic LINQ");//Console.WriteLine(exp1.ToString("Factory methods", "C#")); Console.WriteLine(exp2.ToString("Factory methods", "C#"));
//輸出結果展示 // using static System.Linq.Expressions.Expressionvar b = Parameter(typeof(House),"b" );Lambda(GreaterThan(MakeMemberAccess(b,typeof(House).GetProperty("Price")),Constant(2000)),b )
支持的格式化風格
ExpressionTreeToString 提供多種輸出格式,方便不同場景使用:
- C# 語法風格:ToString(“C#”)
接近 C# 代碼的直觀表示。 - Visual Basic 語法風格:ToString(“VB”)
類似 VB 語法。 - 表達式樹結構:ToString(“Object notation”)
顯示表達式樹的節點結構(如 BinaryExpression、ParameterExpression)。 - 調試視圖:ToString(“DebugView”)
與 Visual Studio 調試器中表達式樹的顯示一致。
四、注意事項
- 不支持所有 C# 方法:某些方法(如 ToString())無法轉換為 SQL,會導致運行時錯誤。
- 調試技巧:通過 query.ToQueryString() 查看生成的 SQL。
- 性能:表達式樹構建在內存中完成,復雜邏輯可能影響啟動性能。
總結
通過靈活使用表達式樹,可以極大增強 EF Core 查詢的靈活性,同時保持高效的服務器端執行。