目录

写在前面

系列文章

表达式树解析

表达式树特性

编译表达树

总结

写在前面

让我们首先简单回顾一下上篇文章介绍的内容,上篇文章介绍了表达式树的基本概念(表达式树又称为“表达式目录树”,以数据形式表示语言级代码,它是一种抽象语法树或者说是一种数据结构),以及两种创建表达式树目录树的方式:以lambda表达式的方式创建,通过API静态方法创建。由于不能将有语句体的lambda表达式转换为表达式树,而有时我们又有这样的需求,那么这种情况你可以选择API的静态方法方式创建,在 .NET Framework 4 中,API 表达式树还支持赋值表达式和控制流表达式,比如循环、条件块和 try-catch 块等。

系列文章

Linq之Lambda表达式初步认识

Linq之Lambda进阶

Linq之隐式类型、自动属性、初始化器、匿名类

Linq之扩展方法

Linq之Expression初见

表达式树解析

我们可以通过API方式创建表达式树,那么我们有没有办法,将给定的表达式树进行解析,分别得到各个部分呢?答案是肯定,下面看一个例子。

有一个这样的表达式树

  //创建表达式树
Expression<Func<int, bool>> expTree = num => num >= ;

可以这样来解析,分别得到各个部分

             //创建表达式树
Expression<Func<int, bool>> expTree = num => num >= ;
//获取输入参数
ParameterExpression param = expTree.Parameters[];
//获取lambda表达式主题部分
BinaryExpression body = (BinaryExpression)expTree.Body;
//获取num>=5的右半部分
ConstantExpression right = (ConstantExpression)body.Right;
//获取num>=5的左半部分
ParameterExpression left = (ParameterExpression)body.Left;
//获取比较运算符
ExpressionType type = body.NodeType;
Console.WriteLine("解析后:{0} {1} {2}",left,type,right);

输出结果

是不是很爽?不知道到这里,你是否对ORM框架中,lambda表达式是如何转化为sql语句有那么一点点的灵感?没有没关系,咱们继续看一个例子。如果数据库中有Person这样的一个数据表。咱们项目中有对应的Person这样的一个持久化类。那么我们创建一个这样的一个查询方法,返回所有龄大于等于18岁的成年人的sql语句。

 namespace Wolfy.ORMDemo
{
class Program
{
static void Main(string[] args)
{
string sql = Query<Person>(person => person.Age >= );
Console.WriteLine(sql);
Console.Read();
}
/// <summary>
/// 得到查询的sql语句
/// </summary>
/// <param name="epression">筛选条件</param>
/// <returns></returns>
static string Query<T>(Expression<Func<T, bool>> epression) where T : class,new()
{
//获取输入参数
ParameterExpression param = epression.Parameters[];
//获取lambda表达式主体部分
BinaryExpression body = (BinaryExpression)epression.Body;
//解析 person.Age
Expression left = body.Left;
string name = (left as MemberExpression).Member.Name;
//获取主体的右部分
ConstantExpression right = (ConstantExpression)body.Right;
//获取运算符
ExpressionType nodeType = body.NodeType;
StringBuilder sb = new StringBuilder();
//使用反射获取实体所有属性,拼接在sql语句中
Type type = typeof(T);
PropertyInfo[] properties = type.GetProperties();
sb.Append("select ");
for (int i = ; i < properties.Length; i++)
{
PropertyInfo property = properties[i];
if (i == properties.Length - )
{
sb.Append(property.Name + " ");
}
else
{
sb.Append(property.Name + " ,");
}
}
sb.Append("from ");
sb.Append(type.Name);
sb.Append(" where ");
sb.Append(name);
if (nodeType == ExpressionType.GreaterThanOrEqual)
{
sb.Append(">=");
}
sb.Append(right);
return sb.ToString();
}
}
class Person
{
public int Age { set; get; }
public string Name { set; get; }
}
}

输出结果

是不是很方便?传进来一个lambda表达式,就可以通过orm框架内部解析,然后转化为sql语句。也就是通过编写lambda就等于写了sql语句,也不用担心不会写sql语句了。

表达式树特性

表达式树应具有永久性。 这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。

那如何修改呢?

可以通过 ExpressionVisitor类遍历现有表达式树,并复制它访问的每个节点。

一个例子

在项目中添加一个AndAlsoModifier 类。

将表达式树中的AndAlse修改为OrElse,代码如下:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;
namespace Wolfy.ExpressionModifyDemo
{
/*该类继承 ExpressionVisitor 类,并且专用于修改表示条件 AND 运算的表达式。
* 它将这些运算从条件 AND 更改为条件 OR。
* 为此,该类将重写基类型的 VisitBinary 方法,这是因为条件 AND 表达式表示为二元表达式。
* 在 VisitBinary 方法中,如果传递到该方法的表达式表示条件 AND 运算,
* 代码将构造一个包含条件 OR 运算符(而不是条件 AND 运算符)的新表达式。
* 如果传递到 VisitBinary 的表达式不表示条件 AND 运算,则该方法交由基类实现来处理。
* 基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。*/
public class AndAlsoModifier : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.AndAlso)
{
Expression left = this.Visit(node.Left);
Expression right = this.Visit(node.Right);
//修改AndAlse为OrElse
return Expression.MakeBinary(ExpressionType.OrElse, left, right, node.IsLiftedToNull, node.Method);
}
return base.VisitBinary(node);
}
}
}

测试代码

 namespace Wolfy.ExpressionModifyDemo
{
class Program
{
static void Main(string[] args)
{
Expression<Func<string, bool>> expr = name => name.Length > && name.StartsWith("G");
//修改前
Console.WriteLine(expr);
AndAlsoModifier treeModifier = new AndAlsoModifier();
Expression modifiedExpr = treeModifier.Modify((Expression)expr);
//修改后
Console.WriteLine(modifiedExpr);
Console.Read();
}
}
}

输出结果

小结:修改表达式树,需继承ExpressionVisitor类,并重写它的VisitBinary(如果是类似AND这类的二元表达式)方法。再举一个例子,如果要将大于修改为小于等于,可修改VisitBinary方法的实现。

         protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.GreaterThan)
{
Expression left = this.Visit(node.Left);
Expression right = this.Visit(node.Right);
//修改> 为<=
return Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, right, node.IsLiftedToNull, node.Method);
}
return base.VisitBinary(node);
}

结果

编译表达树

Expression<TDelegate> 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。

还以最上面的那个表达式树为例

  //创建表达式树
Expression<Func<int, bool>> expTree = num => num >= ;

有这样的一个表达式树,现在,我想直接输入一个值,然后得到结果,该如何办呢?可以这样

             //创建表达式树
Expression<Func<int, bool>> expTree = num => num >= ;
// Compile方法将表达式树描述的 lambda 表达式编译为可执行代码,并生成表示该 lambda 表达式的委托。
Func<int, bool> func = expTree.Compile();
//结果
bool result = func();//true
Console.WriteLine(result);

总结

1.通过表达式解析,你可以得到表达式树的各个部分。你会发现如果你写的方法的参数是Expression<Func<t,t>>类型的,你可以更好的使用lambda表达式的特性,操作更方便。例子中,也简单分析了,ORM框架中,是如何将Lambda表达式解析为sql语句的,也希望能激发你的兴趣。

2.表达式树具有永久性的特性,一经创建,如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。具体操作可参考上面的例子。

3.通过Complie方法编译后的表达式树,就是一个委托,委托对应的方法的方法体就是表达式树中的lambda表达式,你可以像使用委托一样去使用它。有时你嫌麻烦也可以类似这样直接使用

 bool result = expTree.Compile()();

参考文章

http://msdn.microsoft.com/zh-cn/library/bb397951.aspx

http://msdn.microsoft.com/zh-cn/library/bb546136.aspx

Linq之Expression进阶的更多相关文章

  1. Linq之Expression高级篇(常用表达式类型)

    目录 写在前面 系列文章 变量表达式 常量表达式 条件表达式 赋值表达式 二元运算符表达式 一元运算符表达式 循环表达式 块表达式 总结 写在前面 首先回顾一下上篇文章的内容,上篇文章介绍了表达式树的 ...

  2. Linq之Expression初见

    目录 写在前面 系列文章 Expression 表达式树创建方式 一个例子 总结 写在前面 上篇文章介绍了扩展方法,这篇文章开始将陆续介绍在linq中使用最多的表达式树的相关概念,以概念及例子一一列出 ...

  3. 动态拼接linq 使用Expression构造动态linq语句

    最近在做动态构造linq语句,从网上找了很多,大多数,都是基于一张表中的某一个字段,这样的结果,从网上可以搜到很多.但如果有外键表,需要动态构造外键表中的字段,那么问题来了,学挖掘机哪家强?哦,不是, ...

  4. LINQ的Expression与delegate表达式

    Linq的delegate表达式,Insus.NET觉得它封装得好,让开发时简化了很多代码,而且容易阅读与检索. 比如,我们需要计算优惠给客户金额,打85%折,可以这样写: using System; ...

  5. Linq之Lambda进阶

    目录 写在前面 系列文章 带有标准查询运算符的Lambda Lambda中类型推断 Lambda表达式中变量作用域 异步Lambda 总结 写在前面 上篇文章介绍了Lambda的基本概念以及匿名方法, ...

  6. Linq To Sql进阶系列(六)用object的动态查询与保存log篇

    动态的生成sql语句,根据不同的条件构造不同的where字句,是拼接sql 字符串的好处.而Linq的推出,是为了弥补编程中的 Data != Object 的问题.我们又该如何实现用object的动 ...

  7. .NET深入实战系列—Linq to Sql进阶

    最近在写代码的过程中用到了Linq查询,在查找资料的过程中发现网上的资料千奇百怪,于是自己整理了一些关于Linq中容易让人困惑的地方. 本文全部代码基于:UserInfo与Class两个表,其中Cla ...

  8. linq入门系列导航

    写在前面 为什么突然想起来学学linq呢?还是源于在跟一个同事聊天的时候,说到他们正在弄得一个项目,在里面用到了linq to sql.突然想到距上次使用linq to sql是三年前的事情了.下班回 ...

  9. Linq之Linq to Sql

    目录 写在前面 系列文章 Linq to sql 总结 写在前面 上篇文章介绍了linq to xml的相关内容,linq to xml提供一种更便捷的创建xml树,及查询的途径.这篇文章将继续介绍l ...

随机推荐

  1. Window I/O 完成端口 (Windows I/O Completion Port (IOCP))

    相关对象 IO EndPoint, 所有支持重叠IO(overlapped IO)的设备,比如文件,Winsock,管道等. IOCP, IO完成端口内核对象,可以使用API CreateIoComp ...

  2. Linux 下子线程 exit code 在主线程中的使用

    Linux线程函数原型是这样的: void* thread_fun(void* arg) 它的返回值是 空类型指针,入口参数也是 空类型指针.那么线程的 exit code 也应该是 void * 类 ...

  3. BusyBox Init

    嵌入式系统内核启动后的第一个程序就是init,一般位于/sbin/init(一般是符号链接到/bin/busybox), 但有些也会直接放在根目录下如linuxrc,busybox的init不支持多级 ...

  4. jemter的使用(三)

    前面的文章已经把接口请求.响应等前序工作做好,那么如何施加压力呢,看下面 1.点击线程组,设置线程属性,其中:线程数即并发用户数,ramp-up period是多长时间初始化上面的并发用户数,循环次数 ...

  5. 分层图+最短路算法 BZOJ 2763: [JLOI2011]飞行路线

    2763: [JLOI2011]飞行路线 Time Limit: 10 Sec  Memory Limit: 128 MB Description Alice和Bob现在要乘飞机旅行,他们选择了一家相 ...

  6. 优化后的二次测试Miller_Rabin素性测试算法

    ll random(ll n) { return (ll)((double)rand()/RAND_MAX*n + 0.5); } ll pow_mod(ll a,ll p,ll n) { ) ; l ...

  7. Codeforces Zepto Code Rush 2014 -C - Dungeons and Candies

    这题给的一个教训:Codeforces没有超时这个概念.本来以为1000*(1000+1)/2*10*10要超时的.结果我想多了. 这题由于k层都可能有关系,所以建一个图,每两个点之间连边,边权为n* ...

  8. HDU 1251 统计难题

    字典树又一基本题 代码: #include <iostream> #include <cstdio> #include <cstring> #include < ...

  9. 纯js和纯css+html制作的手风琴的效果

    一:纯css+html的手风琴效果 这种用css写的手风琴比较简单,主要是应用到css中的,transition属性. 代码如下: <!DOCTYPE HTML> <html> ...

  10. Ajax类库需要注意的问题

    构建Ajax类库时,注意四步就可以了: 1:创建Ajax  对象 2:链接服务器 3:发送请求 4:返回响应 下面是我自己写的一个Ajax类库: function ajax(url,fnn,fai) ...