LINQ学习笔记:表达式树
构建查询表达式
本节中, 我们假设我们拥有一个这样的实体类:
1: [Table] public partial class Product
2:
3: {
4:
5: [Column(IsPrimaryKey=true)] public int ID;
6:
7: [Column] public string Description;
8:
9: [Column] public bool Discontinued;
10:
11: [Column] public DateTime LastSale;
12:
13: }
委托 VS 表达式树
让我们回忆一下:
1. 本地查询,使用的Enumerable操作符,使用委托
2. 解释查询(Interpreted Query),使用Queryable操作符,使用表达式树
我们可以比较一下Where操作符在Enumerable和Queryable当中的签名:
1: public static IEnumerable Where (this
2:
3: IEnumerable source,
4:
5: Funcbool> predicate)
6:
7: public static IQueryable Where (this
8:
9: IQueryable source,
10:
11: Expressionbool>> predicate)
当把他们嵌入到一个查询当中的时候,Lamdba表达式看上去都是一样的,无论它是绑定到Enumerable或者Queryable:
1: IEnumerable q1 = localProducts.Where
2:
3: (p => !p.Discontinued);
4:
5: IQueryable q2 = sqlProducts.Where
6:
7: (p => !p.Discontinued);
当你将一个Lambda表达式赋给一个中间变量的时候, 你必须显示地指示是将它绑定到委托(Func<>)或者是表达式树(Expression<>>)
编译表达式树
通过调用Compile我们可以将一个表达式树转换为委托. 当我们编写的方法返回可重用的表达式时这回带来特别的价值. 为了演示,我们将给Product类增加一个静态方法, 其返回一个bool值用于断言那些Discontinued并且在过去30天内销售的产品.
1: public partial class Product
2:
3: {
4:
5: public static Expressionbool>>
6:
7: IsSelling()
8:
9: {
10:
11: return p => !p.Discontinued &&
12:
13: p.LastSale > DateTime.Now.AddDays (-30);
14:
15: }
16:
17: }
(注:对于类似的扩展方法,我们应该编写一个全新的文件从而避免去覆盖由VS的设计器自动产生的文件.)
此方法可以同时被用于本地查询和解释查询,如下所示:
1: void Test( )
2:
3: {
4:
5: var dataContext = new MyTypedDataContext (“connectionString”);
6:
7: Product[] localProducts =
8:
9: dataContext.Products.ToArray( );
10:
11: IQueryable sqlQuery =
12:
13: dataContext.Products.Where(Product.IsSelling());
14:
15: IEnumerable localQuery =
16:
17: localProducts.Where(Product.IsSelling.Compile());
18:
19: }
相比之下, 我们并不能将一个委托转换为表达式树,这也使得表达式树更加有用.
AsQueryable
使用AsQueryable操作符可以编写用于操作本地或者远程序列的查询:
1: IQueryable FilterSortProducts
2:
3: (IQueryable input)
4:
5: {
6:
7: return from p in input
8:
9: where …
10:
11: order by …
12:
13: select p;
14:
15: }
16:
17: void Test()
18:
19: {
20:
21: var dataContext = new MyTypedDataContext (“connectionString”);
22:
23: Product[]localProducts =
24:
25: dataContext.Products.ToArray();
26:
27: var sqlQuery =
28:
29: FilterSortProducts (dataContext.Products);
30:
31: var localQuery =
32:
33: FilterSortProducts (localProducts.AsQueryable());
34:
35: }
AsQueryable对本地查询包装了一层Queryable<>外衣,这使得接下来的子查询都是针对表达式树的.当你开始枚举结果集的时候,表达式树会被隐式编译转换成为本地查询然后向往常一直执行.
表达式树
我们之前说过将一个Lambda表达式赋值给一个Expression类型变量会引起C#编译器解析表达式树.使用编程手段, 我们可以在运行时做相同的事情-换句话说, 从零开始动态创建表达式树. 结果集可以被转换为Expression并被使用于LINQ to SQL查询中,或者通过调用Compile将其转换为委托.
表达式DOM
一个表达式树是一个小型DOM. 每一个节点表示一个System.Linq.Expressions命名空间下的一个类型. 其基类是Expression(非泛型),而泛型Expression实际上是表示类型化的Lambda表达式.
Expression<>的基类是非泛型的LambdaExpression类, LambdaExpression提供了针对Labmbda表达式树的统一类型:任何Expression<>都看可以被转换为LambdaExpression.
为了创建表达式树, 我们并不需要直接实例化节点类,而是通过调用Expression类提供的静态方法:
1: //创建输入参数s
2: ParameterExpression p = Expression.Parameter(typeof(string), “s”);
3: //参数属性Length
4: MemberExpression stringLength = Expression.Property(p, “Length”);
5: //常量5
6: ConstantExpression five = Expression.Constant(5);
7: //比较操作符
8: BinaryExpression comparison = Expression.LessThan(stringLength, five);
9: Expression<string,bool>> lambda = Expression.Lambda<string,bool>>(comparison,p);
10: //转换为委托
11: Func<string, bool> runnable = lambda.Compile();
12: Console.WriteLine(runnable(“James”)); //False
13: Console.WriteLine(runnable(“dog”)); //True
此示例动态创建了一个如下的Lambda表达式:
1: Expression<string, bool>> f = s => s.Length < 5;
待续!