表達式樹的解析.
前言
公司的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重寫等等。原理就這樣,實現我這里就不一一列舉。如對大家有幫助,麻煩請推薦,有不足請在下面評論提出,我會一一更改。

