C#表達式樹

前言

  在使用 EF 開中咱們常常使用 xx.Where(p=>p.Name="張三") 查詢數據,之把能這樣是由於 EF 框架會把這些C#代碼轉成Sql語句, 其中主要用到的就是表達式樹,今天就來學習一下表達式樹。node

認識表達式樹

Func<int,int,int> func = (a, b) => a + b;
Expression<Func<int,int,int>> expression = (a, b) => a + b;

上面分別是 Func 委託和表達式樹,看上去很類似,左邊只多了 Expression<> 右邊徹底同樣,其實仍是有很大區別的,對於委託咱們只能傳遞參數來調用,內部的代碼在程序運行中是無從得知的,而表達式樹在這點上相反,表達式樹是一種數據結構,能夠經過 C# 代碼清晰的獲取內部的細節。sql

表達式樹的另外一種寫法

上面的例子中是使用 Lambda 爲表達式樹賦值,其實還有另外一種寫法express

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:表達式主體,例子中是二元表達式,經常使用的還有ide

  • 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
bui

Body 的類型是 Expression,例子中的是二元表達式,因此要轉換成 BinaryExpression 類來查看信息spa

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());

 

寫得有點亂,忘見諒

相關文章
相關標籤/搜索