天天看點

EF使用延遲加載的本質原因

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/qq1010885678/article/details/38238265

EF(Entity Framework)是微軟的一個ORM架構

使用過EF的同學都知道它有一個延遲加載的功能

那麼這個延遲加載的功能到底是什麼?

為什麼需要延遲加載?

使用延遲加載的優點和缺點又各是什麼?

可以通過一個簡單的小例子來闡述EF的這些問題

首先使用到了兩個很簡單的資料表

關系圖如下:

T_Product的Uid關聯到T_Users的Id,形成一個外鍵關系

是不是真的很簡單= =

然後在測試項目中根據資料庫添加EF資料模型

準備工作已經做好了,現在進入主題

首先需要搞明白的是:什麼是延遲加載?

延遲加載又可以了解成 按需加載

根據字面的意思很容易了解:按照所需的資料 加載資料

上例子:

很簡單的代碼,相信大家都看得懂

接下來設定斷點對代碼的執行步驟進行分解

首先我在如圖所示的位置設定了一個斷點

打開sql server profiler監聽接收到的sql語句

運作程式并命中斷點

此時程式停留在到資料庫取資料(姑且這麼認為)的這行代碼上

由于代碼還未執行是以資料庫不可能接收到任何的sql語句

按下F11逐語句執行

可以看到,程式跳轉,資料庫接收到sql語句

正是var user = dbEntities.T_Users.Where(u => u.Id == 2).FirstOrDefault();這行代碼發送了sql語句到資料庫取的資料

那麼所謂的延遲加載展現在哪裡呢?

其實這行代碼是變相的即使加載,看起來像是調用where方法之後馬上到資料庫取出了資料,但是别忽略了後面的FirstOrDefault()

在EF中,調用where方法隻是先儲存了sql語句,但是并沒有送出到資料庫

但是當調用了FirstOrDefault()方法的時候就表示程式要用資料了,這時sql語句才會被送出到資料庫中

這就是延遲加載,到要使用資料的時候才取出資料

口說無憑,下面繼續用代碼來證明這個說法

将代碼拆分如下,還是在調用where方法的地方設定斷點并運作

F11下一步

這時可以看到,代碼已經執行了where方法,并停在了.FirstOrDefault方法,但是此時資料庫并沒有接收到任何sql語句

為了友善看清楚,在Console.ReadKey()也設定了一個斷點,F5代碼停留在Console.ReadKey()

現在已經很明顯了,隻有程式中要使用資料的時候(調用了FirstOrDefault),sql語句才會被發送發資料庫

這就是EF的延遲加載

但是List集合本身就有一個where方法

難道這個where方法要是延遲加載的嗎,但是這并不是到資料庫取資料的呀

在上圖的智能提示中可以隐約的看到,該where方法的第一個參數是一個IEnumerable的linq表達式

我們可以在var user = dbEntities.T_Users.Where(u => u.Id == 2)這行代碼上對where方法按F12轉到定義看一看

注意看一下這個where方法的傳回值類型,是IQueryable而不是IEnumerable

這說明了兩個where方法雖然名字一樣,甚至連參數都差不多但是一個傳回的是IEnumerable另外一個則是IQueryable

這就從本質上差別開了這兩個where方法

同時注意一下IQueryable的where方法第一個參數的this關鍵字,這說明了什麼?

說明了where并不是在IQueryable接口内部的,而是在外部類通過擴充方法的方式加上去的(這個類就是Queryable,該類中為IQueryable接口提供了n多類似where的擴充方法)

是以可以說,EF能實作延遲加載就是因為有Queryable類的存在

但是從這行代碼中我們又可以看到

var user = dbEntities.T_Users.Where(u => u.Id == 2);

where方法是從dbEntities這個EF上下文對象的T_Users屬性中點出來的

where方法不是在Queryable類中的嗎(或者說是IQueryable接口的)

怎麼就可以從EF的上下文屬性中調用呢?

那麼現在我們就找到EF上下文,看看T_Users屬性到底是怎麼回事

可以很清楚的看到T_Users是一個DbSet<T_Users>類型的泛型屬性

F12轉到定義看一下這個DbSet是什麼東西

是不是能夠在DbSet的基類(接口中)找到IQueryable!

這就解釋了為什麼能夠在EF上下文的T_Users屬性中點出where方法并使用

因為T_Users的類型繼承了IQueryable接口,自然能夠使用IQueryable接口中的方法啦!

上面的長篇大論簡要的解釋了一下EF的延遲加載機制

說了那麼多,那到底為什麼需要延遲加載呢?

直接取直接用不就好了?

确實從這個簡單的例子來看延遲加載貌似很多餘

但是通常我們操作資料庫不可能隻是這麼簡單的

就拿很常用的分頁來說

一般是先對資料進行排序

然後按照要求跳過幾行資料

在取幾行資料

這就不是一個簡單的where方法可以實作的了

至少需要先調用order進行排序,然後skip跳過幾行資料,最後take取幾行資料

如果where/order/skip/take等等方法每次使用的時候就馬上送出sql語句到資料庫

那做一個分頁查詢至少要發送4次請求

也就是說要和資料庫互動4次

如果使用延遲加載的話

上面的where/order/skip/take方法調用的時候可以看做隻是在拼湊條件

當條件滿足的時候(一般就是要用資料的時候,比如說FirstOrDefault方法)

在将整個拼湊好的sql語句一起送出到資料庫

這樣一來和資料庫的互動次數由4降到了1

這就是使用延遲加載的本質原因之一:拼湊條件一起送出,降低資料庫互動次數,提高資料庫的吞吐量

這也是延遲加載的優點

在本例中,T_Products中有一個外鍵Uid關聯到T_Users

上代碼:

var product = dbEntities.T_Products.Where(p => p.Uid == 2).FirstOrDefault();

Console.WriteLine(product.T_Users.UserName);

表内容如下

可以看到Uid為2有兩行

取出Uid為2的T_Products集合然後獲得第一行的實體

并将其關聯的外鍵屬性T_User的UserName輸出

繼續在每行代碼設定斷點并運作

程式停留在上圖的位置,F5執行,命中下一個斷點

可以看到,因為調用了FirstOrDefault方法

是以想資料庫發送了sql語句

但是注意觀察左邊的sql語句

并沒有将外鍵列一起查詢出來

繼續F5運作

還是觀察左邊的sql語句

因為執行了Console.WriteLine(product.T_Users.UserName);這行代碼

需要用到外鍵列

此時EF才向資料庫送出查詢外鍵列的sql語句

這就是延遲加載的本質原因之二:針對外鍵屬性,EF隻有在這個外鍵屬性用到的時候才會去查詢

那麼通過上面的介紹,EF延遲加載的優點已經很明顯了

那麼缺點是什麼呢?

比如說現在的需求是

取出T_Products

var products = dbEntities.T_Products.Where(p => true);
            foreach (var p in products)
            {
                Console.WriteLine(p.T_Users.UserName);
            }

            Console.ReadKey();           

表中的所有實體

周遊輸出每一個實體的外鍵T_Users并輸出其UserName

我們知道

在執行完第一行代碼的時候并沒有和資料庫進行互動

但是此時問題來了

foreach每次一次循環都要用一次products集合中實體的外鍵

這就造成了每循環一次就到資料庫中取一次資料

這就是EF延遲加載的缺點:每次用到外鍵列都會去查詢一次資料庫

其實這裡EF内部已經做了一些列的小優化,對于相同的實體隻取一次,比如說T_Products表中Uid為2的有兩行,Uid為3的有兩行,本來需要查詢4次,經過優化之後隻需要查詢兩次

但是當資料量十分巨大的時候,這些小優化起到的作用微乎其微

那麼要怎麼避免這個缺陷呢?

這時可以通過連接配接查詢先将外鍵列一起查詢出來在循環使用

代碼如下:

var products = dbEntities.T_Products.Include("T_Users");//通過Include告訴EF連接配接查詢哪個外鍵列,特别舒服的事情是,Include可以一直點下去~!
            foreach (var p in products)
            {
                Console.WriteLine(p.T_Users.UserName);
            }

            Console.ReadKey();           

點選運作可以看到下圖的效果

注意左邊的sql語句

生成了inner join的sql語句

隻進行了一次資料庫互動