Linq之Expression进阶

目录

写在前面

系列文章

表达式树解析

表达式树特性

编译表达树

总结

写在前面

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

系列文章

Linq之Lambda表达式初步认识

Linq之Lambda进阶

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

Linq之扩展方法

Linq之Expression初见

表达式树解析

我们可以通过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

时间: 2024-11-07 01:28:44

Linq之Expression进阶的相关文章

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

目录 写在前面 系列文章 变量表达式 常量表达式 条件表达式 赋值表达式 二元运算符表达式 一元运算符表达式 循环表达式 块表达式 总结 写在前面 首先回顾一下上篇文章的内容,上篇文章介绍了表达式树的解析和编译.如果忘记了,可以通过下面系列文章提供的入口进行复习.这篇文章将介绍常见的表达式类型. 常见的表达式类型都有个共同的基类Expression.创建这些类型的对象,是通过API的方式创建的(也就是Expression的静态方法),首先引入命名空间: 1 using System.Linq.E

Linq之Expression初见

目录 写在前面 系列文章 Expression 表达式树创建方式 一个例子 总结 写在前面 上篇文章介绍了扩展方法,这篇文章开始将陆续介绍在linq中使用最多的表达式树的相关概念,以概念及例子一一列出如何在代码中使用Expression. 系列文章 Linq之Lambda表达式初步认识 Linq之Lambda进阶 Linq之隐式类型.自动属性.初始化器.匿名类 Linq之扩展方法 Expression 还是老样子,首先看MSDN中对表达式树的描述 表达式树以树形数据结构表示代码,其中每一个节点都

LINQ之旅—进阶篇

(1)Part 1:LINQ之旅—基础篇 (2)Part 2:LINQ之旅—进阶篇 (3)Part 3:LINQ之旅—高级篇

Linq之Lambda进阶

目录 写在前面 系列文章 带有标准查询运算符的Lambda Lambda中类型推断 Lambda表达式中变量作用域 异步Lambda 总结 写在前面 上篇文章介绍了Lambda的基本概念以及匿名方法,本篇继续介绍Lambda的一些内容,既然学了,就要总结的全面一点. 系列文章 Linq之Lambda表达式初步认识 带有标准查询运算符的Lambda 什么事标准查询运算符? “标准查询运算符”是组成语言集成查询 (LINQ) 模式的方法. 大多数这些方法都在序列上运行,其中的序列是一个对象,其类型实

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

动态的生成sql语句,根据不同的条件构造不同的where字句,是拼接sql 字符串的好处.而Linq的推出,是为了弥补编程中的 Data != Object 的问题.我们又该如何实现用object的动态查询呢? 1,用object的查询是什么?我们可以简单的举这么一个例子.我们到公安局查找一个人.首先,我们会给出他的一些特征,比如,身高多少,年龄多少,性别,民族等.那么,我们把这个人的一些特征输入电脑.我们希望,电脑能给我们返回这个人的信息.而实际上,有相同特征的人太多了,常常返回一个集合.那让

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

最近在写代码的过程中用到了Linq查询,在查找资料的过程中发现网上的资料千奇百怪,于是自己整理了一些关于Linq中容易让人困惑的地方. 本文全部代码基于:UserInfo与Class两个表,其中Class中的UserId与UserInfo中的Id对应 本文唯一访问地址:http://www.cnblogs.com/yubaolee/p/BestLinqQuery.html linq联合查询 内联查询 内联是一个实际使用频率很高的查询,它查询两个表共有的且都不为空的部分 from user in

linq入门系列导航

写在前面 为什么突然想起来学学linq呢?还是源于在跟一个同事聊天的时候,说到他们正在弄得一个项目,在里面用到了linq to sql.突然想到距上次使用linq to sql是三年前的事情了.下班回到家,翻看了电脑上面关于linq的笔记,它已经逃得无影无踪了.也怪我,没事瞎折腾电脑,早不知道放哪儿了,隐隐约约记得之前写过这样的笔记.没办法,只能重新整理学习了.这也是临近春节,还是把导航放出来吧,感兴趣的,有想回家给自己充充电的,也可以参考一下. 系列文章 Linq之Lambda表达式初步认识

Linq之Linq to XML

目录 写在前面 系列文章 linq to xml 总结 写在前面 在很多情况下,都可以见到使用xml的影子.例如,在 Web 上,在配置文件.Microsoft Office Word 文件(将word文档另存为xml文件,这也提供了一种通过操作xml,操作word的一种方式)以及数据库中,都可以看到 XML.而linq to xml提供了一种操作xml更便捷的方式. 系列文章 Linq之Lambda表达式初步认识 Linq之Lambda进阶 Linq之隐式类型.自动属性.初始化器.匿名类 Li

Linq之延迟加载特性

目录 写在前面 系列文章 延迟加载 总结 写在前面 上篇文章介绍了linq中常见的几个关键字,并列举了几个例子,算是对linq如何使用有了初步了解.上篇文章中也提到了,能够使用linq的场合有一个要求:实现IEnumerable<T>泛型接口,或者类型兼容(可以通过Cast方法转换,比如ArrayList). 系列文章 Linq之Lambda表达式初步认识 Linq之Lambda进阶 Linq之隐式类型.自动属性.初始化器.匿名类 Linq之扩展方法 Linq之Expression初见 Lin