LINQ,語言集成查詢,就是把一些查詢操作集成到語言中(貌似是廢話),比 如查詢關系數據庫,而且提供一種一致的操作方式,不管最終的數據存儲在哪裡 ?內存中,遠程數據庫還是一Xml格式文件存儲,不僅僅如此,你還可以用你豐 富的想象力擴充自己的查詢。Linq to SQL無疑把Linq的能量發揮的淋漓盡致, 我們就以Linq to SQL的體系結構來學習一下Linq的整體框架。
在上兩章 裡面我們通過源代碼探討了關於DataContext的初始化和Table<TEntity> 對象的獲取,以及Provider的初始化。今天我們來看看Linq to SQL執行的大至 流程
假如我們寫下這樣的代碼:
DataContext dbCtx = new DataContext("server=localhost;database=cnblogs;user id=sa;pwd=sa");
Table<Post> posts = dbCtx.GetTable<Post>();
foreach (Post p in posts)
{
Console.WriteLine(p.Title);
}
在幕後到底發生了什麼呢?
看到foreach代碼大家肯定都 知道這個Table<Post>肯定實現了IEnumerable<Post>接口,這裡的 foreach的代碼和下面這個代碼的效果是一樣的,實際上最終也是轉換成這樣的 代碼:
IEnumerator<Post> iterator = posts.GetEnumerator();
while (iterator.MoveNext())
{
Post p = iterator.Current;
Console.WriteLine(p.Title);
}
既然如此那我們就得看看Table<TEntity>的 GetEnumerator方法了:
public IEnumerator<TEntity> GetEnumerator()
{
return ((IEnumerable<TEntity>)this.context.Provider.Execute(Expression.Constant(this)).ReturnValue).GetEnumerator();
}
從這裡可以看出它是通過調用IProvider的Execute方法。
關於Execute更深層次的內容,在本篇中並不涉及,你只要知道它根據 Lambda表達式生成SQL語句,然後對數據庫進行操作就OK了。
如果存在這 樣一個查詢:
Var result = posts.Where (p=>p.id==1);
這樣的一個查詢到底會發生什麼呢?實際上我 們根本無從得知,如果posts是一個內存中的集合那麼就是在內存中進行對象的 篩選,實際上就是執行一下一個foreach,然後將符合p.Id == 1這個條件的所有 Post對象都添加到返回的集合中;如果posts是一個Table<TEntity>對象 ,那麼這個查詢最後將生成SQL語句對數據庫進行操作。一樣是Where,傳入的參 數也是Lambda表達式,為什麼產生的結果卻不同呢?兩個Where真的是相同的麼 ?
實際上在我們對內存中的集合使用Where等擴展方法的時候是使用 Enumerable類對IEnumerable<T>的擴展。
我們再來看看 Table<TEntity>,它實現了IQueryable<T>接口, IQueryable<T>有一個IQueryProvider類型的成員。在System.Linq命名空 間下還有一個Queryable類,這個類是對IQueryable<T>接口進行擴展的。 那現在看來Linq to SQL和Linq to Objects並沒有什麼直接關系,唯一的就是靠 IEnumerable<T>這個建立起橋梁,讓它們查詢的方式語言都統一。
我們再來看看Enumerable和Queryable中的擴展方法有什麼不同:
Enumerable中的擴展方法接受的一般都是委托類型的參數,而Queryable 中接受的卻是Expression,但為什麼它們都可以接受Lambda表達式作為參數呢?
神奇的賦值符號“=”
Func<int,bool> IsTrue = x=>x==5;
Expresssion<Func<int,bool>> IsTrue = x =>x==5;
賦值符號右邊的表達式是一樣的,在第一個式子中是 將Lambda表達式賦值給委托,第二個式子中將Lambda賦值給Expression,這個東 西叫做表達式樹。
在第一個式子編譯的時候後面的表達式實際上會被轉 換為一個匿名方法,IsTrue也就是一個“方法的指針”,和我們已經 熟識的委托沒有什麼區別。而第二個式子在執行的時候右邊的表達式會被編譯為 一個樹的數據結構(C#編譯器實際上為我們做了詞法分析、語法分析了,在編譯 原理裡前兩個階段就是詞法分析和語法分析,詞法分析首先遍歷傳入的語言字符 串,在我們這裡就是x=>x==5,詞法分析器讀取每個字符,識別出標識符,常 量,關鍵字,運算符,詞法分析器的產出是Token(符號);然後語法分析器根 據該語言的語法范式將Token組織成一個樹形結構,用這個樹形的結構來表示該 語言的代碼文件,在我們這裡x=>x==5就是Lambda表達式這門“語言 ”的語句了,Expression就是那個樹)。Expression是一個遞歸形式的定 義,它有兩個屬性:Parameters,這個屬性就是Lambda表達式的參數,在上面的 代碼中就是:x,它還有一個屬性是Body,Body也是一個Expression類型,從這 裡我們看到Expression是這樣遞歸下去的。
通過上面的介紹,實際上 Queryable中的那些擴展方法所接受的Lambda表達式最後被編譯為樹數據結構。 這些樹數據結構攜帶有查詢表達式的語法,但是最終它們要查詢什麼樣的數據, 是數據庫?還是XML或是Web Service就要靠IQueryProvider來解析了,從這裡我 們大概可以看到這樣一個結構:
看到這個圖,那我們有幾種擴展Linq的方式呢?
第一種:通過 給IEnumerable或IQueryable添加擴展方法,這個就是利用C#語言的特性來達到 的。我將這種擴展稱之為橫向的擴展。
第二種:自己實現 IQueryProvider,這種擴展就是縱向的擴展了,提供自己的Provider,然後這個 Provider解析表達式樹生成最終具體的查詢操作。
小結
本文簡單 的展示一下Linq的體系結構,了解一下Linq的擴展點在哪裡,從這裡我們也能看 到,要做一個好擴展的系統,一個很重要的任務就是提煉接口,接口的粒度,接 口的職責等等都是核心關注點。使用接口將幾個類隔離,變化點也就封裝起來了 。