Expression Tree Basics表达式树基础
本文基本是对一篇博文的翻译,原址:Expression Tree Basics。
大部分Linq新手会觉得表达式树难以理解和掌握。这篇文字希望引导初学者神速了解Linq,并终止得到一个
茅塞顿开的感觉。
表达式树提供了把执行代码转成数据的功能,这种能力对于想在执行时修改或者转化代码的人非常有价值。你
可以把c#代码如Linq 查询表达式放到另个进程去执行,比如SQL database。
但是,首先要说明的是你只会在文章的最后才会容易理解到它对转化代码到数据有很大的帮助。首先,我会提
供一个简单场景。让我们从一个简单的实例.
动态方法理解
考虑到这样一个lambda表达式:
Func<int, int, int> function = (a,b) => a + b;
这句表达式包含三个部分:.
1,定义Func<int, int, int> function
2,等于操作符: =
3, lambda 表达式: (a,b) => a + b;
这条动态方法指出了两数字相加的原始代码。这条lambda表达式的传统代码可以这么写:
public int function(int a, int b)
{
return a + b;
}
然后调用:
int c = function(3, 5);
或者用委托的方式来表达:
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
上面的委托也许写的有争议,但是可以帮助我们理解动态方法。
表达式树分析
表达式树不是可执行的代码,它一数据结构的方式存在。那么代码是怎么保存在一课表达式树中的呢?
Linq 提供了简单语法用于转化代码成数据。第一步是添加引用:
using System.Linq.Expressions;
之后就可以添加如下的表达式树:
Expression<Func<int, int, int>> expression = (a,b) => a + b;
这个实例通过ExpressionTreeVisualizer你可以看到其结构如下图:
这个实例是一个Expression<TDelegate>对象,Expression<TDelegate> 包含四个属性:
Body: Retrieve the body of the expression.
Parameters: Retrieve the parameters of the lambda expression.
NodeType: Gets the ExpressionType for some node in the tree. This is an enumeration of 45
different values, representing all the different possible types of expression nodes, such as
those that return constants, those that return parameters, those that decide whether one value
is less than another (<), those that decide if one is greater than another (>), those that add
values together (+), etc.
Type: Gets the static type of the expression. In this case, the expression is of type Func<int,
int, int>.
在ExpressionTreeVisualizer中如图:
以下代码展示四个属性:
Console.WriteLine("Param 1: {0}, Param 2: {1}", expression.Parameters[0], expression.Parameters
[1]);
BinaryExpression body = (BinaryExpression)expression.Body;
ParameterExpression left = (ParameterExpression)body.Left;
ParameterExpression right = (ParameterExpression)body.Right;
Console.WriteLine(expression.Body);
Console.WriteLine(" The left part of the expression: " +
"{0}{4} The NodeType: {1}{4} The right part: {2}{4} The Type: {3}{4}",
left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine);
结果:
(a + b)
The left part of the expression: a
The NodeType: Add
The right part of the expression: b
The Type: System.Int32
通过图和代码,我们可以发现所有表达式中的元素都是成了一个数据结构中的一个节点。表达式树就是节点的
集合。
编译表达式:从数据转成可执行代码
int result = expression.Compile()(3, 5);
Console.WriteLine(result);
IQueryable<T> 和 Expression Trees(数据查询和表达式树)
现在你应该有对表达式树你应该有了一个初步的概念了。让我们更深入的探讨下表达式在Linq to SQL的应用
。看一个Linq to SQL的实例:
var query = from c in db.Customers
where c.City == "Nantes"
select new { c.City, c.CompanyName };
以上语句将返回一个IQueryable类型的结果,下面是IQueryable的定义:
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
注意到IQueryable 包含了一个Expression类型的属性。这条属性用来保存和该IQueryable对象关联的表达式
树。它以数据的方式保存了可执行的查询语句。你可以通过ExpressionTreeVisualizer取查看当前返回的
IQueryable实例的Expression,除了更多节显得复杂外,基本结构和之前看到图一模一样。
为什么要把Linq to SQL 表达式树转化成表达式树?
如果你耐心的看到这,你会记得一开始我们就提出了这个问题,是时候回到这个为什么了。
Linq to SQ 查询语句不会在c#应用程序中执行,而是转化成SQL 语句在数据库上去执行。比如说如下Linq
to SQL :
var query = from c in db.Customers
where c.City == "Nantes"
select new { c.City, c.CompanyName };
先转化成如下sql语句然后到数据库服务器上执行查询:
SELECT [t0].[City], [t0].[CompanyName]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[City] = @p0
查询代码转成字符串然后执行在另个进程。明显的使用表达式树这种数据结构来作为sql的中转比使用IL代码
或者其他可执行代码更容易。负责表达式树向sql语句的转化的逻辑能保证使输出最好的sql查询语句。
表达式树生而为把代码转化为字符串以运行于其他进程。
因为查询来自编译器对抽象数据结构的解析,所以编译器可以可以解析任何形式的数据结构。它不要求预先指
定执行顺序,或者执行的方式。理论上,它可以不考虑诸多环境限制,比如网络,数据库,当前的返回结果是
否可用。实际上,在Linq to SQL中就不受以上限制。更进一步,你也可以自己写一套转化和解析表达式树,
完全不同于Linq to sql的东西。
总结
表达式树提供以数据结构的形式保存了可执行代码,并且可以把代码表示的逻辑转换成字符串语句的基础功能
同时还有再把数据结构编译并执行的功能。Linq to sql 具体使用它的基础功能,把代码逻辑转成sql语句。