1. 程式人生 > >.NET深入解析LINQ框架(六:LINQ執行表達式)

.NET深入解析LINQ框架(六:LINQ執行表達式)

所有 closed 對象 系列文章 判斷代碼 vid 擴展 多條件 文件

閱讀目錄:

  • 1.LINQ執行表達式

在看本篇文章之前我假設您已經具備我之前分析的一些原理知識,因為這章所要講的內容是建立在之前的一系列知識點之上的,為了保證您的閱讀順利建議您先閱讀本人的LINQ系列文章的前幾篇或者您已經具備比較深入的LINQ原理知識體系,防止耽誤您的寶貴時間。

到目前為止我們對LINQ的執行原理已經很清楚了,從它的前期構想到它真正為我們所用都有足夠的證據,但是似乎問題並沒有我們想的那麽簡單,問題總是在我們使用中頻頻出現尤其是新技術的使用,當然有問題才能有進步。

一:LINQ執行表達式

在研究LINQ的過程中,參考了很多技術文章還有技術書籍,毫無疑問的是Linq to Provider的調用入口都是將Lambda表達式解析成Expression<T>表達式對象,跟Linq to Object不同,Linq to Object是將Lambda直接解析成泛型Func類型的委托,但是我們很多人包括我自己都忽視了一個很大的細節,就是Provider在內部將對Expression<T>進行執行,並非我們所理解的那樣將表達式Expression<T>對象完全解析成等價的SQL,也就是說Expression<T>並不是我們說看到的那樣單純,它具有雙重上下文邏輯在裏面。

我們都是直接使用LINQ作為查詢接口,VS在最後編譯的時候負責對LINQ的語法進行解析並且翻譯成對應的擴展方法調用。我們忽視一個重要的環節,就是VS對LINQ進行解析翻譯的時候是會執行LINQ表達式的,這點非常重要。之前我一直以為VS只負責將LINQ的表達式翻譯成等價的擴展方法調用,後來發現VS為了滿足我們在前期無法確定對象條件的情況下進行Where字句的拼接,允許我們在編寫LINQ語句的時候帶有邏輯判斷表達式在裏面,這個功能對我們進行多條件組合查詢時相當方便,不需要在進行IF、ELSE的多個判斷,只需要順其自然的在LINQ中的第一個表達式中進行判斷就行了。追求優雅代碼的同誌很不希望在一個既有LINQ查詢又帶有鏈式查詢的方法中用兩種查詢方式,如果LINQ能滿足大部分的查詢功能那最完美;

為了說明LINQ在編譯時會被VS執行,我們用LINQPad工具看一下便知;

LINQ查詢表達式:from truck in TB_CX_TRUCKs where 1==1 select truck

LINQ等價的鏈式方法: TB_CX_TRUCKs.Where (truck => True)

圖1:

技術分享

如果沒有執行按道理是直接解析成Lambda的格式(truck)=>1==1才對,然後讓LINQ to Provider提供程序負責處理才對,也許覺得沒有實質的意思反正是恒等的表達式所以解析成這樣。我們在換一種寫法看看;

LINQ查詢表達式:from truck in TB_CX_TRUCKs where string.IsNullOrEmpty("1111") select truck

LINQ等價的鏈式方法:TB_CX_TRUCKs.Where (truck => String.IsNullOrEmpty ("1111"))

圖2:

技術分享

由此可以得出一個結論,LINQ語句是會被執行和解析的兩個動作,在還沒有進入到提供程序時已經可以看出LINQ是可以附帶一些執行邏輯在裏面的,而不是最終的SQL執行邏輯。

表達式的處理可以分為常量表達式和動態變量表達式,常量表達式在VS編譯的時候就可以直接計算表達式是否是true、false。而動態變量表達式則需要在後期進行表達式解析的時候計算的,換句話說Linq to Provider中的Provider提供程序是具有高智商的表達式執行器,不僅僅是對表達式等價解析中間還夾雜著對表達式解析的自定義邏輯代碼。

打個比方,我們都有過拼接查詢條件的經歷,界面上有N個查詢條件字段,需要根據用戶是否填寫了哪個字段進行動態的拼接進LINQ語句中去。一般我們都會進行if的判斷才行,因為我們都覺得Where後面的條件表達式是直接被解析成對應邏輯的SQL語句,所以只要拼接進去的都是被解析成SQL的Where子句。由於LINQ是無法拆分開來進行組裝的,必須一次寫完才能通過編譯。所以我們都在使用著查詢擴展方法進行數據查詢,這樣的困境使我們無法看到LINQ的優雅,反而一直用不到。

通過觀察LINQPad工具解析的SQL語句,發現LINQ查詢表達式在提供程序內部將被執行、解析兩個過程,跟VS的過程是一樣的,能執行先執行,然後解析,解析是建立在前期執行過後的基礎上的。我們還是來看一個比較簡單的LINQ解析後的SQL和鏈式方法;

LINQ查詢表達式:from truck in TB_CX_TRUCKs where 1==1 ||truck.LICENSE_NUMBER.Length<10 select truck

LINQ等價的鏈式方法:TB_CX_TRUCKs.Where (truck => (True || (truck.LICENSE_NUMBER.Length < 10)))

圖3:

技術分享

對照鏈式方法,很明顯VS先對1==1表達式進行了執行並返回true作為後面整個表達式的一部分拼接進Where鏈式方法,所以先執行再解析兩個過程。然後我們對最後的SQL進行分析,沒有看見任何Where語句,為什麽呢?是因為提供程序在內部對表達式進行了執行並分析了我們想要的輸出結果,也不知道這樣的效果是不是為了滿足我們多條件拼接的問題。

由於Where方法裏面的Lambda表達如果被執行的話,那麽將不會執行(truck.LICENSE-NUMBER.Length<10),所以這點為我們的多條件拼接提供了接口。

我們看一下多條件組合查詢示例:

技術分享

將界面上的查詢實體傳入到數據訪問層之後:

技術分享View Code

這樣的查詢LINQ確實很優美,比起之前的IFELSE判斷也省事很多。

技術分享View Code

如果有很多個查詢條件,那麽我們將要寫很多這樣的判斷代碼,即不方便也不美觀。

技術分享

(註:查看大圖)

多條件之間的OR查詢

盡管很多場合下我們都是使用Linq中的where關鍵字來拼接查詢條件,但是有一種需求Linq查詢確實滿足不了我們,那就是多條件之間是OR的關系。因為只要我們用Linq或者鏈式方法出來的寫出來的SQL語句中的where條件後面將都是and關系,這個時候我們只能用鏈式方法來進行拆分才行。

技術分享View Code

這裏有個重點就是老外(估計是比較厲害的前輩,在此謝謝了!)寫的一個*.cs文件,裏面是Expression<T>表達式文件的擴展方法,主要就是用來進行多條件Or、And之間組合查詢用的。

所有說如果多條件組合查詢之間是and關系可以直接使用Linq,如果是or或者是or與and一起,那麽可以使用上面這種鏈式查詢方法。

總結:其實說了那麽多目的只有一個,LINQ的解析過程並非只有一個“提供程序翻譯成SQL”的過程,而是包括了兩個階段,四個過程的處理,LINQ的寫法很多種,原理應該是差不多的,只要我們在寫LINQ的時候綜合考慮這幾個處理過程,應該對我們應對復雜的查詢很有幫助。

.NET深入解析LINQ框架(六:LINQ執行表達式)