LINQ那些事儿(4)- Query Expression和Query Operator
我学习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
All the posts in this blog are provided "AS IS" with no warranties, and confer no rights. Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 2.5 China Mainland License.