表達式樹的解析.
前言
公司的orm框架在dapper的基礎上擴展了一套表達式的方法,當時就研究了一下,把學習過程和結果記錄下來,和大家分享。
有人會說重復造輪子沒必要,直接上EF。
從我的角度來看重復造輪子的原因有以下三種:
1、研究造輪子的原理
2、輪子不滿足現在的開發需要
3、裝B
表達式樹的作用
最常用到的無非就是ORM的刪查改的條件,ORM就是在ado.Net的基礎上封裝了一層表達式,最后還是將表達式解析成sql,由ado.Net去執行。
那么我們能將表達式樹解析成字符串,那么也能反過來。例如運費系統,在后台設置定義好一套計算規則。例如:對應不同的發貨渠道,什么重量取哪個區間的費用,多於哪個階段的費用還要額外費用。我們可以通過解析這套計算規則拼裝好表達式樹傳入參數進行計算。。。
還有別的在評論補充下。。。
不扯多,現在我們只拿解析表達式樹來學習。
創建表達式
首先創建4個屬性的Users類

1 namespace CG.ExpressionProject 2 { 3 /// <summary> 4 /// 用戶類 5 /// </summary> 6 public class Users 7 { 8 public string Name { get; set; } 9 10 public int Phone { get; set; } 11 12 public int Sex { get; set; } 13 14 public int Age { get; set; } 15 } 16 }
1 namespace CG.ExpressionProject 2 { 3 /// <summary> 4 /// 用戶類 5 /// </summary> 6 public class Users 7 { 8 public string Name { get; set; } 9 10 public int Phone { get; set; } 11 12 public int Sex { get; set; } 13 14 public int Age { get; set; } 15 } 16 }
接着,我們從最簡單的開始,寫一個二元運算表達式,F5調試監控觀察。
Expression<Func<Users, bool>> expressionUser = users => users.Name == "SkyChen"
從上圖可以看見有很多屬性,在表達式主體(屬性Body),我們暫時只關注三個屬性,Left(左節點)、Right(右節點)和 NodeType (當前節點類型)
簡單解析
表達式主體(users.Name == "SkyChen")是一個二元運算表達式,因此可以將Body轉換成 BinaryExpression 類型來訪問Left和Right。
Left 和 Right 的 NodeType 分別為 MemberAccess(從字段或屬性進行讀取的運算)、Constant(常量)。
因此可以將 Left 轉換成 MemberExpression 類型來訪問 Member 屬性,將 Right 轉換成 ConstantExpression 類型來訪問 Value 屬性。具體代碼如下:

public static string ResolveExpression(Expression<Func<Users, bool>> expression) { var bodyNode = (BinaryExpression)expression.Body; var leftNode = (MemberExpression)bodyNode.Left; var rightNode = (ConstantExpression)bodyNode.Right; return string.Format(" {0} {2} {1} ", leftNode.Member.Name, rightNode.Value, bodyNode.NodeType.TransferExpressionType()); }
public static string ResolveExpression(Expression<Func<Users, bool>> expression) { var bodyNode = (BinaryExpression)expression.Body; var leftNode = (MemberExpression)bodyNode.Left; var rightNode = (ConstantExpression)bodyNode.Right; return string.Format(" {0} {2} {1} ", leftNode.Member.Name, rightNode.Value, bodyNode.NodeType.TransferExpressionType()); }
TransferExpressionType 是針對部分 ExpressionType 的一個轉換。

public static string TransferExpressionType(this ExpressionType expressionType) { string type = ""; switch (expressionType) { case ExpressionType.Equal: type = "="; break; case ExpressionType.GreaterThanOrEqual: type = ">="; break; case ExpressionType.LessThanOrEqual: type = "<="; break; case ExpressionType.NotEqual: type = "!="; break; case ExpressionType.AndAlso: type = "And"; break; case ExpressionType.OrElse: type = "Or"; break; } return type; }
public static string TransferExpressionType(this ExpressionType expressionType) { string type = ""; switch (expressionType) { case ExpressionType.Equal: type = "="; break; case ExpressionType.GreaterThanOrEqual: type = ">="; break; case ExpressionType.LessThanOrEqual: type = "<="; break; case ExpressionType.NotEqual: type = "!="; break; case ExpressionType.AndAlso: type = "And"; break; case ExpressionType.OrElse: type = "Or"; break; } return type; }
那么。一個最簡單的表達式解析成where語句就完成了。
升級
然而,實踐工作中,大家都會寫相對復雜或者說多個條件的表達式。那么再采用上面的方式是無法確認表達式節點的類型進行轉換的。我們可以添加一個Visit方法,根據 NodeType 轉換成對應的Expression的類型,從而方法訪問對應的屬性進行表達式解析。
但是,重寫之前,我們得了解一件事,既然叫表達式樹,意味着在子節點里,還會有多個節點,如下圖:
那么,我們假設,只要是 BinaryExpression(二元運算表達式)就會有多個子節,去訪問子節點就是一個遞歸的過程,而終點就是 MemberExpression 和 ConstantExpression,對應字段名稱和常量值的拼接。
下面是代碼實現:

public class ExpressionTypeHelper { public StringBuilder GeWhere = new StringBuilder(100); public string Where { get { return GeWhere.ToString(); } } public void ResolveExpression(Expression<Func<Users, bool>> expression) { Visit(expression.Body); } public void Visit(Expression expression) { switch (expression.NodeType) { case ExpressionType.Constant: VisitConstantExpression(expression); break; case ExpressionType.MemberAccess: VisitMemberExpression(expression); break; case ExpressionType.Convert: VisitUnaryExpression(expression); break; default: VisitBinaryExpression(expression); break; } } public void VisitUnaryExpression(Expression expression) { var e = (UnaryExpression)expression; Visit(e.Operand); } public void VisitBinaryExpression(Expression expression) { var e = (BinaryExpression)expression; GeWhere.Append("("); Visit(e.Left); GeWhere.Append(e.NodeType.TransferExpressionType()); Visit(e.Right); GeWhere.Append(")"); } public void VisitConstantExpression(Expression expression) { var e = (ConstantExpression)expression; if (e.Type == typeof(string)) { GeWhere.Append("'" + e.Value + "'"); } else { GeWhere.Append(e.Value); } } public void VisitMemberExpression(Expression expression) { var e = (MemberExpression)expression; GeWhere.Append(e.Member.Name); } }
public class ExpressionTypeHelper { public StringBuilder GeWhere = new StringBuilder(100); public string Where { get { return GeWhere.ToString(); } } public void ResolveExpression(Expression<Func<Users, bool>> expression) { Visit(expression.Body); } public void Visit(Expression expression) { switch (expression.NodeType) { case ExpressionType.Constant: VisitConstantExpression(expression); break; case ExpressionType.MemberAccess: VisitMemberExpression(expression); break; case ExpressionType.Convert: VisitUnaryExpression(expression); break; default: VisitBinaryExpression(expression); break; } } public void VisitUnaryExpression(Expression expression) { var e = (UnaryExpression)expression; Visit(e.Operand); } public void VisitBinaryExpression(Expression expression) { var e = (BinaryExpression)expression; GeWhere.Append("("); Visit(e.Left); GeWhere.Append(e.NodeType.TransferExpressionType()); Visit(e.Right); GeWhere.Append(")"); } public void VisitConstantExpression(Expression expression) { var e = (ConstantExpression)expression; if (e.Type == typeof(string)) { GeWhere.Append("'" + e.Value + "'"); } else { GeWhere.Append(e.Value); } } public void VisitMemberExpression(Expression expression) { var e = (MemberExpression)expression; GeWhere.Append(e.Member.Name); } }
結果如下:
ExpressionVisitor的使用
一個基本的表達式解析思路基本實現了,但是!隨着自己的orm的完善是不是這么多種的Expression類型都得在Visit方法添一遍,不是的。
ExpressionVisitor類是提供給我們的表達式樹解析的幫助類,我們只要定義一個類繼承ExpressionVisitor,實現一個 ResolveExpression 入口方法,重寫
VisitBinary、VisitConstant、VisitMember方法,代碼如下:

public class ExpressionTrasfer : ExpressionVisitor { public StringBuilder GeWhere = new StringBuilder(100); public string Where { get { return GeWhere.ToString(); } } public void ResolveExpression(Expression<Func<Users, bool>> expression) { Visit(expression.Body); } protected override Expression VisitBinary(BinaryExpression node) { GeWhere.Append("("); Visit(node.Left); GeWhere.Append(node.NodeType.TransferExpressionType()); Visit(node.Right); GeWhere.Append(")"); return node; } protected override Expression VisitConstant(ConstantExpression node) { if (node.Type == typeof(string)) { GeWhere.Append("'" + node.Value + "'"); } else if (node.Type == typeof(int)) { GeWhere.Append(node.Value); } return node; } protected override Expression VisitMember(MemberExpression node) { GeWhere.Append(node.Member.Name); return node; } }
public class ExpressionTrasfer : ExpressionVisitor { public StringBuilder GeWhere = new StringBuilder(100); public string Where { get { return GeWhere.ToString(); } } public void ResolveExpression(Expression<Func<Users, bool>> expression) { Visit(expression.Body); } protected override Expression VisitBinary(BinaryExpression node) { GeWhere.Append("("); Visit(node.Left); GeWhere.Append(node.NodeType.TransferExpressionType()); Visit(node.Right); GeWhere.Append(")"); return node; } protected override Expression VisitConstant(ConstantExpression node) { if (node.Type == typeof(string)) { GeWhere.Append("'" + node.Value + "'"); } else if (node.Type == typeof(int)) { GeWhere.Append(node.Value); } return node; } protected override Expression VisitMember(MemberExpression node) { GeWhere.Append(node.Member.Name); return node; } }
結束
一個簡單的表達式解析大致完成了,當然里面還有很多可以完善,例如值類型的判斷,is 還是 = ,VisitMethodCall重寫等等。原理就這樣,實現我這里就不一一列舉。如對大家有幫助,麻煩請推薦,有不足請在下面評論提出,我會一一更改。