目录
写在前面
让我们首先简单回顾一下上篇文章介绍的内容,上篇文章介绍了表达式树的基本概念(表达式树又称为“表达式目录树”,以数据形式表示语言级代码,它是一种抽象语法树或者说是一种数据结构),以及两种创建表达式树目录树的方式:以lambda表达式的方式创建,通过API静态方法创建。由于不能将有语句体的lambda表达式转换为表达式树,而有时我们又有这样的需求,那么这种情况你可以选择API的静态方法方式创建,在 .NET Framework 4 中,API 表达式树还支持赋值表达式和控制流表达式,比如循环、条件块和 try-catch 块等。
系列文章
表达式树解析
我们可以通过API方式创建表达式树,那么我们有没有办法,将给定的表达式树进行解析,分别得到各个部分呢?答案是肯定,下面看一个例子。
有一个这样的表达式树
1 //创建表达式树 2 Expression<Func<int, bool>> expTree = num => num >= 5;
可以这样来解析,分别得到各个部分
1 //创建表达式树 2 Expression<Func<int, bool>> expTree = num => num >= 5; 3 //获取输入参数 4 ParameterExpression param = expTree.Parameters[0]; 5 //获取lambda表达式主题部分 6 BinaryExpression body = (BinaryExpression)expTree.Body; 7 //获取num>=5的右半部分 8 ConstantExpression right = (ConstantExpression)body.Right; 9 //获取num>=5的左半部分 10 ParameterExpression left = (ParameterExpression)body.Left; 11 //获取比较运算符 12 ExpressionType type = body.NodeType; 13 Console.WriteLine("解析后:{0} {1} {2}",left,type,right);
输出结果
是不是很爽?不知道到这里,你是否对ORM框架中,lambda表达式是如何转化为sql语句有那么一点点的灵感?没有没关系,咱们继续看一个例子。如果数据库中有Person这样的一个数据表。咱们项目中有对应的Person这样的一个持久化类。那么我们创建一个这样的一个查询方法,返回所有龄大于等于18岁的成年人的sql语句。
1 namespace Wolfy.ORMDemo 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 string sql = Query<Person>(person => person.Age >= 18); 8 Console.WriteLine(sql); 9 Console.Read(); 10 } 11 /// <summary> 12 /// 得到查询的sql语句 13 /// </summary> 14 /// <param name="epression">筛选条件</param> 15 /// <returns></returns> 16 static string Query<T>(Expression<Func<T, bool>> epression) where T : class,new() 17 { 18 //获取输入参数 19 ParameterExpression param = epression.Parameters[0]; 20 //获取lambda表达式主体部分 21 BinaryExpression body = (BinaryExpression)epression.Body; 22 //解析 person.Age 23 Expression left = body.Left; 24 string name = (left as MemberExpression).Member.Name; 25 //获取主体的右部分 26 ConstantExpression right = (ConstantExpression)body.Right; 27 //获取运算符 28 ExpressionType nodeType = body.NodeType; 29 StringBuilder sb = new StringBuilder(); 30 //使用反射获取实体所有属性,拼接在sql语句中 31 Type type = typeof(T); 32 PropertyInfo[] properties = type.GetProperties(); 33 sb.Append("select "); 34 for (int i = 0; i < properties.Length; i++) 35 { 36 PropertyInfo property = properties[i]; 37 if (i == properties.Length - 1) 38 { 39 sb.Append(property.Name + " "); 40 } 41 else 42 { 43 sb.Append(property.Name + " ,"); 44 } 45 } 46 sb.Append("from "); 47 sb.Append(type.Name); 48 sb.Append(" where "); 49 sb.Append(name); 50 if (nodeType == ExpressionType.GreaterThanOrEqual) 51 { 52 sb.Append(">="); 53 } 54 sb.Append(right); 55 return sb.ToString(); 56 } 57 } 58 class Person 59 { 60 public int Age { set; get; } 61 public string Name { set; get; } 62 } 63 }
输出结果
是不是很方便?传进来一个lambda表达式,就可以通过orm框架内部解析,然后转化为sql语句。也就是通过编写lambda就等于写了sql语句,也不用担心不会写sql语句了。
表达式树特性
表达式树应具有永久性。 这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。
那如何修改呢?
可以通过 ExpressionVisitor类遍历现有表达式树,并复制它访问的每个节点。
一个例子
在项目中添加一个AndAlsoModifier 类。
将表达式树中的AndAlse修改为OrElse,代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 namespace Wolfy.ExpressionModifyDemo 8 { 9 /*该类继承 ExpressionVisitor 类,并且专用于修改表示条件 AND 运算的表达式。 10 * 它将这些运算从条件 AND 更改为条件 OR。 11 * 为此,该类将重写基类型的 VisitBinary 方法,这是因为条件 AND 表达式表示为二元表达式。 12 * 在 VisitBinary 方法中,如果传递到该方法的表达式表示条件 AND 运算, 13 * 代码将构造一个包含条件 OR 运算符(而不是条件 AND 运算符)的新表达式。 14 * 如果传递到 VisitBinary 的表达式不表示条件 AND 运算,则该方法交由基类实现来处理。 15 * 基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。*/ 16 public class AndAlsoModifier : ExpressionVisitor 17 { 18 public Expression Modify(Expression expression) 19 { 20 return Visit(expression); 21 } 22 protected override Expression VisitBinary(BinaryExpression node) 23 { 24 if (node.NodeType == ExpressionType.AndAlso) 25 { 26 Expression left = this.Visit(node.Left); 27 Expression right = this.Visit(node.Right); 28 //修改AndAlse为OrElse 29 return Expression.MakeBinary(ExpressionType.OrElse, left, right, node.IsLiftedToNull, node.Method); 30 } 31 return base.VisitBinary(node); 32 } 33 } 34 }
测试代码
1 namespace Wolfy.ExpressionModifyDemo 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Expression<Func<string, bool>> expr = name => name.Length > 10 && name.StartsWith("G"); 8 //修改前 9 Console.WriteLine(expr); 10 AndAlsoModifier treeModifier = new AndAlsoModifier(); 11 Expression modifiedExpr = treeModifier.Modify((Expression)expr); 12 //修改后 13 Console.WriteLine(modifiedExpr); 14 Console.Read(); 15 } 16 } 17 }
输出结果
小结:修改表达式树,需继承ExpressionVisitor类,并重写它的VisitBinary(如果是类似AND这类的二元表达式)方法。再举一个例子,如果要将大于修改为小于等于,可修改VisitBinary方法的实现。
1 protected override Expression VisitBinary(BinaryExpression node) 2 { 3 if (node.NodeType == ExpressionType.GreaterThan) 4 { 5 Expression left = this.Visit(node.Left); 6 Expression right = this.Visit(node.Right); 7 //修改> 为<= 8 return Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, right, node.IsLiftedToNull, node.Method); 9 } 10 return base.VisitBinary(node); 11 }
结果
编译表达树
Expression<TDelegate> 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。
还以最上面的那个表达式树为例
1 //创建表达式树 2 Expression<Func<int, bool>> expTree = num => num >= 5;
有这样的一个表达式树,现在,我想直接输入一个值,然后得到结果,该如何办呢?可以这样
1 //创建表达式树 2 Expression<Func<int, bool>> expTree = num => num >= 5; 3 // Compile方法将表达式树描述的 lambda 表达式编译为可执行代码,并生成表示该 lambda 表达式的委托。 4 Func<int, bool> func = expTree.Compile(); 5 //结果 6 bool result = func(10);//true 7 Console.WriteLine(result);
总结
1.通过表达式解析,你可以得到表达式树的各个部分。你会发现如果你写的方法的参数是Expression<Func<t,t>>类型的,你可以更好的使用lambda表达式的特性,操作更方便。例子中,也简单分析了,ORM框架中,是如何将Lambda表达式解析为sql语句的,也希望能激发你的兴趣。
2.表达式树具有永久性的特性,一经创建,如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。具体操作可参考上面的例子。
3.通过Complie方法编译后的表达式树,就是一个委托,委托对应的方法的方法体就是表达式树中的lambda表达式,你可以像使用委托一样去使用它。有时你嫌麻烦也可以类似这样直接使用
1 bool result = expTree.Compile()(10);
参考文章
http://msdn.microsoft.com/zh-cn/library/bb397951.aspx
http://msdn.microsoft.com/zh-cn/library/bb546136.aspx