構建查詢表達式
本節中, 我們假設我們擁有一個這樣的實體類:
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;
待續!