.NET深入解析LINQ框架(五:IQueryable、IQueryProvider接口詳解)


閱讀目錄:

  • 1.環路執行對象模型、碎片化執行模型(假遞歸式調用)
  • 2.N層對象執行模型(縱橫向對比鏈式擴展方法)
  • 3.LINQ查詢表達式和鏈式查詢方法其實都是空殼子
  • 4.詳細的對象結構圖(對象的執行原理)
  • 5.IQueryable<T>與IQueryProvider一對一的關系能否改成一對多的關系
  • 6.完整的自定義查詢

1】. 環路執行對象模型、碎片化執行模型(假遞歸式調用)

這個主題扯的可能有點遠,但是它關系着整個LINQ框架的設計結構,至少在我還沒有搞懂LINQ的本意之前,在我腦海里一直頻頻出現這樣的模型,這些模型幫助我理解LINQ的設計原理。其實在最早接觸環路模型和碎片化模型是在前兩個月,那個時候有幸接觸企業應用架構方面的知識,里面就有很多業務碎片化的設計技巧。其實理解這些所謂的設計模型后將大大開闊我們的眼界,畢竟研究框架是要研究它的設計原理,它的存在必然是為了解決某一類問題,問題驅動它的設計模型。所以我們在研究這樣的模型的時候其實已經在不知不覺的理解問題的本質。

到底環路執行模型是什么?它與碎片化之間是什么關系?假遞歸式調用又是什么奧秘?這些種種問題我們必須都要把它解決了才能暢通無阻的去研究下面的東西。其實環路執行、碎片化、假遞歸式都是問題的不同角度的稱呼,就好比我們經常會用依賴倒置、控制反轉、依賴注入這些詞匯去形容設計原則、設計方法一樣,他們都是為了解決某種問題而存在,通過巧妙的設計來達到很完美的效果。這里其實也是如此,我們來一一的分解說明。

想必沒有人不了解遞歸的原理,對遞歸的使用也是很常見的,通過遞歸算法我們可以解決一下無法解決的大問題,通過將大問題分解成多個同樣數據結構的小問題然后讓計算機重復的去計算就行了。最為常見的就是遍歷樹形結構的對象,如:XML,它的每個節點都是一樣的對象,所以遞歸調用的方法也是同一個方法,只不過不斷的調用將產生多個調用棧,最后在按照調用順序的反向出棧就得出一個完整的數據結構。

那么在LINQ中來說,我們無法通過一個方法多次調用來產生我們想要的表達式樹,一個Where查詢表達式擴展方法可能不僅僅是被LINQ查詢表達式所使用,還有可能被ORM的入口方法所使用,比如Update更新的時候就需要Where給出更新的條件,Delete也同樣如此。(當然我們這里討論是LINQ背后的設計原理不單單針對LINQ的技術,而是某一類問題的通用設計模式。)那么我們如何構造出一個類似遞歸但不是遞歸的算法結構,方法1可能被方法2調用,方法2也可能被方法1所調用,這樣的方法很多,N個方法分別表達不同的語義,具體的構造看使用者的需求,所以這里就出現碎片化的概念了,只有碎片化后才能最大程度的重組,既然能重組了就形成了環路的執行模型。非常完美,看似簡單卻深不見底的模型我們只了解到冰山一角而已,在企業架構、領域驅動設計方向都已經有着很多成功的案例,要不然也不會被稱為設計模式了更為強大的稱呼是企業應用架構模式才對。用文字的方式講解計算機程序問題似乎有點吃力,用代碼+圖形分析的方式來講解最適合我們程序員的思維習慣了。下面我用一個簡單的例子再附上一些簡單的圖示來跟大家分享一下這幾個模式語言的關系。

大家肯定都知道每逢過年過節都會有很多禮品擺放在超市里商場里買,但是我們都知道一個潛規則,就是這些商品的包裝花費了很多功夫,一層套一層,其實里面的東西可能很不起眼,這也是一種營銷手段吧。我們暫且不管這里面是什么東西,我們現在要設計一個能夠任意進行N層次包裝的模型出來,一件商品左一層右一層的反復包裝,包裝幾次我們不管,我們提供能進行N層包裝的方法出來就行了。

View Code
/// <summary> 
   /// 商品抽象類 
   /// </summary> 
   public abstract class Merchandise 
   { 
       /// <summary> 
       /// 商品名 
       /// </summary> 
       public string MerchandiseName { get; protected set; } 
       /// <summary> 
       /// 單價 
       /// </summary> 
       public int UnitPrice { get; protected set; } 
       /// <summary> 
       /// 數量 
       /// </summary> 
       public int Number { get; protected set; } 

   } 
   /// <summary> 
   /// 蘋果 
   /// </summary> 
   public class Apple : Merchandise 
   { 
       public Apple() { } 
       private void Init() 
       { 
           base.MerchandiseName = "進口泰國蘋果"; 
           base.UnitPrice = 8;//8塊錢一個 
           base.Number = 3;//3個一籃裝 
       } 
   } 
   /// <summary> 
   /// 香蕉 
   /// </summary> 
   public class Banana : Merchandise 
   { 
       public Banana() { } 
       private void Init() 
       { 
           base.MerchandiseName = "雲南綠色香蕉"; 
           base.UnitPrice = 3;//3塊錢一根 
           base.Number = 10;//10根一籃裝 
       } 
   } 

   /// <summary> 
   /// 商品包裝類 
   /// </summary> 
   public static class MerchandisePack 
   { 
       /// <summary> 
       /// 貼一個商標 
       /// </summary> 
       public static Merchandise PackLogo(this Merchandise mer) 
       { 
           return mer; 
       } 
       /// <summary> 
       /// 包裝一個紅色的盒子 
       /// </summary> 
       public static Merchandise PackRedBox(this Merchandise mer) 
       { 
           return mer; 
       } 
       /// <summary> 
       /// 包裝一個藍色的盒子 
       /// </summary> 
       public static Merchandise PackBlueBox(this Merchandise mer) 
       { 
           return mer; 
       }

這么簡單的代碼我就不一一解釋了,這里只是為了演示而用沒有添加沒用的代碼,免得耽誤大家時間。

我們看關鍵部分的代碼:

View Code
Apple apple = new Apple(); 
           apple.PackLogo().PackBlueBox().PackRedBox().PackLogo(); 

           Banana banana = new Banana(); 
           banana.PackRedBox().PackBlueBox().PackLogo();

這段代碼我想完全可以說服我們,碎片化后體現出來的擴展性是多么的靈活。apple在一開始的時候都是需要在上面貼一個小logo的,我們吃蘋果的都知道的。由於現在是特殊節日,我們為了給接收禮品的人一點小小的Surprise,所以商家要求商品都統一的套了幾層包裝,有了這個模型確實方便了很多。

完全實現了獨立擴展的能力,不會將包裝的方法牽扯到領域對象中去,很干凈明了。

(注:查看大圖)

通過這種架構模式進行系統開發后,我們一目了然的就知道系統的每一個邏輯的過程,更像一種工作流的執行方式,先是什么然后是什么。不像在IF ELSE里面充斥亂七八糟的邏輯,很難維護。不愧為企業應用架構模式的一種啊。當然LINQ中只有Linq to Object才會出現重復的使用一到兩個方法來完成功能,像Linq to Entity 幾乎不會出現這種情況。一般針對查詢的化只是關鍵字存在於不同的查詢上下文中,這里是為了講解它的背后設計原理。

2】.N層對象執行模型(縱橫向對比鏈式擴展方法)

其實本來不打算加這一小節的,但是考慮到肯定有部分朋友不是很理解多個對象如何協調的去解決某類問題的。IQueryable<T>接口貌似是一個對象,但是它們都屬於一個完整的IQueryable<T>中的一員。N層對象體現在哪里?從一開始的IQueryable被擴展方法所處理就已經開始第一層的對象處理,重復性的環路假遞歸似的調用就形成N層對象模型。在LINQ中的查詢表達式與查詢方法其實是一一對應的,擴展方法是縱向的概念,而LINQ查詢表達式是橫向的,其實兩者屬於對應關系。詳情可以參見本人的“NET深入解析LINQ框架(四:IQueryable、IQueryProvider接口詳解)”一文;

3】.LINQ查詢表達式和鏈式查詢方法其實都是空殼子

LINQ的真正意圖是在方便我們構建表達式樹(ExpressionTree),手動構建過表達式樹的朋友都會覺得很麻煩(對動態表達式有興趣的可以參見本人的“.NET深入解析LINQ框架(三:LINQ優雅的前奏)”一文),所以我們可以通過直接編寫Lambda的方式調用擴展方法,由於LambdaExpression也是來自於Expression,而Expression<T>又是來自LambdaExpression,別被這些搞暈,Expression<T>其實是簡化我們使用表達式的方式。對於Expression<T>的編譯方式是編輯器幫我們生成好的,在運行時我們只管獲取ExpressionTree就行了。LINQ的查詢表達式是通過擴展方法橫向支撐的,你不用LINQ也一樣可以直接使用各個擴展方法,但是那樣會很麻煩,開發速度會很慢,最大的問題不在於此,而是沒有統一的查詢方式來查詢所有的數據源。LINQ的本意和初衷是提供統一的方式來供我們查詢所有的數據源,這點很重要。

4】詳細的對象結構圖(對象的執行原理)

這篇文章的重點就在這一節了,上面說了那么多的話如果朋友能懂還好不懂的話還真是頭疼。這一節我將給出LINQ的核心的執行圖,我們將很清楚的看見LINQ的最終表達式樹的對象結構,它是如何構建一棵完整的樹形結構的,IQueryable接口是怎么和IQueryProvider接口配合的,為什么IQueryable具備延遲加載的能力。文章的最后將給出一個完整的Linq to Provider的小例子,喜歡擴展LINQ的朋友肯定會喜歡的。

(注:查看大圖)

上圖看上去可能會很亂,但是靜下心來看還是能理解的,按照DbQueryable生命周期來看,之上而下,如果有問題可以回復評論進一步探討。

5】.IQueryable<T>與IQueryProvider一對一的關系能否改成一對多的關系

IQueryable對象都有一個配套的IQueryProvider對象,在頻繁的創建IQueryable的時候都會重新創建IQueryProvider對象,畢竟是一種浪費。我們可以適當的修改實現IQueryable類的內部結構,讓每次創建IQueryable之后能重用上一次的IQueryProvider的對象,畢竟IQueryProvider對象沒有任何的中間狀態的數據,只是CreateQuery、 Execute兩個方法。這里只是本人的一點小小的改進想法,不一定需要考慮這些。

6】.完整的自定義查詢

LINQ的分析接近尾聲了,這篇文章將是深入分析LINQ的最后一篇。既然已經結束了LINQ的全部分析,那么我們動手寫一個小例子,作為想擴展LINQ的小雛形。該例子不會涉及到對表達式樹的分析,畢竟表達式樹的分析並非易事,后面會有專本的文章來剖析表達式樹的完整結構,這里只是全盤的IQueryable和IQueryProvider的實現。

ORM一直是我們比較喜歡去寫的框架,這里就使用自定義的IQueryable來查詢相應的對象實體。首先我們需要繼承IQueryable<T>接口來讓LINQ能查詢我們自己的數據上下文。

View Code
public class DbQuery<T> : IQueryable<T>, IDisposable 
    { 
        public DbQuery() 
        { 
            Provider = new DbQueryProvider(); 
            Expression = Expression.Constant(this);//最后一個表達式將是第一IQueryable對象的引用。 
        } 
        public DbQuery(Expression expression) 
        { 
            Provider = new DbQueryProvider(); 
            Expression = expression; 
        } 

        public Type ElementType 
        { 
            get { return typeof(T); } 
            private set { ElementType = value; } 
        } 

        public System.Linq.Expressions.Expression Expression 
        { 
            get; 
            private set; 
        } 

        public IQueryProvider Provider 
        { 
            get; 
            private set; 
        } 

        public IEnumerator<T> GetEnumerator() 
        { 
            return (Provider.Execute<T>(Expression) as IEnumerable<T>).GetEnumerator(); 
        } 

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
        { 
            return (Provider.Execute(Expression) as IEnumerable).GetEnumerator(); 
        } 

        public void Dispose() 
        { 

        } 
    }

下面需要實現提供程序:

View Code
 public class DbQueryProvider : IQueryProvider
    {
        public IQueryable<TElement> CreateQuery<TElement>(System.Linq.Expressions.Expression expression)
        {
            return new DbQuery<TElement>();
        }

        public IQueryable CreateQuery(System.Linq.Expressions.Expression expression)
        {
            //這里牽扯到對表達式樹的分析,就不多說了。
            throw new NotImplementedException();
        }

        public TResult Execute<TResult>(System.Linq.Expressions.Expression expression)
        {
            return default(TResult);//強類型數據集
        }

        public object Execute(System.Linq.Expressions.Expression expression)
        {
            return new List<object>();//弱類型數據集
        }
    }

我們看看如何使用;

View Code
using (DbQuery<Order> dbquery = new DbQuery<Order>()) 
           { 
               var OrderList = from order in dbquery where order.OrderName == "111" select order; 
               OrderList.Provider.Execute<List<Order>>(OrderList.Expression);//立即執行 
               foreach (var o in OrderList) 
               { 
                   //延遲執行 
               } 
           }

喜歡編寫ORM框架的朋友完全值得花點時間搞一套自己的LINQ TO ORM,這里只是讓你高興一下。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM