SQL就是经过把表明式树翻译成SQL来落到实处的,表明式树体现的是代码的树形结构

  本文首要内容:

解析表达式树(Parsing Expression Trees)

上面包车型大巴代码示例演示了怎么将表明式树表示为lambda表达式num => num < 5,能够被解释为局部。

public void DecomposedExpressionTrees()
{
    //创建一个表达式树
    Expression<Func<int, bool>> exprTree = num => num < 5;
    //分解表达式
    ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
    //num < 5
    BinaryExpression operation = (BinaryExpression)exprTree.Body;
    ParameterExpression left = (ParameterExpression)operation.Left;
    ConstantExpression right = (ConstantExpression)operation.Right;

    Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
            param.Name, left.Name, operation.NodeType, right.Value);
}

公布式树的不变性(Immutability of Expression Trees)

表明式树应该是不可变的。那代表借使你想修改表明式树,那么您不可能不重新已经存在的结构表达式树并替换其有个别节点。你能够行使表明式树访问器(ExpressionVisitor)遍历表明式树。越多那上头音讯详见How
to: Modify Expression Trees
(C#)
.

  在上一篇中,大家用代码的法门创立了三个未曾再次回到值,用到了循环以及标准判断的说明式,为了深化我们对发挥式树的明亮,大家先想起一下,看2个有重临值的例子。

编写翻译表明式树

泛型
Expression
类型提供三个
Compile办法来将表明式树所代表的代码编写翻译成可进行的信托。

下边那段代码呈现怎么编写翻译表达式树和平运动行结果代码

public void ComplieExpressTrees()
{
    //创建一个表达式树
    Expression<Func<int, bool>> expr = num => num < 5;
    //编译表达式树为委托
    Func<int, bool> result = expr.Compile();
    //调用委托并写结果到控制台
    Console.WriteLine(result(4));

    //也可以使用简单的语法来编译运行表达式树
    Console.WriteLine(expr.Compile()(4));
    //结果一样
}

更加多关于什么运作表达式树消息,详见How to: Execute Expression Trees
(C#)
.

表明式的遍历

  说完了发挥式树的创办,咱们来看望哪些访问表达式树。MSDN官方能找到的关于遍历表明式树的文章真的不多,有1篇比较全的(链接.aspx)),真的未有艺术看下来。请问盖茨三伯正是那样教你们写文书档案的么?

  不过ExpressionVisitor是唯一一种大家得以拿来就用的帮助类,所以大家尽量也得把它啃下来。大家可以看一下ExpressionVisitor类的显要进口方法是Visit方法,其中主要是一个针对ExpressionNodeType的switch
case,那个带有了8五种操作类型的枚举类,可是毫无操心,在此间大家只处理4四种操作类型,14种具体的表达式类型,也等于说只有14个艺术大家要求区分一下。作者将地点链接中的代码转换到上边包车型地铁报表方便我们查阅。

图片 1

  认识了ExpressionVisitor之后,下边我们就来一步一步的探访到底是假若因而它来访问我们的抒发式树的。接下来大家要协调写3个类继承自这么些ExpressionVisitor类,然后覆盖在那之中的某部分情势从而完毕大家温馨的目地。大家要促成如何的职能吗?

List<User> myUsers = new List<User>();
var userSql = myUsers.AsQueryable().Where(u => u.Age > 2);
Console.WriteLine(userSql);
// SELECT * FROM (SELECT * FROM User) AS T WHERE (Age>2)

List<User> myUsers2 = new List<User>();
var userSql2 = myUsers.AsQueryable().Where(u => u.Name=="Jesse");
Console.WriteLine(userSql2);
// SELECT * FROM (SELECT * FROM USER) AS T WHERE (Name='Jesse')

  我们改造了IQueryable的Where方法,让它依据我们输入的询问条件来布局SQL语句。

  要贯彻这么些效率,首先大家得明白IQueryable的Where
方法在哪个地方,它是何许促成的?

public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }
        if (predicate == null)
        {
            throw new ArgumentNullException("predicate");
        }

        return source.Provider.CreateQuery<TSource>(
            Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod())
            .MakeGenericMethod(new Type[] { typeof(TSource) }), 
            new Expression[] { source.Expression, Expression.Quote(predicate) }));
    }
}

  通过F1贰咱们得以跟到System.Linq下有2个Querable的静态类,而我们的Where方法正是是扩大方法的山势存在于这一个类中(包蕴其的GroupBy,Join,Last等有趣味的同校能够自行Reflect
J)。大家能够见见地点的代码中,实际上是调用了Queryable的Provider的CreateQuery方法。这些Provider正是风传中的Linq
Provider,不过大家今天不打算细说它,我们的首要性在于传给那几个措施的参数被转成了八个表述式树。实际上Provider也正是吸收了那几个表明式树,然后开始展览遍历解释的,那么大家得以毫无Provider直接实行翻译啊?
I SAY YES! WHY CAN’T?

public static class QueryExtensions
{
    public static string Where<TSource>(this IQueryable<TSource> source,
        Expression<Func<TSource, bool>> predicate)
    {
        var expression = Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod())
        .MakeGenericMethod(new Type[] { typeof(TSource) }),
        new Expression[] { source.Expression, Expression.Quote(predicate) });

        var translator = new QueryTranslator();
        return translator.Translate(expression);
    }
}

  上面大家协调达成了二个Where的壮大方法,将该Where方法转换到表达式树,只可是大家从未调用Provider的法子,而是一向让另2个类去将它翻译成SQL语句,然后间接重返该SQL语句。接下来的标题是,那么些类怎样去翻译这些表明式树啊?大家的ExpressionVisitor要半场了!

class QueryTranslator : ExpressionVisitor
{
    internal string Translate(Expression expression)
    {
        this.sb = new StringBuilder();
        this.Visit(expression);
        return this.sb.ToString();
    }
}

  首先我们有3个类继承自ExpressionVisitor,里面有八个大家友好的Translate方法,然后大家一向调用Visit方法即可。上边我们关系了Visit方法其实是一个入口,会基于表明式的类型调用其余的Visit方法,我们要做的正是找到相应的措施重写就能够了。不过上面有一群Visit方法,我们要要遮盖哪哪些吗?
那就要看大家的表明式类型了,在大家的Where扩充方法中,大家传入的表明式树是由Expression.Call方法协会的,而它回到的是MethodCallExpression所以我们首先步是覆盖VisitMethodCall。

protected override Expression VisitMethodCall(MethodCallExpression m)
{
    if (m.Method.DeclaringType == typeof(QueryExtensions) && m.Method.Name == "Where")
    {
        sb.Append("SELECT * FROM (");
        this.Visit(m.Arguments[0]);
        sb.Append(") AS T WHERE ");
        LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
        this.Visit(lambda.Body);
        return m;
    }
    throw new NotSupportedException(string.Format("方法{0}不支持", m.Method.Name));
}

  代码很简短,方法名是Where那大家就一贯发轫拼SQL语句。重点是在那个法子里面三回调用了Visit方法,大家要精晓它们会分别调用哪多个具体的Visit方法,大家要做的正是重写它们。

  第二个咱们就隐瞒了,大家可以下载源代码本身去调节一下,大家来看看第1个Visit方法。很掌握,大家协会了3个Lambda表明式树,然而注意,大家并未有平素Visit这拉姆da表明式树,它是Visit了它的Body。它的Body是什么?如若小编的尺码是Age>7,那便是三个二元运算,不是么?所以大家要重写VisitBinary方法,Let’s
get started。

protected override Expression VisitBinary(BinaryExpression b)
{
    sb.Append("(");
    this.Visit(b.Left);
    switch (b.NodeType)
    {
        case ExpressionType.And:
            sb.Append(" AND ");
            break;
        case ExpressionType.Or:
            sb.Append(" OR");
            break;
        case ExpressionType.Equal:
            sb.Append(" = ");
            break;
        case ExpressionType.NotEqual:
            sb.Append(" <> ");
            break;
        case ExpressionType.LessThan:
            sb.Append(" < ");
            break;
        case ExpressionType.LessThanOrEqual:
            sb.Append(" <= ");
            break;
        case ExpressionType.GreaterThan:
            sb.Append(" > ");
            break;
        case ExpressionType.GreaterThanOrEqual:
            sb.Append(" >= ");
            break;
        default:
            throw new NotSupportedException(string.Format(“二元运算符{0}不支持”, b.NodeType));
    }
    this.Visit(b.Right);
    sb.Append(")");
    return b;
}

  大家依据那些表明式的操作类型转换来相应的SQL运算符,大家要做的正是把左手的属性名和左侧的值加到大家的SQL语句中。所以大家要重写VisitMember和VisitConstant方法。

protected override Expression VisitConstant(ConstantExpression c)
{
    IQueryable q = c.Value as IQueryable;
    if (q != null)
    {
        // 我们假设我们那个Queryable就是对应的表
        sb.Append("SELECT * FROM ");
        sb.Append(q.ElementType.Name);
    }
    else if (c.Value == null)
    {
        sb.Append("NULL");
    }
    else
    {
        switch (Type.GetTypeCode(c.Value.GetType()))
        {
            case TypeCode.Boolean:
                sb.Append(((bool)c.Value) ? 1 : 0);
                break;
            case TypeCode.String:
                sb.Append("'");
                sb.Append(c.Value);
                sb.Append("'");
                break;
            case TypeCode.Object:
                throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
            default:
                sb.Append(c.Value);
                break;
        }
    }
    return c;
}

protected override Expression VisitMember(MemberExpression m)
{
    if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
    {
        sb.Append(m.Member.Name);
        return m;
    }
    throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
}

  到此地,我们的原委基本上就知晓了。来回看一下大家干了怎么工作。

  1. 重写IQuerable的Where方法,构造MethodCallExpression传给大家的表达式访问类。
  2. 在我们的表明式访问类中重写相应的切实访问方法。
  3. 在现实访问方法中,解释表明式,翻译成SQL语句。

  实际上大家并不曾干什么很复杂的思想政治工作,只要明白实际的表达式类型和具体表访问方法就能够了。看到众多园友说表明式树难以领悟,小编也意在能够尽自个儿的努力去把它掌握的表明出来,让大家1块读书,尽管大家以为何地不亮堂,或许说笔者发挥的方法倒霉精晓,也欢迎我们踊跃的建议来,前边大家得以一连完善那些翻译SQL的职能,大家地点的代码中只帮忙Where语句,并且只帮助四个规格。笔者的目地的期待经过这一个事例让我们更加好的知晓表明式树的遍历难题,那样大家就能够完毕大家温馨的LinqProvider了,请我们关切,大家来任何Linq
To 什么吧?有好点子么? 之间想整个Linq to
天涯论坛,可是好像博客园未有公开Service。

  点这里面下载文中源代码。  

  参考引用:

     http://msdn.microsoft.com/en-us/library/bb397951(v=vs.120).aspx
   
 http://msdn.microsoft.com/en-us/library/system.linq.expressions.aspx
   
 http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx
   
 http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx

 

通过API创立表明式树

动用微软提供的API——Expression其1类来成立表明式树。这么些类包含了创制钦定项目表明式树节点的静态工厂方法,例如,
ParameterExpression它象征1个参数只怕变量,又如MethodCallExpression,它意味着一个方法调用。ParameterExpression,
MethodCallExpression以及别的的钦定项指标表明式树都在System.Linq.Expressions命名空间下。那一个类都一而再自
Expression抽象类.

上边包车型大巴代码体现怎样用API创制表达式树来表示三个lambda表达式num => num < 5

// 在你的代码文件添加引用:  
// using System.Linq.Expressions;  

// 为lambda表达式num => num < 5手动生成表达式树   
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");  
ConstantExpression five = Expression.Constant(5, typeof(int));  
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);  
Expression<Func<int, bool>> lambda1 =  
    Expression.Lambda<Func<int, bool>>(  
        numLessThanFive,  
        new ParameterExpression[] { numParam }); 

在.NET
Framework肆.0事后,表明式树API也支撑分配和控制流表明式,例如循环,条件判断块以及这些捕捉块(try-catch)。通过API,你能够创立比编写翻译器通过从lambda表明式创造的越来越错综复杂的发布式树。下边的这些例子体现了哪些创立三个表达式树来表示二个数的阶乘(factorial
of number).

//创建一个参数表达式
ParameterExpression value = Expression.Parameter(typeof(int), "value");
//创建一个表达式表示本地变量
ParameterExpression result = Expression.Parameter(typeof(int), "result");
//创建标签从循环跳到指定标签
LabelTarget label = Expression.Label(typeof(int));
//创建方法体
BlockExpression block = Expression.Block(
    //添加本地变量
    new[] { result },
    //为本地变量赋值一个常量
    Expression.Assign(result, Expression.Constant(1)),
    //循环
    Expression.Loop(
        //添加循环条件
        Expression.IfThenElse(
            //条件:value > 1
            Expression.GreaterThan(value, Expression.Constant(1)),
            //if true
            Expression.MultiplyAssign(result, Expression.PostDecrementAssign(value)),
            //if false
            Expression.Break(label, result)
            ),
        label
        )
    );

int facotorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);  
Console.WriteLine(factorial);  
// 输出 120.  

更多关于详见Generating Dynamic Methods with Expression Trees in Visual
Studio
2010
,也支撑后续VS版本.

  表明式树是随着.NET
3.5生产的,所以现在也不算什么新技巧了。不过不清楚有个外人是对它了然的很透彻,
在上一篇拉姆da表达式的东山再起中就看的出豪门对Lambda表达式和表明式树照旧相比较感兴趣的,那大家就来能够的看壹看这些培养和练习了LINQ
to SQL以及让LINQ to 伊夫rything的好东西吧。

[翻译]表明式树(Expression Trees)

*初稿地址:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/index*

表明式树展现的是代码的树形结构,每2个节点都以多个表明式,例如:调用三个办法可能调用1个贰元运算表达式比如
x < y

你能够经过表明式树来编译和平运动行代码。那意味能动态修改运维的代码,就好比在数据库中运作LINQ以2个变量的格局查询数据和开创三个动态的查询语句。关于表明式树在LINQ的越来越多选拔音信详见How
to: Use Expression Trees to Build Dynamic Queries
(C#)
.

表明式树也被用于DL悍马H2(动态语言运营时),提供DL中华V与.NET
Framework之间的互操作性,使编写翻译器解析(Emit)表达式树,而不是MSIL。关于DLLX570更加多消息详细详见Dynamic
Language Runtime
Overview
.

您能够用C#/VB编写翻译器在您的lambda表达式变量的底子上生成3个表明式树,大概您能够因此选择在System.Linq.Expressions取名空间创设表明式树.

有再次来到值的表明式树

// 直接返回常量值 
ConstantExpression ce1 = Expression.Constant(10);

// 直接用我们上面创建的常量表达式来创建表达式树
Expression<Func<int>> expr1 = Expression.Lambda<Func<int>>(ce1);
Console.WriteLine(expr1.Compile().Invoke()); 
// 10

// --------------在方法体内创建变量,经过操作之后再返回------------------

// 1.创建方法体表达式 2.在方法体内声明变量并附值 3. 返回该变量
ParameterExpression param2 = Expression.Parameter(typeof(int));
BlockExpression block2 = Expression.Block(
    new[]{param2},
    Expression.AddAssign(param2,Expression.Constant(20)),
    param2
    );
Expression<Func<int>> expr2 = Expression.Lambda<Func<int>>(block2);
Console.WriteLine(expr2.Compile().Invoke());
// 20

// -------------利用GotoExpression返回值-----------------------------------

LabelTarget returnTarget = Expression.Label(typeof(Int32));
LabelExpression returnLabel = Expression.Label(returnTarget,Expression.Constant(10,typeof(Int32)));

// 为输入参加+10之后返回
ParameterExpression inParam3=Expression.Parameter(typeof(int));
BlockExpression block3 = Expression.Block(
    Expression.AddAssign(inParam3,Expression.Constant(10)),
    Expression.Return(returnTarget,inParam3),
    returnLabel);

Expression<Func<int,int>> expr3 = Expression.Lambda<Func<int,int>>(block3,inParam3);
Console.WriteLine(expr3.Compile().Invoke(20));
// 30

    大家地方列出了2个例证,都足以兑现在表明式树中再次来到值,第3种和第三种其实是平等的,那正是将我们要回到的值所在的表明式写在block的末尾一个参数。而第二种大家是行使了goto
语句,若是大家在表明式中想跳出循环,恐怕提前退出方式它就派上用场了。这们上壹篇中也有讲到Expression.Return的用法。当然,大家还是能够经过switch
case 来重返值,请看上面的switch case的用法。

//简单的switch case 语句
ParameterExpression genderParam = Expression.Parameter(typeof(int));
SwitchExpression swithExpression = Expression.Switch(
    genderParam, 
    Expression.Constant("不详"), //默认值
    Expression.SwitchCase(Expression.Constant("男"),Expression.Constant(1)),  
Expression.SwitchCase(Expression.Constant("女"),Expression.Constant(0))
//你可以将上面的Expression.Constant替换成其它复杂的表达式,ParameterExpression, BinaryExpression等, 这也是表达式灵活的地方, 因为归根结底它们都是继承自Expression, 而基本上我们用到的地方都是以基类作为参数类型接受的,所以我们可以传递任意类型的表达式。
    );

Expression<Func<int, string>> expr4 = Expression.Lambda<Func<int, string>>(swithExpression, genderParam);
Console.WriteLine(expr4.Compile().Invoke(1)); //男
Console.WriteLine(expr4.Compile().Invoke(0)); //女
Console.WriteLine(expr4.Compile().Invoke(11)); //不详

  有人说表明式繁琐,那作者认同,可有人说表达式倒霉精通,大概自个儿就不曾主意承认了。的确,表达式的门类有不少,光大家上一篇列出来的就有二三种,但运用起来并不复杂,大家只必要大致知道有些表明类型所代表的含义就行了。实际上Expression类为大家提供了一名目繁多的工厂方法来帮忙大家创设表明式,就像是我们地点用到的Constant,
Parameter,
SwitchCase等等。当然,自个儿出手胜过客人讲授百倍,笔者相信借使你手动的去敲一些例子,你会意识创造表达式树其实并不复杂。

从拉姆da表达式创造表明式树

当2个lambda表明式被分配到品种为Expression的的1个变量时,编写翻译器会分析生成代码成立三个表明式树来表示那个lambda
.

C#编写翻译器能从lambda生成表明式树(只怕从三个单行的lambda)。但它不可能转换来lambda评释(或多行lambda)。越来越多关于lambda音讯见Lambda
Expressions
.

上面包车型地铁代码例子表达了何等用C#来生成两个表明式树来代表多少个lambda表达式:
num => num < 5

Expression<Func<int, bool>> lambda = num => num < 5;

  上一篇循规蹈矩表明式树(1)我们最首要研商了哪些依据Lambda表达式以及通过代码的主意直接成立表明式树。表明式树首若是由不相同类别的表达式构成的,而在上文中大家也列出了相比常用的两种表明式类型,由于它自个儿协会的特色所以用代码写起来然免有某个繁琐,当然大家也不必然要坚忍不拔完全本人去写,唯有大家精通它了,我们才能更加好的去行使它。

       
为啥要学习表达式树?表明式树是将大家原本可以直接由代码编写的逻辑以表达式的格局存款和储蓄在树状的布局里,从而能够在运行时去分析那一个树,然后实施,完成动态的编纂和施行代码。LINQ
to
SQL就是透过把表达式树翻译成SQL来促成的,所以了演表达树有助于大家越来越好的通晓LINQ to SQL,同时假设你有趣味,可以用它创设出广大诙谐的事物来。

  本类别布置三篇,第2篇主要介绍表明式树的创始情势。第三篇主要介绍表明式树的遍历难点。第二篇,将利用表明式树构建贰个融洽的LinqProvider。 

相关文章