我学习LINQ的时候是直接看MSDN和LINQ team的blog,经常会被里面的一些名词弄混,下面这些名词你都弄懂了吗?
Expression Tree
Expression
Lambda Expression
Query Expression
Query Operator
Expression Tree
和Expression的区别类似XmlNode和XmlElement的区别。Expression Tree用于表达对IQueryable<T>类型数据源的查询树,是Select/Where/From等多个Query method嵌套,在运行时LINQ2SQL会根据Expression Tree来生成SQL语句。
Expression
确切的说是Expression类,为Expression Tree的每一个节点的基类,并提供了构造不同类型Expression的factory method。在System.Linq.Expression命名空间中提供了多种类型的Expression,经常用到的包括:
Class | Description |
BinaryExpression | 用来表达所有的二元运算,形式为(left) op (right)。如a+b, c && d等。 |
UnaryExpression | 用来表达所有的一元运算,形式为op(operand)。如!a,b++等。 |
ConstantExpression | 用来表达常量或外部变量(不在Expression Tree的控制结构内定义的变量)的定义。 |
ParameterExpression | 用来表达Expression Tree控制结构内的局部变量的定义 |
MethodCallExpression | 用来表达函数的调用 |
MemberExpression | 用来表达属性的访问 |
让我们用尝试构造Expression Tree来表达
context.Customers.Where(c => c.City == “London”)
// context.Customers
ConstantExpression customersExpr = Expression.Constant(context.Customers);
// 定义Customer c
ParameterExpression parameterExpr = Expression.Parameter(typeof(Customer), "c");
// 访问c.City属性?
MemberExpression cityExpr = Expression.Property(parameterExpr, "City");
// c.City == "London"
BinaryExpression equalExpr = Expression.Equal(cityExpr, Expression.Constant("London"));
// c => c.City == "London"
LambdaExpression conditionExpr = Expression.Lambda(equalExpr, parameterExpr);
// 注意: Where方法的签名是:
// Queryable.Where<T>(this IQueryable<T> source, Func<T, bool> predicate)
MethodCallExpression methodExpr = Expression.Call(
typeof(Queryable),
"Where",
new Type[] {typeof(Customer)},
customersExpr, conditionExpr
);
Console.WriteLine(methodExpr.ToString());
// 输出:Table(Customer).Where(c => (c.City = "London"))
另外一个有趣的是,在Queryable的源代码里有不少Expression.Quote()的调用,把a变成’(a)是什么意思? 来看看下面这段代码,分别表示() => 2 + 3和 () => ‘(2 + 3):
BinaryExpression expr = Expression.Add(
Expression.Constant(2), Expression.Constant(3));
var expr1 = Expression.Lambda<Func<int>>(expr);
var expr2 = Expression.Lambda<Func<BinaryExpression>>(Expression.Quote(expr));
Console.WriteLine(expr1.Compile()()); // 输出:5
Console.WriteLine(expr2.Compile()()); // 输出:(2 + 3)
Quote(A)的意思是,输出值为A表达式,而不是A表达式的计算值。
Lambda Expression
lambda expression可以是Expression Tree的一个节点,可以用来创建一个委托。但你知道如何用lambda expression来创建节点和委托吗?
LambdaExpression expr = () => 2 + 3; // 错误
LambdaExpression expr = Expression.Lambda(
Expression.Add(Expression.Constant(2), Expression.Constant(3))) // 正确,定义节点
Expression<Func<int>> expr = () => 2 + 3; //正确,定义节点
var expr = Expression.Lambda<Func<int>>(
Expression.Add(Expression.Constant(2), Expression.Constant(3))) // 正确,定义节点
Func<int> fn = () => 2 + 3; // 正确,定义委托
Func<int> fn = () => { return 2 + 3; } //正确,定义委托
这里问个问题,你认为下面的语句1和语句2会导致查询操作有什么区别(输出结果都是6)?答案在“LINQ那些事(8)”。
var context = GenerateContext();
Expression<Func<Customer, bool>> condition = c => c.City == "London";
var result1 = context.Customers.Where(condition); // 1
var result2 = context.Customers.Where(condition.Compile()); // 2
Console.WriteLine(result1.Count());
Console.WriteLine(result2.Count());
Query Operator
指的是在Enumerable和Queryable类中定义的用于用于对数据进行project/filter操作等的extension method,包括Where/Select/Join/OrderBy/GroupBy等。
Query Expression
刚接触LINQ的时候感觉最特别就是可以用类sql的语句在csharp代码里写查询语句
var result =
from c in context.Customers
where c.City == "London"
select c;
VS对Query expression提供了诸多支持,包括intelligent sense和编译检查等等。但select和where在IL里是没有,只是compiler在运行时根据query expression转化成对Query operator的调用。但是,并不是所有的query operator在query expression有等价表达,query expression支持的operator只有:
Select/SelectMany/Join/GroupJoin/Where/OrderBy/OrderByDescending/GroupBy/ThenBy/ThenByDescending。
既然query operator是通过extension method的方式提供,这就意味着我们可以通过自己编写extension method,来改变query expression的行为,我们看下面的代码:
public static class QueryOperatorExt
{
public static IQueryable<T> Where<T>(
this IQueryable<T> source, Expression<Func<T, bool>> predicate)
{
Console.WriteLine("Calling Where in the customer extension method");
return Queryable.Where(source, predicate);
}
}
class Program
{
static void Main(string[] args)
{
var context = GenerateContext();
var result =
from c in context.Customers
where c.City == "London"
select c;
Console.WriteLine(result.Count());
}
}
// 输出:
Calling Where in the customer extension method
6
参考资料
LINQ和DLR的Expression Tree http://rednaxelafx.javaeye.com/blog/237822
Lambda表达式 http://msdn.microsoft.com/zh-cn/library/bb397687.aspx
表达式目录树 http://msdn.microsoft.com/zh-cn/library/bb397951.aspx
总结:本节讨论了Expression Tree/Expression/Lambda Expression/Query expression/Query Operator的含义和区别,示例了如何构建Expression Tree,正确使用lambda expression,以及如何通过extension method来扩展query expression。
链接
1、 LINQ那些事儿(1)- 定义从关系数据库到Entity Class的映射
2、 LINQ那些事儿(2)- 简单对象的CRUD操作和Association的级联操作
4、 LINQ那些事儿(4)- Query Expression和Query Operator
6、 LINQ那些事儿(6)- DataContext的对象生命周期管理
7、 LINQ那些事儿(7)- 通过自定义IEnumerable<T>来扩展LINQ
8、LINQ那些事儿(8)- 通过自定义IQueryable<T>和IQueryableProvider来扩展LINQ