前言 在使用 EF 開中我們經常使用 xx.Where(p=>p.Name="張三") 查詢數據,之把能這樣是因為 EF 框架會把這些C#代碼轉成Sql語句, 其中主要用到的就是表達式樹,今天就來學習一下表達式樹。 認識表達式樹 Func<int,int,int> func = (a, b) => ...
前言
在使用 EF 開中我們經常使用 xx.Where(p=>p.Name="張三") 查詢數據,之把能這樣是因為 EF 框架會把這些C#代碼轉成Sql語句, 其中主要用到的就是表達式樹,今天就來學習一下表達式樹。
認識表達式樹
Func<int,int,int> func = (a, b) => a + b; Expression<Func<int,int,int>> expression = (a, b) => a + b;
上面分別是 Func 委托和表達式樹,看上去很相似,左邊只多了 Expression<> 右邊完全一樣,其實還是有很大區別的,對於委托我們只能傳遞參數來調用,內部的代碼在程式運行中是無從得知的,而表達式樹在這點上相反,表達式樹是一種數據結構,可以通過 C# 代碼清晰的獲取內部的細節。
表達式樹的另一種寫法
上面的例子中是使用 Lambda 為表達式樹賦值,其實還有另一種寫法
ParameterExpression parameterA = Expression.Parameter(typeof(int),"a"); ParameterExpression parameterB = Expression.Parameter(typeof(int),"b"); BinaryExpression binaryExpression = Expression.Add(parameterA, parameterB); Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(binaryExpression,parameterA,parameterB);
上面的例子對應於第一種寫法,第一種寫法是語法糖,其實編譯器最終生成還是這種代碼,可以通過反編譯軟體來驗證。
解析表達式樹
認識一下表達式樹的主要部分
Body:表達式主體,例子中是二元表達式,常用的還有
- ConstantExpression:常量表達式
- ParameterExpression:參數表達式
- UnaryExpression:一元運算符表達式
- BinaryExpression:二元運算符表達式
- TypeBinaryExpression:is運算符表達式
- ConditionalExpression:條件表達式
- MemberExpression:訪問欄位或屬性表達式
- MethodCallExpression:調用成員函數表達式
- Expression<TDelegate>:委托表達式
NodeType:節點類型,例子中是 Lambda ,常用還有的+,-,*,/,>,=,<,&&,|| 等都有,不過並不是符號而是對應的英文,詳情查看 ExpressionType 枚舉
Parameters:表達式的參數,a 和 b
Console.WriteLine(expression.Body); Console.WriteLine(expression.NodeType); Console.WriteLine(expression.Parameters[0]); Console.WriteLine(expression.Parameters[1]);
輸出是
(a + b)
Lambda
a
b
Body 的類型是 Expression,例子中的是二元表達式,所以要轉換成 BinaryExpression 類來查看信息
BinaryExpression binaryExpression = (BinaryExpression)expression.Body;
Console.WriteLine(binaryExpression.Left);
Console.WriteLine(binaryExpression.Right);
Console.WriteLine(binaryExpression.NodeType);
輸出是
a
b
Add
剛纔是一個簡單表達式,再來看兩個複雜點的,經過第一次解析後 Left 和 Right 就是第一種解析的表達式,可以把 Left 和 Right 再解析一次,最終完全解析,不管多複雜的表達式都可以像這樣解析出來
上面的例子只是為了瞭解表達式樹結構,用這種方法解析存在兩個問題
一是 BinaryExpression 這裡固定了只能解析二元表達式,如果是其它表達式就會報錯
二是不知道需要解析多少層才解析完
要解析表達式樹要用 C# 里的 ExpressionVisitor 類,這個類就是專門解析表達式樹的,它是一個抽象類,需要建個類繼承它,使用過程如下,首先調用父類 Visit 方法,在 Visit 中會判斷表達式的類型是一元(對應VisitUnary)、二元(對應VisitBinary),常量(對應VisitConstant)、參數(對應VisitParameter)等表達式,然後就會進對應的解析方法中支解析,比如二元表達式的解析方法就是 VisitBinary,然後我們重寫 VisitBinary
下麵是使用 ExpressionVisitor 解析表達式樹的例子,這麼說並不完全對,解析代碼是 ExpressionVisitor 已經寫好的,我們做的只解析過程中加入一些自己的代碼而已
首尾呼應
最後來實現一個簡單的由表達式樹生成sql語句的功能
class MyVisitor : ExpressionVisitor { private string tableName; private StringBuilder sbSql = new StringBuilder(); public override Expression Visit(Expression node) { return base.Visit(node); } protected override Expression VisitBinary(BinaryExpression node) { base.Visit(node.Left); sbSql.Append(ExpressionTypeToSql(node.NodeType)); base.Visit(node.Right); return node; } public string GetSqlString() { return "select * from "+tableName+" where "+sbSql.ToString(); } protected override Expression VisitConstant(ConstantExpression node) { if (node.Type == typeof(int)) { sbSql.Append( node.Value); } else { sbSql.Append("'"+node.Value+"'"); } return base.VisitConstant(node); } protected override Expression VisitParameter(ParameterExpression node) { if (tableName == null) { tableName = "[" + node.Type.Name + "]"; } return base.VisitParameter(node); } protected override Expression VisitMember(MemberExpression node) { sbSql.Append("[" + node.Member.Name + "]"); return base.VisitMember(node); } public string ExpressionTypeToSql(ExpressionType expressionType) { switch (expressionType) { case ExpressionType.Add: return " + "; case ExpressionType.And: case ExpressionType.AndAlso: return " and "; case ExpressionType.Equal: return " = "; case ExpressionType.NotEqual: return " != "; case ExpressionType.GreaterThan: return " > "; case ExpressionType.GreaterThanOrEqual: return " >= "; case ExpressionType.LessThan: return " < "; case ExpressionType.LessThanOrEqual: return " <= "; case ExpressionType.Multiply: return " * "; case ExpressionType.Or: case ExpressionType.OrElse: return " or "; default: return ""; } } }
Expression<Func<Person, bool>> expression = p=>p.Name=="張三"&&p.Name!="李四"; MyVisitor myVisitor = new MyVisitor(); myVisitor.Visit(expression); Console.WriteLine(myVisitor.GetSqlString());
寫得有點亂,忘見諒