我學習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”)
01 // context.Customers
02 ConstantExpression customersExpr = Expression.Constant(context.Customers);
03 // 定義Customer c
04 ParameterExpression parameterExpr = Expression.Parameter(typeof(Customer), "c");
05 // 訪問c.City屬性?
06 MemberExpression cityExpr = Expression.Property(parameterExpr, "City");
07 // c.City == "London"
08 BinaryExpression equalExpr = Expression.Equal(cityExpr, Expression.Constant("London"));
09 // c => c.City == "London"
10 LambdaExpression conditionExpr = Expression.Lambda(equalExpr, parameterExpr);
11
12 // 注意: Where方法的簽名是:
13 // Queryable.Where<T>(this IQueryable<T> source, Func<T, bool> predicate)
14 MethodCallExpression methodExpr = Expression.Call(
15 typeof(Queryable),
16 "Where",
17 new Type[] {typeof(Customer)},
18 customersExpr, conditionExpr
19 );
20
21 Console.WriteLine(methodExpr.ToString());
22 // 輸出:Table(Customer).Where(c => (c.City = "London"))
另外一個有趣的是,在Queryable的源代碼裡有不少Expression.Quote()的調用,把a變成’(a)是什麼意思? 來看看下面這段代碼,分別表示() => 2 + 3和 () => ‘(2 + 3):
1 BinaryExpression expr = Expression.Add(
2 Expression.Constant(2), Expression.Constant(3));
3 var expr1 = Expression.Lambda<Func<int>>(expr);
4 var expr2 = Expression.Lambda<Func<BinaryExpression>>(Expression.Quote(expr));
5
6 Console.WriteLine(expr1.Compile()()); // 輸出:5
7 Console.WriteLine(expr2.Compile()()); // 輸出:(2 + 3)
Quote(A)的意思是,輸出值為A表達式,而不是A表達式的計算值。
Lambda Expression
lambda expression可以是Expression Tree的一個節點,可以用來創建一個委托。但你知道如何用lambda expression來創建節點和委托嗎?
01 LambdaExpression expr = () => 2 + 3; // 錯誤
02 LambdaExpression expr = Expression.Lambda(
03 Expression.Add(Expression.Constant(2), Expression.Constant(3))) // 正確,定義節點
04
05 Expression<Func<int>> expr = () => 2 + 3; //正確,定義節點
06 var expr = Expression.Lambda<Func<int>>(
07 Expression.Add(Expression.Constant(2), Expression.Constant(3))) // 正確,定義節點
08
09 Func<int> fn = () => 2 + 3; // 正確,定義委托
10 Func<int> fn = () => { return 2 + 3; } //正確,定義委托
這裡問個問題,你認為下面的語句1和語句2會導致查詢操作有什麼區別(輸出結果都是6)?答案在“LINQ那些事(8)”。
1 var context = GenerateContext();
2 Expression<Func<Customer, bool>> condition = c => c.City == "London";
3
4 var result1 = context.Customers.Where(condition); // 1
5 var result2 = context.Customers.Where(condition.Compile()); // 2
6
7 Console.WriteLine(result1.Count());
8 Console.WriteLine(result2.Count());
Query Operator
指的是在Enumerable和Queryable類中定義的用於用於對數據進行project/filter操作等的extension method,包括Where/Select/Join/OrderBy/GroupBy等。
Query Expression
剛接觸LINQ的時候感覺最特別就是可以用類sql的語句在csharp代碼裡寫查詢語句
1 var result =
2 from c in context.Customers
3 where c.City == "London"
4 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的行為,我們看下面的代碼:
01 public static class QueryOperatorExt
02 {
03 public static IQueryable<T> Where<T>(
04 this IQueryable<T> source, Expression<Func<T, bool>> predicate)
05 {
06 Console.WriteLine("Calling Where in the customer extension method");
07
08 return Queryable.Where(source, predicate);
09 }
10 }
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 var context = GenerateContext();
16 var result =
17 from c in context.Customers
18 where c.City == "London"
19 select c;
20
21 Console.WriteLine(result.Count());
22 }
23 }
24 // 輸出:
25 Calling Where in the customer extension method
26 6
總結:本節討論了Expression Tree/Expression/Lambda Expression/Query expression/Query Operator的含義和區別,示例了如何構建Expression Tree,正確使用lambda expression,以及如何通過extension method來擴展query expression。