1. 程式人生 > >ado.net EF學習系列----深入理解查詢延遲載入技術

ado.net EF學習系列----深入理解查詢延遲載入技術

ado.net EF是微軟的一個ORM框架,使用過EF的同學都知道EF有一個延遲載入的技術。

如果你是一個老鳥,你可能瞭解一些,如果下面的學習過程中哪些方面講解的不對,歡迎批評指教。如果一個菜鳥,那我們就一起開始今天的學習。

首先,提出以下幾個問題。

何為延遲載入呢?

我們該如何使用呢?

我們為什麼要使用延遲載入技術呢?

延遲載入技術有什麼優、缺點呢?

好,帶著上面的問題我們開始今天的學習。

1.何為延遲載入

EF的延遲載入,就是使用Lambda表示式或者Linq 從 EF實體物件中查詢資料時,EF並不是直接將資料查詢出來,而是在用到具體資料的時候才會載入到記憶體。說白了就是按需載入。

2.如何使用延遲載入技術

在這裡,我們新增一個實體資料模型,對應的是資料庫中的Flower、Indicator兩個表。具體表的定義如下所示。
下面我們就根據上面的資料模型編寫一個具體的延遲載入,我們到裡面一探究竟。 編寫程式碼,截圖如下所示。
看到我上面程式碼截圖中標註的內容了嗎? 通過上面的Lambda表示式查詢出來的資料是一個實現了IQueryable介面的類,在這裡是ObjectSet<Folwer>,實體集。 不相信是嗎?那好,我們繼續檢視原始碼。
上面的程式碼說明我們通過entity.Flower查詢出來的是一個ObjectSet<Flower>型別的資料。 而接下來的程式碼有說明,這個ObjectSet<T>型別的資料實現了IQueryable介面,按照面向介面程式設計
的原則,當然可以使用介面物件來接收查詢出來的實體集。同時也實現了IEnumable介面,也就可以使用foreach()遍歷集合。

上面的都是對程式碼進行的解釋,那麼真正的延遲載入又是體現在哪裡呢? 完整程式碼如下所示
namespace WebApplication1
{
    public partial class EFDemo : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            using (FlowersPlatformEntities entity = new FlowersPlatformEntities())
            {
                
                var item2 = entity.Flower.Where(c => c.Name == "牡丹");
                foreach (var item in item2)
                {
                    Response.Write(item.Id);
                }
            }
        }
    }
}

為了方便觀察,在上面的查詢程式碼處新增斷點,開啟sql server profiler監視工具,執行程式,開始觀察 程式在斷點處停下來
繼續單步除錯
通過監視工具我們知道,此時並沒有sql語句的查詢操作。 OK,繼續單步除錯
Ok,當我們走到item2的時候發現sql語句執行了,資料查詢出來了。  對上面的過程解釋一下entity.Flower.where()的時候sql語句並沒執行,當我們在下面需要用到上面查詢語句的結果集item2的時候sql語句真正執行了。 沒錯,這就是EF的延遲載入技術,只有在資料真正用到的時候才會去資料庫中查詢。 順便說一下,EF實現延遲載入的核心就是因為IQueryable介面的使用。如果我們把上面的程式碼修改一下,如下所示。
看到上面查詢的返回值了嗎?是一個Flower變數。如果你再新增斷點,開啟監視工具可以發現,這個時候已經沒有延遲載入了。 上面只是延遲載入技術的一種,我們接下來學習另一種延遲載入技術。 上面我們定義的兩個表Flower和Indicator表兩者之間是一對多的關係,如果我們想要查詢某個Flower下的所有Indicator資料,我們應該怎麼做呢? 如果放在以前,你可能會說,我們使用表之間的連線查詢不就行了嗎? 不錯,這確實是一種方法。不過你有沒有想過連線查詢就是笛卡爾積查詢,如果查詢Flower表下的所有對應Indicator資料,這個查詢量又是多少呢? Flower表有1萬條記錄,Indicator表也有1萬條記錄,那麼他們的笛卡爾積就是一億條資料,這樣的查詢肯定不是高效的。 解決方法就可以操作EF的另一種延遲載入技術,實現的核心就是實體類的導航屬性。 在開始之前,我們再看一下上面Flower和Indicator兩個表的導航屬性
編寫具體程式碼如下所示。
namespace WebApplication1
{
    public partial class EFDemo : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            using (FlowersPlatformEntities entity = new FlowersPlatformEntities())
            {

                var item2 = entity.Flower.Where(c => c.Name == "牡丹");
                foreach (var item in item2)
                {
                    foreach (var it in item.Indicator)
                    {
                         Response.Write(it.Index);
                    }
                }
            }
        }
    }
}
在上面的程式碼foreach迴圈出新增斷點,執行程式,開啟監視工具,觀察
執行到第一個foreach()迴圈的時候會執行一個查詢,上面已經說得很清楚了。

但是一定要注意,此時並沒有查詢出導航屬性。

執行到第二個foreach()迴圈的時候,我們看到又執行了一條查詢語句。 這個時候你可能就疑惑了,我們並沒有要求查詢啊,其實,這個查詢是根據導航屬性自動幫助我們查詢的。 這就是第二種延遲載入技術,當我們需要用到導航屬性的時候,如果導航屬性不存在記憶體中,EF會自動幫助我們把導航屬性查詢出來載入到記憶體中。 此種延遲載入技術,現在應用的非常廣泛。 在此總結一下,只要查詢結果是實現了IQueryable介面的類,查詢的結果都是延遲載入的。(不知道這句話說的準確不準確) Ok,上面介紹了為何要使用延遲載入技術,那麼我們為何要使用這種技術呢?

3.為何要使用延遲載入技術

上面的長篇大論簡要的解釋了一下EF的延遲載入機制,說了那麼多,那到底為什麼需要延遲載入呢?直接取直接用不就好了?
確實,從上面的簡單例子來看延遲載入貌似很多餘,但是通常我們操作資料庫不可能只是這麼簡單的。
就拿很常用的分頁來說,一般是先對資料進行排序,然後按照要求跳過幾行資料,在取幾行數。這就不是一個簡單的where方法可以實現的了
至少需要先呼叫order進行排序,然後skip跳過幾行資料,最後take取幾行資料。如果where/order/skip/take等等方法每次使用的時候就馬上提交sql語句到資料庫,那做一個分頁查詢至少要傳送4次請求,也就是說要和資料庫互動4次。如果使用延遲載入的話上面的where/order/skip/take方法呼叫的時候可以看做只是在拼湊條件,當條件滿足的時候(一般就是要用資料的時候,比如說FirstOrDefault方法),在將整個拼湊好的sql語句一起提交到資料庫,這樣一來和資料庫的互動次數由4降到了1。是不是很高效了?
當然還有其他的例子,在這裡就不列舉了。

4.延遲載入技術的優缺點

優點在上面講解第二類延遲載入技術和為何要使用延遲載入技術的時候已經說過了,下面我們主要來說一下EF延遲載入技術的缺點。 缺點: (1)每次使用的時候都會去查詢資料庫 
namespace WebApplication1
{
    public partial class EFDemo : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            using (FlowersPlatformEntities entity = new FlowersPlatformEntities())
            {

                var item2 = entity.Flower.Where(c => c.Name == "牡丹");
                foreach (var item in item2)
                {
                    Response.Write(item.Id);
                }
                foreach (var item in item2)
                {
                    Response.Write(item.Id);
                }
            }
        }
    }
}

上面的程式碼在程式執行的時候,你認為會執行幾次的資料庫查詢操作?一次?兩次? 我們通過監視工具來說明問題
OK,看到了嗎?其實是執行了兩次。每次需要使用到資料的時候都要執行查詢操作。 當然,這個缺點也是可以使用快取技術解決的 OK,終於寫完了。