寫在前面
系列文章
表達式樹解析
表達式樹特性
編譯表達樹
總結
讓我們首先簡單回顧一下上篇文章介紹的內容,上篇文章介紹了表達式樹的基本概念(表達式樹又稱為“表達式目錄樹”,以數據形式表示語言級代碼,它是一種抽象語法樹或者說是一種數據結構),以及兩種創建表達式樹目錄樹的方式:以lambda表達式的方式創建,通過API靜態方法創建。由於不能將有語句體的lambda表達式轉換為表達式樹,而有時我們又有這樣的需求,那麼這種情況你可以選擇API的靜態方法方式創建,在 .NET Framework 4 中,API 表達式樹還支持賦值表達式和控制流表達式,比如循環、條件塊和 try-catch 塊等。
Linq之Lambda表達式初步認識
Linq之Lambda進階
Linq之隱式類型、自動屬性、初始化器、匿名類
Linq之擴展方法
Linq之Expression初見
我們可以通過API方式創建表達式樹,那麼我們有沒有辦法,將給定的表達式樹進行解析,分別得到各個部分呢?答案是肯定,下面看一個例子。
有一個這樣的表達式樹
1 //創建表達式樹 2 Expression<Func<int, bool>> expTree = num => num >= 5;
可以這樣來解析,分別得到各個部分
1 //創建表達式樹 2 Expression<Func<int, bool>> expTree = num => num >= 5; 3 //獲取輸入參數 4 ParameterExpression param = expTree.Parameters[0]; 5 //獲取lambda表達式主題部分 6 BinaryExpression body = (BinaryExpression)expTree.Body; 7 //獲取num>=5的右半部分 8 ConstantExpression right = (ConstantExpression)body.Right; 9 //獲取num>=5的左半部分 10 ParameterExpression left = (ParameterExpression)body.Left; 11 //獲取比較運算符 12 ExpressionType type = body.NodeType; 13 Console.WriteLine("解析後:{0} {1} {2}",left,type,right);
輸出結果
是不是很爽?不知道到這裡,你是否對ORM框架中,lambda表達式是如何轉化為sql語句有那麼一點點的靈感?沒有沒關系,咱們繼續看一個例子。如果數據庫中有Person這樣的一個數據表。咱們項目中有對應的Person這樣的一個持久化類。那麼我們創建一個這樣的一個查詢方法,返回所有齡大於等於18歲的成年人的sql語句。
1 namespace Wolfy.ORMDemo 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 string sql = Query<Person>(person => person.Age >= 18); 8 Console.WriteLine(sql); 9 Console.Read(); 10 } 11 /// <summary> 12 /// 得到查詢的sql語句 13 /// </summary> 14 /// <param name="epression">篩選條件</param> 15 /// <returns></returns> 16 static string Query<T>(Expression<Func<T, bool>> epression) where T : class,new() 17 { 18 //獲取輸入參數 19 ParameterExpression param = epression.Parameters[0]; 20 //獲取lambda表達式主體部分 21 BinaryExpression body = (BinaryExpression)epression.Body; 22 //解析 person.Age 23 Expression left = body.Left; 24 string name = (left as MemberExpression).Member.Name; 25 //獲取主體的右部分 26 ConstantExpression right = (ConstantExpression)body.Right; 27 //獲取運算符 28 ExpressionType nodeType = body.NodeType; 29 StringBuilder sb = new StringBuilder(); 30 //使用反射獲取實體所有屬性,拼接在sql語句中 31 Type type = typeof(T); 32 PropertyInfo[] properties = type.GetProperties(); 33 sb.Append("select "); 34 for (int i = 0; i < properties.Length; i++) 35 { 36 PropertyInfo property = properties[i]; 37 if (i == properties.Length - 1) 38 { 39 sb.Append(property.Name + " "); 40 } 41 else 42 { 43 sb.Append(property.Name + " ,"); 44 } 45 } 46 sb.Append("from "); 47 sb.Append(type.Name); 48 sb.Append(" where "); 49 sb.Append(name); 50 if (nodeType == ExpressionType.GreaterThanOrEqual) 51 { 52 sb.Append(">="); 53 } 54 sb.Append(right); 55 return sb.ToString(); 56 } 57 } 58 class Person 59 { 60 public int Age { set; get; } 61 public string Name { set; get; } 62 } 63 }
輸出結果
是不是很方便?傳進來一個lambda表達式,就可以通過orm框架內部解析,然後轉化為sql語句。也就是通過編寫lambda就等於寫了sql語句,也不用擔心不會寫sql語句了。
表達式樹應具有永久性。 這意味著如果你想修改某個表達式樹,則必須復制該表達式樹然後替換其中的節點來創建一個新的表達式樹。
那如何修改呢?
可以通過 ExpressionVisitor類遍歷現有表達式樹,並復制它訪問的每個節點。
一個例子
在項目中添加一個AndAlsoModifier 類。
將表達式樹中的AndAlse修改為OrElse,代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 namespace Wolfy.ExpressionModifyDemo 8 { 9 /*該類繼承 ExpressionVisitor 類,並且專用於修改表示條件 AND 運算的表達式。 10 * 它將這些運算從條件 AND 更改為條件 OR。 11 * 為此,該類將重寫基類型的 VisitBinary 方法,這是因為條件 AND 表達式表示為二元表達式。 12 * 在 VisitBinary 方法中,如果傳遞到該方法的表達式表示條件 AND 運算, 13 * 代碼將構造一個包含條件 OR 運算符(而不是條件 AND 運算符)的新表達式。 14 * 如果傳遞到 VisitBinary 的表達式不表示條件 AND 運算,則該方法交由基類實現來處理。 15 * 基類方法構造類似於傳入的表達式樹的節點,但這些節點將其子目錄樹替換為訪問器遞歸生成的表達式樹。*/ 16 public class AndAlsoModifier : ExpressionVisitor 17 { 18 public Expression Modify(Expression expression) 19 { 20 return Visit(expression); 21 } 22 protected override Expression VisitBinary(BinaryExpression node) 23 { 24 if (node.NodeType == ExpressionType.AndAlso) 25 { 26 Expression left = this.Visit(node.Left); 27 Expression right = this.Visit(node.Right); 28 //修改AndAlse為OrElse 29 return Expression.MakeBinary(ExpressionType.OrElse, left, right, node.IsLiftedToNull, node.Method); 30 } 31 return base.VisitBinary(node); 32 } 33 } 34 }
測試代碼
1 namespace Wolfy.ExpressionModifyDemo 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Expression<Func<string, bool>> expr = name => name.Length > 10 && name.StartsWith("G"); 8 //修改前 9 Console.WriteLine(expr); 10 AndAlsoModifier treeModifier = new AndAlsoModifier(); 11 Expression modifiedExpr = treeModifier.Modify((Expression)expr); 12 //修改後 13 Console.WriteLine(modifiedExpr); 14 Console.Read(); 15 } 16 } 17 }
輸出結果
小結:修改表達式樹,需繼承ExpressionVisitor類,並重寫它的VisitBinary(如果是類似AND這類的二元表達式)方法。再舉一個例子,如果要將大於修改為小於等於,可修改VisitBinary方法的實現。
1 protected override Expression VisitBinary(BinaryExpression node) 2 { 3 if (node.NodeType == ExpressionType.GreaterThan) 4 { 5 Expression left = this.Visit(node.Left); 6 Expression right = this.Visit(node.Right); 7 //修改> 為<= 8 return Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, right, node.IsLiftedToNull, node.Method); 9 } 10 return base.VisitBinary(node); 11 }
結果
Expression<TDelegate> 類型提供了 Compile 方法以將表達式樹表示的代碼編譯成可執行委托。
還以最上面的那個表達式樹為例
1 //創建表達式樹 2 Expression<Func<int, bool>> expTree = num => num >= 5;
有這樣的一個表達式樹,現在,我想直接輸入一個值,然後得到結果,該如何辦呢?可以這樣
1 //創建表達式樹 2 Expression<Func<int, bool>> expTree = num => num >= 5; 3 // Compile方法將表達式樹描述的 lambda 表達式編譯為可執行代碼,並生成表示該 lambda 表達式的委托。 4 Func<int, bool> func = expTree.Compile(); 5 //結果 6 bool result = func(10);//true 7 Console.WriteLine(result);
1.通過表達式解析,你可以得到表達式樹的各個部分。你會發現如果你寫的方法的參數是Expression<Func<t,t>>類型的,你可以更好的使用lambda表達式的特性,操作更方便。例子中,也簡單分析了,ORM框架中,是如何將Lambda表達式解析為sql語句的,也希望能激發你的興趣。
2.表達式樹具有永久性的特性,一經創建,如果你想修改某個表達式樹,則必須復制該表達式樹然後替換其中的節點來創建一個新的表達式樹。具體操作可參考上面的例子。
3.通過Complie方法編譯後的表達式樹,就是一個委托,委托對應的方法的方法體就是表達式樹中的lambda表達式,你可以像使用委托一樣去使用它。有時你嫌麻煩也可以類似這樣直接使用
1 bool result = expTree.Compile()(10);
參考文章
http://msdn.microsoft.com/zh-cn/library/bb397951.aspx
http://msdn.microsoft.com/zh-cn/library/bb546136.aspx