一、用IQueryable和IQueryProvider進行轉換
在LINQ to SQL中的所有查詢表達式中,數據源都是Table
1、IQueryable和相關接口的介紹
IQueryable
理解IQueryable的最簡單方式就是,把它看作一個查詢,在執行的時候,將會生成結果序列。從LINQ的角度看,由於是通過IQueryable的Expression屬性返回結果,所以查詢的詳細信息就保存於表達式樹中。一個查詢進行執行,就是開始遍歷IQueryable的過程(換句話說,即調用GetEnumerator方法,然後對其結果調用MoveNext方法),或者調用IQueryProvider上的Execute方法並傳遞表達式樹。給定任何IQueryable查詢後,你可通過執行如下步驟來創建新的查詢:
(1) 請求現有查詢的查詢表達式樹(使用Expression屬性);
(2) 構建一個新的表達式樹,包含最初的表達式和你想要的額外功能(例如,過濾、投影或排序);
(3) 請求現有查詢的查詢提供器(使用Provider屬性);
(4) 調用提供器的CreateQuery方法,傳遞新表達式樹。
2、模擬接口實現來記錄調用
這裏我們會編寫IQueryable和IQueryProvider的實現,FakeQueryProvider和FakeQuery類型。實現如下:
無參數的構造函數,主程序用它爲查詢創建普通“數據源”;而FakeQueryProvider則會調用另一個構造函數並傳入當前的查詢表達式。Expression.Constant(this)用作初始數據源表達式只是爲了展示查詢表示原始的對象。(例如,假設某個實現表示一個數據表——如果不使用任何查詢操作符,查詢將返回整個數據表。)
上述CreateQuery方法不執行真正的處理,而是作爲查詢的工廠方法。Execute重載方法只是在記錄調用日誌後返回空結果。通常情況下,在這裏應該完成大量的分析工作,以及對Web服務、數據庫或任何目標平臺的實際調用。
3、把表達式粘合在一起:Queryable的擴展方法
正如Enumerable類型包含着關於IEnumerable
①Enumerable的方法都使用委託作爲參數,而對於Queryable來說則需要表達式樹(Lambda表達式既可以被轉換爲委託實例,也可以轉換爲表達式樹);
②Enumerable的擴展方法會完成與對應查詢操作符相關的實際工作(至少會構建完成這些工作的迭代器)。而Queryable中的查詢操作符的“實現”做的事情非常少:它們僅僅基於參數創建一個新的查詢,或在查詢提供器上調用Execute。換句話說,它們只用來構建查詢和要執行的請求——不包含操作符背後的邏輯。這意味着,它們可用於任何使用表達式樹的LINQ提供器,但是它們單獨使用時沒有任何意義。它們是代碼和提供器細節之間的黏合劑。
4、模擬實際運行的查詢提供器
GetEnumerator只在最後才調用,而不在任何中間查詢中調用,並且在GetEnumerator被調用的時候,我們已經有了出現在原始查詢表達式中的所有信息。到目前爲止,單一的表達式樹已經捕獲了所有的信息。實際的表達式樹可能非常深且複雜,特別是在Where子句包含額外方法調用的時候。LINQ to SQL檢查表達式樹以算出應該執行什麼樣的查詢。當調用CreateQuery時,LINQ提供器能夠構建它們自己的查詢(以它們需要的任何形式),不過,當調用GetEnumerator的時候,看一下最後的表達式樹,可知道它們通常都比較簡單,這是因爲所有需要的信息都已經保存在同一個地方了。
二、LINQ友好的API和LINQ to XML
1、LINQ to XML中的核心類型
LINQ to XML位於System.Xml.Linq程序集,並且大多數類型都位於System.Xml.Linq命名空間。下圖展示了最常用的一些類型:
- XName表示元素和特性的名稱。
- XNamespace表示XML命名空間,通常是一個URI。
- XObject是XNode和XAttribute的共同父類:與在DOM API中不同,在LINQ to XML中特性不是節點。如果某方法返回子節點的元素,這裏面是不包含特性的
- XNode表示XML樹中的節點。它定義了各種用於操作和查詢樹的成員。
- XAttribute表示包含名/值對的特性。
- XContainer是XML樹中可以包含子內容(主要爲元素或文檔)的節點。
- XText表示文本節點,其派生類XCData表示CDATA文本節點。
- XElement表示元素。它和XAttribute是LINQ to XML中最常用的類。與在DOM API中不同,在創建一個XElement時,不需要創建包含它的文檔。
- XDocument表示文檔。可以通過Root屬性訪問其根元素,相當於XmlDocument.Document Element。
2、聲明式構造
①在DOM API中,我們通常創建一個元素,然後向其中添加內容。在LINQ to XML中我們也可以這樣做,使用繼承自XContainer的Add方法,但這並不是LINQ to XML的慣用法。不過還是有必要看一下XContainer.Add的簽名,因爲它使用了內容模型。你也許會認爲其簽名爲Add(XNode)或Add(XObject),但事實上它只是Add(object)。XElement(和XDocument)的構造函數簽名也使用了同樣的模式。在名稱之後,你可以什麼都不指定(創建空元素),也可以指定一個對象(創建包含單個子節點的元素),或對象數組(創建包含多個子節點的元素)。在創建多個子節點的時候,使用了參數數組(C#中的params關鍵字),這意味着編譯器將爲我們創建數組,我們只需要不斷列出參數即可。
②在創建內容時,不管是通過構造函數還是Add方法,都要考慮以下幾點:
- 空引用會被忽略
- XNode和XAttribute實例可以直接添加。如果它們已經有了父元素,將會被複制,但除此之外不需要任何轉換
- 字符串、數字、日期、時間等將使用標準XML格式轉換爲XText
- 如果參數實現了IEnumerable,Add方法將迭代其內容,並添加各個值,必要的時候會使用遞歸
- 其他沒有特殊處理的對象將調用ToString()將其轉換爲文本
3、查詢單個節點
XElement包含很多軸方法,可用於查詢資源,每個方法都返回適當的IEnumerable
4、合併查詢操作符
查詢的部分結果往往爲另一個序列,而在LINQ to XML中則通常爲元素的序列。如何找出各個項目中的某一個元素呢?這時我們需要對各個元素執行另一個查詢,然後合併這些結果。LINQ to Objects已經提供了SelectMany操作符來實現該功能,但對於XML來說並非是理想的。LINQ to XML提供了一些擴展方法(位於System.Xml.Linq.Extensions類中),有的針對特殊的序列類型,有的是包含強制類型參數的泛型方法,以應對C# 4之前缺乏泛型接口協變性的問題。上節中提到的軸方法大多可作爲擴展方法的形式使用。
因而下面的查詢可以進行如下轉變:
5、與LINQ和諧共處
LINQ to XML使用瞭如下三種方式與其他LINQ相適應:
- 在構造函數中消費序列。LINQ是刻意聲明式語言,LINQ to XML支持聲明式地創建XML結構。
- 在查詢方法中返回序列。這大概是數據訪問API必須遵循的最爲明顯的步驟:查詢結果應該輕而易舉地返回IEumerable
或實現了該接口的類。 - 擴展了可以對XML類型的序列所作的查詢,這樣可以讓它們看上去更像是統一的查詢API,儘管有些查詢必須用於XML。
三、用並行LINQ代替LINQ to Objects
並行LINQ的背後理念是,某個LINQ to Objects查詢需要執行很長的時間,而使用多線程利用多核優勢進行查詢則可以運行得很快,並且改動也很少。
1、在單線程中繪製曼德博羅特集
我們遍歷每一行以及每行中的每一列,計算相關像素的索引。如下:
2、ParallelEnumerable、ParallelQuery和AsParallel
並行LINQ帶來了一些新的類型,它們位於System.Linq命名空間,ParallelEnumerable是一個靜態類,與Enumerable類似。它裏面幾乎全部是擴展方法,其中大多數都擴展了ParallelQuery這個類型。該類型包含泛型和非泛型形式(ParallelQuery
如何以並行查詢開始呢?答案是調用AsParallel,它是ParallelEnumerable中的擴展方法,擴展了IEnumerable
3、調整並行查詢
你只需要使用AsOrdered擴展方法,強制對查詢排序即可。它比無序查詢要略慢,但仍明顯快於單線程版本
四、使用LINQ to Rx反轉查詢模型
當數據由消費者掌管,即當新數據可用的時候,由數據消費者進行響應,這就是所謂的“推”。有一個有趣的程序集叫做System.Interactive,它包含各種額外的LINQ to Objects方法;System.Reactive實現了各種推操作。
1、IObservable和IObserver
LINQ to Rx的數據模型與普通IEnumerable
這兩個接口屬於.NET 4(位於System命名空間),但LINQ to Rx的其餘部分則是需要單獨下載的。實際上,在.NET 4中這兩個接口是IObservable
LINQ to Rx與我們熟悉的事件十分類似。調用一個可觀察對象(observable)的Subscribe,就像是對事件使用+=來註冊處理程序一樣。Subscribe返回的可處置(disposable)值會記住傳入的觀察者(observer):處置它就像對同一個處理程序使用-=一樣。通常,觀察者將重複調用OnNext方法,並最終調用OnCompleted——這期間如果出現了某種錯誤,就用OnError代替。在序列結束或發生錯誤之後不會再調用其他方法。
2、簡單的開始
這裏我們使用Observable.Range來創建一個可觀察的範圍,而不再使用Enumerable.Range。每當一個觀察者訂閱這個範圍時,使用OnNext把數字發送給該觀察者,最後將調用OnCompleted。Range方法返回的是一個冷可觀察對象(cold observable)。它處於休眠狀態,直到某個觀察者訂閱了它,它纔會向該觀察者發送值。如果其他觀察者也訂閱了該對象,將會得到該範圍的一個副本。這與點擊按鈕這種普通的事件不太相同,對於後者,多個觀察者可以同時訂閱同一個實際的值序列——並且即便沒有任何觀察者,也會有效地產生值。這種序列稱爲熱可觀察對象(hot observable)
3、查詢可觀察對象
相關示例如下:
我們在LINQ to Objects中處理分組時常常要嵌套foreach循環,因此在LINQ to Rx中要嵌套訂閱。在進行分組操作,LINQ to Objects在返回之前把整個分組收集在一起,這意味着要對結果進行緩衝,直到序列的末尾。在某些情況下,在LINQ to Objects中所需的大量的數據緩衝操作,都可以用LINQ to Rx更高效地實現。
4、意義何在
Rx提供了一種優雅的方式來思考各種異步處理——如普通.NET事件(可以使用Observable. FromEvent將其視爲可觀察對象)、異步I/O和調用We b服務。它提供了一種有效的方式來管理複雜性和併發。
五、擴展LINQ to Objects
1、設計和實現指南
當爲LINQ進行相關查詢操作符的擴展時,應該注意以下幾點:
- 單元測試:不要忘記測試個別情況,如空序列和無效參數等;
- 檢查參數:好的方法會檢查傳入的參數。但這對LINQ操作符來說有一個問題。很多操作符都返回一個序列,而實現這種功能最簡單的方式就是迭代器塊。但你應該在調用方法的同時執行參數檢查,而不應該等到調用者決定迭代其結果的時候。如果打算使用迭代器塊,就把方法分成兩部分:在公共方法中執行參數檢查,然後調用一個私有方法進行迭代。
- 優化:IEnumerable
本身所支持的操作十分有限,但你所操作的序列的執行時類型可能具備更多的功能。 - 文檔:在文檔中指明代碼對輸入的處理和操作符的預期性能是十分重要的。
- 儘量只迭代一次:可以對IEnumerable
進行多次迭代——實際上對於同一個序列,你可以同時擁有多個活動的迭代器。但對於一個查詢操作符來說,這樣做可不是什麼好主意。 - 釋放迭代器:在大多數情況下,我們可以使用foreach語句來迭代數據源。在這種情況下,要爲迭代器使用using塊
- 自定義比較器:很多LINQ操作符都包含可以指定適當IEqualityComparer
或IComparer 的重載。通常簡單的重載只需要調用複雜的重載,傳入EqualityComparer .Default或Comparer .Default作爲比較器