解析表達式樹


表達式樹的解析.

 

前言

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

 

 
分類:  C#


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM