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