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 ...
随机推荐
- [转]Android输出Log到文件
前言:开发中遇到mx4这款机型Eclipse联调不上,logcat看不了,需要输出生成文件查看调试信息.网上搜了下,功能很完善了.startService和过滤输出信息需要自己添加设置,另外注意添加权 ...
- Rhythmbox中文乱码问题的解决
Rhythmbox中文乱码问题的解决 Rhythmbox是Ubuntu自带的一款很优秀的音乐播放器,但是在处理中文时却不太友好,导入歌曲时中文会变成乱码 这个问题也是很好解决的. ** 1.Ctrl+ ...
- TestNG之参数化
TestNG提供了两种参数化的方式,一种是通过XML,一种是通过代码实现,下面对这两种方式做介绍. 一.通过xml /** * <suite name="Suite" par ...
- 深度优先搜索 codevs 1065 01字符串
codevs 1065 01字符串 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 输出仅有0和1组成的长度为n的字符串,并且 ...
- tarjan算法求割点cojs 8
tarjan求割点:cojs 8. 备用交换机 ★★ 输入文件:gd.in 输出文件:gd.out 简单对比时间限制:1 s 内存限制:128 MB [问题描述] n个城市之间有通讯网 ...
- mac和centos下git安装
mac下面的git安装,这篇文章写的很详细了http://www.cnblogs.com/ccdev/archive/2012/09/12/2682098.html 谈谈centos下的安装.我用的是 ...
- linux启动jmeter,执行./jmeter.sh报错解决方法
1.l-bash: ./jmeter.sh: Permission denied解决办法:jmeter.sh的执行权限改改,是权限不够chmod 777 jmeter.sh 2.An error oc ...
- Cookie测试工具小汇
现在很多网站都用到Cookies,特别是用户的登陆以及购物网站的购物车. Cookies 通常用来存储用户信息和用户在某应用系统的操作,当一个用户使用Cookies 访问了某一个应用系统时,Web 服 ...
- scroll滚动条插件初始化问题
一种特殊场景下是滚动条容器先隐藏,点击某个东西后显示出来.然后实例化滚动条.实例 js: var flag = true; document.getElementById('btn1').onclic ...
- Chrome 开发工具 Javascript 调试技巧
http://www.w3cplus.com/tools/dev-tips.html 一.Sources 面板介绍: Sources 面板分为左中右 3 部分左:Sources 当前页面加载的资源列表 ...