Linq之Expression进阶
目录
写在前面
让我们首先简单回顾一下上篇文章介绍的内容,上篇文章介绍了表达式树的基本概念(表达式树又称为“表达式目录树”,以数据形式表示语言级代码,它是一种抽象语法树或者说是一种数据结构),以及两种创建表达式树目录树的方式:以lambda表达式的方式创建,通过API静态方法创建。由于不能将有语句体的lambda表达式转换为表达式树,而有时我们又有这样的需求,那么这种情况你可以选择API的静态方法方式创建,在 .NET Framework 4 中,API 表达式树还支持赋值表达式和控制流表达式,比如循环、条件块和 try-catch 块等。
系列文章
表达式树解析
我们可以通过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进阶的更多相关文章
- Linq之Expression高级篇(常用表达式类型)
目录 写在前面 系列文章 变量表达式 常量表达式 条件表达式 赋值表达式 二元运算符表达式 一元运算符表达式 循环表达式 块表达式 总结 写在前面 首先回顾一下上篇文章的内容,上篇文章介绍了表达式树的 ...
- Linq之Expression初见
目录 写在前面 系列文章 Expression 表达式树创建方式 一个例子 总结 写在前面 上篇文章介绍了扩展方法,这篇文章开始将陆续介绍在linq中使用最多的表达式树的相关概念,以概念及例子一一列出 ...
- 动态拼接linq 使用Expression构造动态linq语句
最近在做动态构造linq语句,从网上找了很多,大多数,都是基于一张表中的某一个字段,这样的结果,从网上可以搜到很多.但如果有外键表,需要动态构造外键表中的字段,那么问题来了,学挖掘机哪家强?哦,不是, ...
- LINQ的Expression与delegate表达式
Linq的delegate表达式,Insus.NET觉得它封装得好,让开发时简化了很多代码,而且容易阅读与检索. 比如,我们需要计算优惠给客户金额,打85%折,可以这样写: using System; ...
- Linq之Lambda进阶
目录 写在前面 系列文章 带有标准查询运算符的Lambda Lambda中类型推断 Lambda表达式中变量作用域 异步Lambda 总结 写在前面 上篇文章介绍了Lambda的基本概念以及匿名方法, ...
- Linq To Sql进阶系列(六)用object的动态查询与保存log篇
动态的生成sql语句,根据不同的条件构造不同的where字句,是拼接sql 字符串的好处.而Linq的推出,是为了弥补编程中的 Data != Object 的问题.我们又该如何实现用object的动 ...
- .NET深入实战系列—Linq to Sql进阶
最近在写代码的过程中用到了Linq查询,在查找资料的过程中发现网上的资料千奇百怪,于是自己整理了一些关于Linq中容易让人困惑的地方. 本文全部代码基于:UserInfo与Class两个表,其中Cla ...
- linq入门系列导航
写在前面 为什么突然想起来学学linq呢?还是源于在跟一个同事聊天的时候,说到他们正在弄得一个项目,在里面用到了linq to sql.突然想到距上次使用linq to sql是三年前的事情了.下班回 ...
- Linq之Linq to Sql
目录 写在前面 系列文章 Linq to sql 总结 写在前面 上篇文章介绍了linq to xml的相关内容,linq to xml提供一种更便捷的创建xml树,及查询的途径.这篇文章将继续介绍l ...
随机推荐
- struts2 redirect 配置动态传递参数
<action name="actionName" class="com.towerking.TestAction" method="execu ...
- matlab ASCII 格式导入
matlab ASCII 格式导入 可以用fprintf函数,来代替save函数啊比如现在我有一个变量a=[0.1223 345.4544]如果我想保存它的话,可以用下面的程序:fid = fopen ...
- hadooop 运维之 container error exit code 1
hadoop container exit code: 1 在执行hadoop的时候,发现nodemanager 进程日志里面有这个错误. 网上搜索,一般找到的都是yarn classspath配置的 ...
- 解决客户端通过zookeeper连接到hbase时连接过多的问题
原因:客户端程序通过zookeeper访问hbase的连接数超过设置的默认链接数(默认数是30),连接数不够用会导致后续的连接连接不上去. 解决办法:设置hbase-site.xml配置文件,添加如下 ...
- MIT jos 6.828 Fall 2014 训练记录(lab 6)
源代码参见我的github: https://github.com/YaoZengzeng/jos 在这个实验中将实现一个基于Intel 82540M(又称E1000)的网卡驱动.不过,一个网卡驱动还 ...
- iOS9新特性——堆叠视图UIStackView
一.引言 随着autolayout的推广开来,更多的app开始使用自动布局的方式来构建自己的UI系统,autolayout配合storyBoard和一些第三方的框架,对于创建约束来说,已经十分方便,但 ...
- 图解Js event对象offsetX, clientX, pageX, screenX, layerX, x区别
通过 3 张图和 1 张表格,轻松区别 JavaScript Event 对象中的offsetX, clientX, pageX, screenX, layerX, x等属性. 一.测试代码如下: & ...
- 挖Linux中的古老缩略语
[2005-06-22 15:23][Nigel McFarlane][TechTarget] <<阅读原文>> Unix已经有35年历史了.许多人认为它开始于中世纪,这个中世 ...
- java遍历hashTable
//获取key值 Enumeration k = lovResults.keys();while(k.hasMoreElements()){ System.out.println(k.nextElem ...
- Google proto buffer的安装/使用
protobuf安装/使用原本是要在官网上下载的:http://protobuf.googlecode.com/files/protobuf-2.5.0.tar.gz可惜已被墙,幸好有好心人提供了以下 ...