EF 延遲加載和預(yù)先加載
最近悟出來一個道理,在這兒分享給大家:學(xué)歷代表你的過去,能力代表你的現(xiàn)在,學(xué)習(xí)代表你的將來。
十年河?xùn)|十年河西,莫欺少年窮
學(xué)無止境,精益求精
本節(jié)探討延遲加載和預(yù)先加載
Entity Framework作為一個優(yōu)秀的ORM框架,它使得操作數(shù)據(jù)庫就像操作內(nèi)存中的數(shù)據(jù)一樣,但是這種抽象是有性能代價的,故魚和熊掌不能兼得。但是,通過對EF的學(xué)習(xí),可以避免不必要的性能損失。本篇只介紹關(guān)聯(lián)實體的加載的相關(guān)知識,這在我之前的文章中都有介紹。
我們已經(jīng)了解到EF的關(guān)聯(lián)實體加載有三種方式:Lazy Loading,Eager Loading,Explicit Loading,其中Lazy Loading和Explicit Loading都是延遲加載。Eager Loading是預(yù)先加載
延遲加載(Lazy Loading)。當實體第一次被讀取時,相關(guān)數(shù)據(jù)不會被獲取。但是,當你第一次嘗試存取導(dǎo)航屬性時,該導(dǎo)航屬性所需的數(shù)據(jù)會自動加載。結(jié)果會使用多個查詢發(fā)送到數(shù)據(jù)庫——一次是讀取實體本身,然后是每個相關(guān)的實體。DbContext類默認是使用延遲加載的。
使用延遲加載必須滿足以下兩個條件
1、類是由Public修飾,不能是封閉類,也就是說,不能帶有Sealded修飾符
2、導(dǎo)航屬性標記為Virtual。
如下 Score 模型滿足了延遲加載的兩個條件
public class Score { [Key] public int Id { get; set; } public int StudentScore { get; set; }//學(xué)生分數(shù) public int StudentID { get; set; }//學(xué)生ID public int CourseID { get; set; }//課程ID public virtual Student Student { get; set; }//virtual關(guān)鍵字修飾,用于延遲加載 提高性能 只有顯式調(diào)用時 才會加載 并可以代表一個Student對象 也就是 屬性==對象 public virtual Course Course { get; set; }//virtual關(guān)鍵字修飾,用于延遲加載 提高性能 只有顯式調(diào)用時 才會加載 并可以代表一個Course對象 也就是 屬性==對象 }
如果您不想使用延遲加載,您可以關(guān)閉Lazy Loading,將LazyLoadingEnabled設(shè)為false,如果導(dǎo)航屬性沒有標記為virtual,Lazy Loading也是不起作用的。
public StudentContext() : base("StudentContext")//指定連接字符串 { this.Configuration.LazyLoadingEnabled = false; //關(guān)閉延遲加載 }
(一)延遲加載
延遲加載就是數(shù)據(jù)不會一次性查出來,而是一條一條的查詢,這樣就會多次請求數(shù)據(jù)庫進行查詢,增加了數(shù)據(jù)庫的負擔。如果您的數(shù)據(jù)量打太大,建議使用預(yù)先加載,如果兩張表數(shù)據(jù)量很大,建議使用延遲加載。
public ActionResult linqTOsql() { using (var db = new StudentContext()) { var Elist = db.Scores.Where(t => t.StudentScore > 80); foreach (Score item in Elist) { //todo 延遲加載 執(zhí)行多次查詢 } } return View(); }
(二)預(yù)先加載<Eager Loading>使用Include方法關(guān)聯(lián)預(yù)先加載的實體。
注:需引入:using System.Data.Entity;命名空間
預(yù)先加載就是從數(shù)據(jù)庫中一次性查詢所有數(shù)據(jù),存放到內(nèi)存中。如下方法采用預(yù)先加載
public ActionResult linqTOsql() { using (var db = new StudentContext()) { var Elist = from o in db.Scores.Include(t=>t.Student).Where(t=>t.StudentScore>80) select o; ViewBag.Elist = Elist; } return View(); }
上述代碼就是將Score表和Student表左連接,然后查詢學(xué)生成績在80分以上的信息。Include()是立即查詢的,像ToList()一樣,不會稍后延遲優(yōu)化后再加載。
如果兩張表記錄很大(字段多,上百萬條記錄),采用Include()關(guān)聯(lián)兩張表效率會很低,因為:它除了要做笛卡爾積,還要把數(shù)據(jù)一次性查詢出來。因此:在字段多,記錄多的情況下,建議使用延遲加載。
在此需要說明的是:EF中有兩種表關(guān)聯(lián)的方法,一種是Join()方法,一種是Include()方法
Join()方法使用說明:兩表不必含有外鍵關(guān)系,需要代碼手動指定連接外鍵相等(具有可拓展性,除了值相等,還能指定是>,<以及其他對兩表的相應(yīng)鍵的關(guān)系),以及結(jié)果字段。
Include()方法說明:兩表必須含有外鍵關(guān)系,只需要指定鍵名對應(yīng)的類屬性名即可,不需指定結(jié)果字段(即全部映射)。默認搜索某表時,不會順帶查詢外鍵表,直到真正使用時才會再讀取數(shù)據(jù)庫查詢;若是使用 Include(),則會在讀取本表時把指定的外鍵表信息也讀出來。
(三)顯式加載<Explicit Loading>有點類似于延遲加載,只是你在代碼中顯式地獲取相關(guān)數(shù)據(jù)。當您訪問一個導(dǎo)航屬性時,它不會自動加載。你需要通過使用實體的對象狀態(tài)管理器并調(diào)用集合上的Collection.Load方法或通過持有單個實體的屬性的Reference.Load方法來手動加載相關(guān)數(shù)據(jù)。
數(shù)據(jù)模型更改如下:
public class Score { [Key] public int Id { get; set; } public int StudentScore { get; set; }//學(xué)生分數(shù) public int StudentID { get; set; }//學(xué)生ID public int CourseID { get; set; }//課程ID //變成了泛型 public virtual List<Student> Student { get; set; }//virtual關(guān)鍵字修飾,用于延遲加載 提高性能 只有顯式調(diào)用時 才會加載 并可以代表一個Student對象 也就是 屬性==對象 public virtual Course Course { get; set; }//virtual關(guān)鍵字修飾,用于延遲加載 提高性能 只有顯式調(diào)用時 才會加載 并可以代表一個Course對象 也就是 屬性==對象 }
代碼如下:
public ActionResult linqTOsql() { using (var db = new StudentContext()) { var Elist = db.Scores.Where(t => t.StudentScore > 80); foreach (Score item in Elist) { var model = db.Entry(item);//Entry: 獲取給定實體的 System.Data.Entity.Infrastructure.DbEntityEntry<TEntity> 對象,以便提供對與該實體有關(guān)的信息的訪問以及對實體執(zhí)行操作的功能。 model.Collection(t => t.Student).Load();//Score中的Student導(dǎo)航屬性 必須為泛型集合 查詢/加載實體集合 foreach (Student A in item.Student) { } } } return View(); }
性能注意事項
如果你知道你立即需要每個實體的相關(guān)數(shù)據(jù),預(yù)先加載通常提供最佳的性能。因為單個查詢發(fā)送到數(shù)據(jù)庫并一次性獲取數(shù)據(jù)的效率通常比在每個實體上再發(fā)出一次查詢的效率更高。例如,在上面的示例中,假定每個系有十個相關(guān)的課程,預(yù)先加載會導(dǎo)致只有一個查詢(join聯(lián)合查詢)往返于數(shù)據(jù)庫。延遲加載和顯式加載兩者都將造成11個查詢和往返。在高延遲的情況下,額外的查詢和往返通常是不利的。
另一方面,在某些情況下使用延遲加載的效率更高。預(yù)先加載可能會導(dǎo)致生成SQL Server不能有效處理的非常復(fù)雜的聯(lián)接查詢?;蛘?,如果您正在處理的是需要訪問的某個實體的導(dǎo)航屬性,該屬性僅為實體集的一個子集,延遲加載可能比預(yù)先加載性能更好,因為預(yù)先加載會將所有的數(shù)據(jù)全部加載,即使你不需要訪問它們。如果應(yīng)用程序的性能是極為重要的,你最好測試并在這兩種方法之間選擇一種最佳的。
延遲加載可能會屏蔽一些導(dǎo)致性能問題的代碼。例如,代碼沒有指定預(yù)先或顯式加載但在處理大量實體并時在每次迭代中都使用了導(dǎo)航屬性的情況下,代碼的效率可能會很低(因為會有大量的數(shù)據(jù)庫往返查詢)。一個在開發(fā)環(huán)境下表現(xiàn)良好的應(yīng)用程序可能會在移動到Windows Azure SQL數(shù)據(jù)庫時由于增加了延遲導(dǎo)致延遲加載的性能下降。你應(yīng)當分析并測試以確保延遲加載是否是適當?shù)?/p>
@陳臥龍的博客

浙公網(wǎng)安備 33010602011771號