前言
延遲加載也可以叫做按需加載,可以分兩方面來理解,一方面指暫時不需要該數據,不用在當前馬上加載,而可以推遲到使用它時再加載;另一方面指不確定是否將會需要該數據,所以暫時請不要加載,待確定需要后再加載它。延遲加載是一種很重要的數據訪問特性,可以有效地減少與數據源的交互(注意,這里所提的交互不是指交互次數,而是指交互的數據量),從而提升程序性能。
接下來,我將針對上一篇文章中提出的DIY實體數據模型,討論如何使其具有延遲加載特性。本文的所有測試均建立在已有的測試數據之上,測試數據准備代碼如下:
1 [ClassInitialize()] 2 public static void MyClassInitialize(TestContext testContext) 3 { 4 using (var db = new EntityContext()) 5 { 6 var role = db.Roles.FirstOrDefault(); 7 if (role == null) 8 { 9 role = new Role(); 10 role.ID = Guid.NewGuid(); 11 role.Name = "員工"; 12 } 13 14 var user = db.Users.FirstOrDefault(); 15 if (user == null) 16 { 17 user = new User(); 18 user.ID = Guid.NewGuid(); 19 user.Account = "Apollo"; 20 user.Password = "11111111"; 21 user.Roles = new List<Role>() { role }; 22 23 var userDetail = new UserDetail(); 24 userDetail.ID = user.ID; 25 userDetail.Name = "Yilin"; 26 userDetail.Sex = "男"; 27 userDetail.Birthday = DateTime.Now; 28 29 user.UserDetail = userDetail; 30 31 db.Users.AddObject(user); 32 db.SaveChanges(); 33 } 34 } 35 }
即時加載
上一篇文章提出的DIY實體數據模型采用的是即時加載模式,它存在幾點缺陷。首先,如果需要關聯對象數據,必須在查詢數據時顯式指明,如下面的查詢Lambda表達式中,通過使用Include(“UserDetail”)方法來指定需要加載User對象的UserDetail數據:
1 [TestMethod] 2 public void IncludeQueryTest() 3 { 4 using (var db = new EntityContext()) 5 { 6 var user = db.Users.Include("UserDetail").FirstOrDefault(o => o.Account == "Apollo"); 7 Assert.IsNotNull(user); 8 Assert.IsNotNull(user.UserDetail); 9 Assert.IsNotNull(user.Roles); 10 } 11 }
運行上面的測試,會發現前面兩個斷言都通過,第三個斷言失敗,這是因為沒有顯式指定加載User關聯的Roles數據。
其次,即時加載會在查詢時加載所有顯式指示的加載項。來看即時加載測試代碼運行情況與SQL Server Profiler工具截獲的SQL對應圖:
圖1即時加載
從上圖中可以得到兩點信息,首先在執行Lambda表達式語句時,EF即時將其翻譯成了SQL語句,並執行了查詢操作;其次生成的SQL語句包括了所有顯式指定的加載數據項。
延遲加載
要使DIY實體數據模型支持延遲加載特性,其實很簡單,僅需要將關聯對象屬性聲明為virtual即可:
1 public class User 2 { 3 public Guid ID { get; set; } 4 5 public string Account { get; set; } 6 7 public string Password { get; set; } 8 9 public virtual UserDetail UserDetail { get; set; } 10 11 public virtual IList<Role> Roles { get; set; } 12 }
測試方法:
1 [TestMethod] 2 public void QueryTest() 3 { 4 using (var db = new EntityContext()) 5 { 6 var user = db.Users.FirstOrDefault(o => o.Account == "Apollo"); 7 Assert.IsNotNull(user); 8 Assert.IsNotNull(user.UserDetail); 9 Assert.IsNotNull(user.Roles); 10 } 11 }
運行以上測試方法,所有斷言全部通過,證明延遲加載模式下,不需要顯式指定需加載的數據項。並且,在Lambda查詢表達式語句執行后,並沒有執行關聯對象數據的查詢操作,如下圖所示:
圖2延遲加載
而是在使用數據時才執行關聯對象的數據查詢操作,如下圖所示:
圖3按需加載
實踐建議
延遲加載雖然有諸多優點,但並不是所有情景下都是最好的選擇。濫用延遲加載,有時不但不能提升軟件性能,反而會適得其反。在大多數情況下,作為程序開發人員,我們都清楚在當前的程序上下文環境中,有哪些關聯數據將是需要的。此時,通過顯式指定要加載的關聯數據,可以強制Entity Framework一次性將所需數據全部取回,這樣就能避免因延遲加載而導致的與數據源進行多次交互而帶來的性能問題。所以,往往需要在加載數據量和數據交互次數上權衡后作出取舍。
有兩種方式可以顯式指定Entity Framework使用即時加載模式進行數據查詢,一種是通過在實體對象上下文環境類的構造函數中設置是否支持延遲加載屬性值,如下所示:
1 public EntityContext() 2 : this("name=Membership") 3 { 4 this.ContextOptions.LazyLoadingEnabled = true; 5 }
這種方法是作用於實體對象模型全局的,所以並不推薦。另一種方法就是前文提到的使用Include()方法顯式指定需加載的關聯對象屬性,這種方法方便靈活,值得推薦:
1 var user = db.Users.Include("UserDetail").FirstOrDefault(o => o.Account == "Apollo");
總結
本文首先對DIY實體數據模型即時加載特性進行了驗證;然后提出了使該實體數據模型支持延遲加載特性的方法,並對修改后的實體數據模型進行了延遲加載特性驗證;最后給出了在項目開發中如何在即時加載和延遲加載兩者中進行取舍,以及在延遲加載模式下臨時執行即時加載的一些建議。希望本文對你了解和合理應用Entity Framework的即時加載和延遲加載有所幫助。
下一篇文章將分享如何ASP.NET頁面中使用EntityDataSource數據源控件,輕松實現實體數據的綁定和持久化。