1. 程式人生 > >.NET深入解析LINQ框架(二:LINQ優雅的前奏)

.NET深入解析LINQ框架(二:LINQ優雅的前奏)

mode 沒有 不想 log 業務 queryable 上下 dom 做了

閱讀目錄:

  • 1.LINQ框架的主要設計模型
    • 1.1.鏈式設計模式 (以流水線般的鏈接方式設計系統邏輯)
    • 1.2.鏈式查詢方法(逐步加工查詢表達式中的每一個工作點)
  • 2.LINQ框架的核心設計原理
    • 2.1.托管語言之上的語言(LINQ查詢表達式)
    • 2.2.托管語言構造的基礎(LINQ依附通用接口與查詢操作符對應的方法對接)
    • 2.3.深入IEnumerable、IEnumerable<T>、Enumerable(LINQ to Object框架的入口)
    • 2.4.深入IQueryable、IQueryable<T>、Queryable(LINQ to Provider框架的入口)
    • 2.5.LINQ針對不同數據源的查詢接口
    • 2.6.整體梳理LINQ的框架原理

1】.LINQ框架的主要設計模型

到了這裏我們似乎隱隱約約的能看見LINQ的原理,它不是空中花園,它是有基礎的。在上面的一系列新特性的支持下,微軟通過大面積的構建擴展方法使得上述特性能連貫的互相作用,形成自然的集成查詢框架。上面的這些特性都屬於語言為了LINQ而做的增強,也可以說是設計者們在不斷的探索新的比較符合現代開發體系的語言特性,也越來越多的支持函數式的編程特性,比如DLR的引入對Python、Ruby函數式腳本語言的強大支持,後面也會越來越多的支持其他的函數式腳本語言。

下面我們將主要學習對象模型的相關知識,什麽是對象模型?其實很多時候我們註重的是語言層面的學習而並沒有將重點放在對象的設計原理上,導致學習成本的不斷增加。我們應該更重要的去學習和培養設計能力(所謂設計能力體現技術層次)。對象模型簡單點講就是對象的設計模型,如何構造能滿足需要的深層對象結構。在目前.NET平臺上的主流ORM框架ADO.NET EntityFramework中的架構體系中的概念層中的設計就體現出了對象模型的作用。在ADO.NET EntityFrameWork、Linq to SQL框架中有很多值得我們探索的對象模型。

在LINQ裏面充斥著大量的擴展方法,在這些擴展方法的後背其實是隱藏著一個很大的設計秘密,那就是鏈式編程模型,下面我們將通過詳細的學習鏈式編程模式來理解LINQ為什麽能連貫的使用相同的方法而顯現的如此優雅。

  • 1.1.鏈式設計模式(以流水線般的鏈接方式設計系統邏輯)

鏈式設計模式是一直被我們忽視的一種很優美的模式,最近一次接觸它的美是在學習LINQ的時候,看到連貫的擴展方法陸續登場頓時讓我覺得這真是無可挑剔。其實在很多場合下我們也可以借鑒這種設計模式,可以很自然的處理很多比較棘手的問題。比較大膽的設計是業務碎片化後利用鏈式模式將碎片化後的業務算法進行人為的邏輯重組,如果設計的好的話,將是一道頂級盛宴。由於這篇文章是講解LINQ的內容,這裏我就不多扯它了,後面會有專門的文章來講解大膽的鏈式業務流程重組的相關知識。

在很多時候我們設計一個系統功能或者應用框架時,完全可以借助鏈式設計模式來優雅我們的開發方式,使編碼起來很順利很方便。

為了很形象的表達鏈式設計模式的使用方式,這裏我使用一個比較簡單的小例子來展示它的設計理念和使用方式。

例子說明:假設我有一個表示學生的對象類型還有一個表示學生集合的類型。學生集合類型主要就是用來容納學生實體,集合類型提供一系列的方法可以對這個集合進行連續的操作,很常用的就是篩選操作。比如篩選出所有性別是女生的學生,然後再在所有已經篩選出來的女性學生的集合當中篩選出年齡大於20周歲的學生列表,再繼續篩選來自江蘇南京地區的學生列表等等這一系列的連貫操作。這樣的處理方式我想是LINQ最為常見的,畢竟LINQ是為了查詢而生,而查詢主要就是面向集合類的數據。

對象圖:

技術分享

對象圖中可以很清楚的看出各個對象中的屬性和方法,在Student類中我們定義了幾個基本的學生屬性。而在StudentCollection中比較重要的是SelectByFemale方法和SelectByMankind方法,分別是篩選學生性別為女性和男性的方法,其他的就是SelectByAge和SelectByAddress分別是用來篩選年齡和地址的。由於具體的方法代碼比較簡單這裏就不貼出來了,目的是為了讓大家能直觀的看出鏈式設計模式的好處和靈活的地方。

示例代碼:

技術分享View Code

看起來是不是很優雅,我反正覺得很優雅很舒服。其實在我們設計StudentCollection對象內部方法的時候可能有一個地方很別扭,那就是方法的每次返回類型必須能讓下一次的方法調用順利進行,所以必須保持每次方法的調用都是同一種數據類型,也就是StudentCollection集合類型。

很多時候我們的設計思維存在著盲點,那就是每次返回後和本次沒關系,鏈式編程似乎找到了這個盲點並且很嚴肅的跟我們強調要經常性的去鍛煉這個設計盲點。我們利用思維導圖來分析一下鏈式設計的盲點在哪裏,也順便來找找我們經常忽視的設計優點。

思維導圖:

技術分享

上圖中每個方法都具有返回返回類型,但是只要保證返回的類型能是下一個方法的操作對象就行了,在設計對象方法的時候肯定是需要將大的過程拆分成一個可以組織的小過程。很多時候我們設計對象模型的時候也很難想到這些,其實也是我們不夠熟練罷了,我們要做的就是多練習多看設計類的書,其他的交給時間吧。

  • 1.2.鏈式查詢方法(逐步加工查詢表達式中的每一個工作點)

在上面的鏈式設計模式中我們大概了解到如果構建一個形成環路的對象模型,這樣就可以反復的使用對象集合來執行重復的查詢操作。其實LINQ就是使用這種方式來作為它的查詢原理的。這裏將直接點題到LINQ的核心設計原理上。LINQ的鏈式模型主要用在了查詢對象集合上,通過大面積構建擴展方法讓對象充滿可以使用的LINQ表達式所對應的查詢方法。

那麽我們如何來理解LINQ的查詢呢?大部分的同誌都知道LINQ的語法,都是"from *** in *** where *** select *** " 類似SQL這樣的語法。其實這是構建與CTS之上的一種由編輯器負責處理的新的查詢語法,它不是C#也不是VB.NET之類的托管語言。其實我們都知道C#、VB.NET之類的語法都是基於.NET平臺的IL中間語言,他們屬於源代碼的一部分,並不是程序的最終輸出項。而IL才是我們每次編譯之後的輸出項的程序代碼。LINQ的語法最終也是IL的語法,當我們編寫LINQ的查詢表達式的時候其實編輯器已經智能的幫我們翻譯成對象的方法。太多的原理在下一結介紹。

關於鏈式查詢方法也是一個對象設計問題,我們參見鏈式設計模式可以很自然的構建符合我們自己實際需求的鏈式查詢方法,這一系列的查詢方法的添加存在一個很大的問題就是無法動態的添加到要擴展的對象內部去。比如對已經發布的對象是無法進行直接修改的,所以這裏就用到了我們上面提到的擴展方法技術,通過擴展方法我們很方便的為已經發布的對象添加行為。為了具有說服力我們還是看一個小列子來加強印象。

例子說明:假設我有一套已經發布的ORM簡易型的組件,這個組件構建於.NET2.0之上,現在我需要將它擴展成鏈式的查詢方式,而不想再使用以前繁瑣的查詢方式。所以我需要單獨建立一個.NET3.0或.NET3.5的擴展作為以前程序集的一個擴展程序集,在使用的時候可以使用或者可以不使用,只有這樣我們才能使用擴展方法或者其他的新的語法特性。

技術分享View Code

ORMHelper.FindEntityList<T>
是一段根據實體現有屬性查詢對象列表的泛型方法,當然這裏是為了演示就比較簡單點。如果我需要添加其他的條件就必須為Base_Deptment類型參數
model添加值才能使用,現在我想通過鏈式設計模式擴展它成為鏈式查詢的使用方式,如:

技術分享View Code

這裏的代碼只是配合上下文理解,可能有些不太合理的地方,但是沒有什麽影響。

這樣就可以將一個原本很臃腫的功能設計成如此優雅的使用方式。對於Linq to CustomEntity 實現我後面會有專門的文章講解,這裏也就不往下扯了。例子本身是想告訴我們可以借鑒鏈式查詢實現更為人性化、優雅的組件或者框架。

2】.LINQ框架的核心設計原理

  • 2.1.托管語言之上的語言(LINQ查詢表達式)

通過上面的例子我們應該基本了解了鏈式設計模式、鏈式查詢方法的奧妙和用武之地。通過一個簡單的例子我們也認識到鏈式查詢方法在數據查詢方面具有獨特的優勢,這恰恰也是理解LINQ的好思路。

那麽鏈式查詢方法為LINQ準備了些什麽?準備了對應的方法?沒錯,鏈式設計模式為鏈式查詢做好了充足的理論基礎,然後通過大面積的構建鏈式查詢方法與LINQ查詢表達式的查詢操作符做對應自然就行成了使用LINQ查詢任何數據源的好紐帶。LINQ提供統一的查詢接口,然後通過自定義的鏈式查詢方法將用戶的操作數據形成Lambda表達式,再通過提取Lambda表達式中的相關數據結構組織成你自己想要的參數送往數據驅動程序查詢數據。

LINQ本身不屬於托管語言的範疇,它是編輯器支撐的一種方便性的語法,目的是減少我們直接使用查詢方法的麻煩。相比之下,如果我們直接使用查詢方法那麽所付出的精力和時間將會很多。

示例代碼:

技術分享View Code

有兩種方式查詢集合數據,第一種是使用鏈式查詢方式查詢數據。第二種是使用LINQ查詢表達式查詢數據。毋庸置疑肯定是LINQ方便,簡單方便更符合我們習慣的SQL查詢方式。

這樣我們就可以很輕松的得出一個篩選過後的對象。編輯器負責對LINQ進行處理而不是CLR負責對LINQ進行處理,編輯器將LINQ處理成框架所實現的基本接口集。記住,LINQ是語法糖層面的,它不是C#不是VB.NET更不是CLR的基本內核的支持。

  • 2.2.托管語言構造的基礎(LINQ依附通用接口與查詢操作符對應的方法對接)

LINQ是統一的數據查詢接口,那麽它如何做到與不同的數據源直接銜接的?在4.1小結中,我們通過一個簡單的LINQ查詢表達式很方便的查詢出了Student[]數組中的指定項,這裏面是如何工作的?下面我們就來一步一步分析LINQ如何做到統一數據查詢的。

我們現在假設沒有LINQ,看看.NET是如何一點一點構建支持LINQ的內庫的。

LINQ是在.NET3.5版本中引入的,核心程序集也就是System.Core.dll,有兩個命名空間是直接關系到LINQ的,分別是System.Linq(LINQ查詢表達式直接對應的鏈式查詢方法集)、System.Linq.Expressions(LINQ查詢表達式中的邏輯表達式樹)。在System.Linq中首要的就是Enumerable靜態類,該類是封裝了對查詢IEnumerable接口類型的靜態擴展方法。這裏需要註意的是,LINQ查詢的數據源主要分為兩類,必須支持的也是首先要支持的就是Linq to object,對於內存中的對象查詢當然是以IEnumerable對象為主,查詢是面向集合類的,在.NET裏面是使用IEnumerable作為叠代器對象的實現接口,所以在System.Linq.Enumerable靜態類中全部是封裝了對IEnumerable接口的鏈式查詢方法,這些方法都是通過擴展方法提供的,也就是在.NET3.5以下的版本中是沒有的,擴展程序集包是不會被加載的。更為關鍵的是所有的擴展方法中的邏輯表達式都是Func泛型委托,也就是直接使用委托去執行邏輯操作,在我們調用的時候是以Lambda的形式給出邏輯的條件,這些邏輯被直接編譯成可以執行的匿名方法,而不是表達式對象Expression,對於內存中的對象查詢直接調用就行了。

另外一類LINQ支持的查詢對象便是我們自定的數據源了,這類數據源的查詢鏈式方法是由System.Linq.Queryable類提供的,如果我們使用LINQ查詢表達式來查詢System.Linq.IQueryable<T>類型對象的話,編輯器會認為你是查詢自定的數據源對象,在執行的時候會調用你實現的System.Linq.IQueryableProvider<T>接口實現類。該類提供對表達式樹的解析和執行。細看System.Linq.Queryable靜態類中的所有擴展方法與System.Linq.Enumerable類中的擴展方法的區別便是所有的Func類型都被System.Linq.Expressions.Expression<T>類型包裝著,這也符合我們上篇文章所講的,對System.Linq.Expressions.Expression的解析是當成數據結構的,在需要的時候我們自己來讀取相關的邏輯結構。

不管是查詢Linq to object 還是自定的數據源,查詢的LINQ語法是不變的,這也就是統一了數據查詢接口,要變的是數據查詢提供程序,Linq to Sql、Linq to Entities都是實現了自定義的數據源查詢功能。

  • 2.3.深入IEnumerable、IEnumerable<T>、Enumerable(LINQ to Object框架的入口)

在4.2結中已經為LINQ的查詢做了支撐,那麽查詢到底區別在什麽地方?在使用IEnumerable<T>和IQueryable<T>之間的區別是什麽?如何很好的理解這兩者在LINQ的整個框架中的關系。

LINQ是統一了.NET平臺上的數據查詢接口,不管我們想查詢什麽類型的數據,也不管這個數據在網絡世界的何方,我們都可以很好的查詢到。那麽也不管我們想查詢什麽樣的數據都需要我們創建成熟的對象模型才行,如果還是直接的將數據從服務器拖下來然後還是一個DataTable或者是一個DOM樹,其實是意義不大的,我們需要的是能連續的在內存中對對象進行查詢。當我們把數據從遠程服務器中查詢到內存中後需要使用我們創建的對象模型對象化它,為Linq to object做準備。只有Linq to object可以了Linq to custom才可以完美的執行,這是個反向關系。

泛型的IEnumerable<T>接口繼承自IEnumerable接口,該接口表示可叠代的數據集合。Linq to object 也就是查詢IEnumerable<T>集合。Enumerable靜態類中的所有靜態方法都是對應著操作IEnumerable<T>集合類型的LINQ查詢表達式的,當每次查詢時都是直接的調用Enumerable裏面的靜態方法。對於Linq to object 其實沒有太多好講的了,要做的就是熟悉LINQ的查詢表達式語法。

  • 2.4.深入IQueryable、IQueryable<T>、Queryable(LINQ to Provider框架的入口)

IQueryable接口是提供給我們來實現自定義數據源用的,為了支持強類型的數據源集合我們直接使用IQueryable<T>接口,當我們使用LINQ來查詢IQueryable<T>接口時查詢表達式會被直接編譯成對應的Queryable靜態類中的對應的靜態擴展方法。邏輯條件這個時候是被當成查詢表達式處理的,而不像IEnumerable<T>接口直接是委托。當然,要想自己實現LINQ查詢數據源還是比較難的,我們需要自行的去處理表達式目錄樹才行,後面的文章將會詳細的講解到。

  • 2.5.LINQ針對不同數據源的查詢接口

到目前為止我想我們都對LINQ的統一數據源查詢有了大致的了解,不管我們的數據源是什麽,RDMS、DOM等等,我們都有相對應的查詢方法,辛苦的只是封裝的人而已,做後臺開發的朋友可能需要借助這些專門的查詢語言來查詢數據,給前端程序員方便的使用LINQ查詢數據源。

組件開發人員首要的任務就是創建對象模型,該對象模型應該是真正數據源的抽象模型,以便於該對象可能成功的被放入到IQueryable<T>中進行查詢。

  • 2.6.整體梳理LINQ的框架原理

通過上面的詳細的介紹我們對LINQ的框架基本掌握了,如果只是使用它其實是很簡單的,只要熟悉LINQ的查詢語法就行了,但是我想我們每個程序員都有很強的好奇心,想搞懂框架的設計原理,這也是我們必須具備的戰鬥力。

LINQ查詢表達式最後是調用的鏈式查詢方法,這些方法都是在靜態類中定義好的,IEnumerable<T>類型是直接的使用匿名方法調用執行,而IQueryable<T>是使用人工解析的方式進行的,也就是自定義數據源。Linq to xml、Linq to sql、Linq to Entities等等還有一些輕量級的查詢庫都是很優秀的擴展數據源例子,很值得我們去挖掘學習。

.NET深入解析LINQ框架(二:LINQ優雅的前奏)