Code First Entity Framework 6化被動為主動之explicit loading模式實戰分析( 附源碼)
Posted on 2018-09-14 09:48 冰碟 閱讀(444) 評論(0) 收藏 舉報在使用Entity Framework加載關聯實體時,可以有三種方式:
1.懶加載(lazy Loading);
2.貪婪加載(eager loading);
3.顯示加載(explicit loading)。
EF默認使用的是懶加載(lazy Loading)。一切由EF自動處理。
這種方式會導致應用程序多次連接數據庫,這種情況推薦在數據量較大的情況下使用。當我們需要加載數據較少時,一次性全部加載數據會相對更高效。
我們來看看EF的顯示加載(explicit loading)如何讓我們完全掌控數據的加載(非自動化)。
這里我們使用Code First模式。
數據表:商品表,商品分類表,品牌表
Step1:新建控制臺應用程序,添加EF6引用(可以使用NuGet獲取)



Step2:創建三個實體對象:Product、Category 、Brand
Product實體類:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace EFExplicitLoading.Models {
7 public class Product {
8 public int ProductId { get; set; }
9 public String Title { get; set; }
10 public int CategoryId { get; set; }
11
12 public virtual Category Category { get; set; }
13
14 public virtual Brand Brand { get; set; }
15
16 }
17 }
Category實體類:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace EFExplicitLoading.Models {
7 public class Category {
8
9 public Category() {
10 Products = new HashSet<Product>();
11 }
12
13 public int CategoryId { get; set; }
14 public String Name { get; set; }
15
16 public virtual ICollection<Product> Products { get; set; }
17 }
18 }
Brand實體類:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace EFExplicitLoading.Models {
7 public class Brand {
8
9 public Brand() {
10 Products = new HashSet<Product>();
11 }
12
13 public int BrandId { get; set; }
14
15 public String Name { get; set; }
16
17 public virtual ICollection<Product> Products { get; set; }
18 }
19 }
Step3:創建派生自DbContext的類(ExplicitContext)
1 using System.Data.Entity;
2 using EFExplicitLoading.Models;
3
4 namespace EFExplicitLoading {
5
6 public class ExplicitContext : DbContext {
7 public ExplicitContext()
8 : base("ExplicitConnectionString") {
9 }
10
11 public DbSet<Product> Products { get; set; }
12
13 public DbSet<Category> Categories { get; set; }
14
15 public DbSet<Brand> Brands { get; set; }
16
17 protected override void OnModelCreating(DbModelBuilder modelBuilder) {
18 modelBuilder.Entity<Product>().ToTable("Product");
19 modelBuilder.Entity<Brand>().ToTable("Brand");
20 modelBuilder.Entity<Category>().ToTable("Category");
21 }
22 }
23
24 }
Step4:在App.config中添加connectionStrings節點
1 <connectionStrings> 2 <add name="ExplicitConnectionString" connectionString="Data Source=.;Initial Catalog=EFExplicit;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> 3 </connectionStrings>
準備工作已就緒,當我們使用ExplicitContext類時,EF會根據實體自動創建數據庫和表
下面回到控制臺入口函數處:
Step5:添加測試數據
1
2 using (var context = new ExplicitContext()) {
3 //先清空數據
4 context.Database.ExecuteSqlCommand("delete from product");
5 context.Database.ExecuteSqlCommand("delete from brand");
6 context.Database.ExecuteSqlCommand("delete from category");
7
8 var category1 = new Category {Name = "服裝"};
9 var category2 = new Category {Name = "家電"};
10 var brand1 = new Brand {Name = "品牌1"};
11 var brand2 = new Brand {Name = "品牌2"};
12
13 context.Products.Add(new Product {Brand = brand1, Category = category1, Title = "產品1"});
14 context.Products.Add(new Product {Brand = brand1, Category = category2, Title = "產品2"});
15 context.Products.Add(new Product {Brand = brand1, Category = category2, Title = "產品3"});
16 context.Products.Add(new Product {Brand = brand2, Category = category2, Title = "產品4"});
17 context.Products.Add(new Product {Brand = brand2, Category = category2, Title = "產品5"});
18 context.Products.Add(new Product {Brand = brand2, Category = category2, Title = "產品6"});
19 context.SaveChanges();
20 }
Step6:開始測試explicit loading
1 using (var context = new ExplicitContext()) {
2 //禁用懶加載
3 context.Configuration.LazyLoadingEnabled = false;
4
5 var category = context.Categories.First(c => c.Name == "家電");
6
7 //判斷分類關聯的產品是否已經被加載
8 if (!context.Entry(category).Collection(x => x.Products).IsLoaded) {
9 context.Entry(category).Collection(x => x.Products).Load();
10 Console.WriteLine("分類{0}的產品被顯示加載...", category.Name);
11 }
12
13 Console.Write("分類{0}下的共有{1}件產品", category.Name, category.Products.Count());
14
15 foreach (var product in context.Products) {
16 if (!context.Entry(product).Reference(x => x.Category).IsLoaded) {
17 context.Entry(product).Reference(x => x.Category).Load();
18 Console.WriteLine("分類{0}被顯示加載.", product.Category.Name);
19 }
20 else {
21 Console.WriteLine("分類{0}已經加載過了.", product.Category.Name);
22 }
23 }
24
25 //重新使用category計算 產品數量
26 Console.WriteLine("產品加載完后,分類{0}下有{1}件產品", category.Name, category.Products.Count());
27
28 category.Products.Clear();
29 Console.WriteLine("產品集合被清空了.");
30 Console.WriteLine("此時分類{0}下有{1}件產品", category.Name, category.Products.Count());
31
32
33 context.Entry(category).Collection(x => x.Products).Load();
34 Console.WriteLine("重新顯示加載產品");
35 Console.WriteLine("此時分類{0}下有{1}件產品", category.Name, category.Products.Count());
36
37 //使用ObjectContext刷新實體
38 var objectContext = ((IObjectContextAdapter) context).ObjectContext;
39 var objectSet = objectContext.CreateObjectSet<Product>();
40 objectSet.MergeOption = MergeOption.OverwriteChanges;
41 objectSet.Load();
42 //MergeOption.AppendOnly
43 //MergeOption.OverwriteChanges
44 //MergeOption.NoTracking
45 //MergeOption.PreserveChanges
46
47
48 Console.WriteLine("產品集合以MergeOption.OverwriteChanges的方式重新加載");
49 Console.WriteLine("此時分類{0}下有{1}件產品", category.Name, category.Products.Count());
50 }
51
52
53 Console.WriteLine("測試部分加載...");
54 //測試部分加載,然后加載所有
55 using (var context = new ExplicitContext()) {
56 //禁用懶加載
57 context.Configuration.LazyLoadingEnabled = false;
58 var category = context.Categories.First(c => c.Name == "家電");
59 //先加載1條數據
60 Console.WriteLine("先加載1條數據");
61 context.Entry(category).Collection(x => x.Products).Query().Take(1).Load();
62 Console.WriteLine("分類{0}下共有{1}件產品", category.Name, category.Products.Count());
63
64 //加載所有
65 context.Entry(category).Collection(x => x.Products).Load();
66 Console.WriteLine("加載所有數據");
67
68 Console.WriteLine("此時分類{0}下有{1}件產品", category.Name, category.Products.Count());
69 }
70
71 Console.ReadKey();
執行結果:

禁用懶加載(lazy loading)
要想測試我們的explict loading,必須先禁用懶加載,有2種方式可以禁用懶加載:
1.設置Context.Configuration.LazyLoadingEnabled的值為false 或
2.移除每一個實體類中的關聯實體屬性(導航屬性)的virtual訪問修飾符,這種方法可以更精確控制懶加載,即只禁用移除virtual修飾符實體的懶加載
IsLoaded屬性和Load方法
接下來,我們利用EF獲取一條Name為家電的分類實體數據,由于Category與Product是一對多的關系,此時我們可以使用IsLoaded屬性來測試此分類下的Products集合是否被加載
context.Entry(category).Collection(x => x.Products).IsLoaded
若沒有加載,我們使用Load方法來加載關聯的Products,在接下來的foreach遍歷中,我們可以看到,Category關聯的Product已經全部被加載完畢
PS:當使用Take(n)方法Load數據時,IsLoaded屬性依然為False,因為只有當關聯的所有數據加載完,IsLoaded才為true
關系修復(relationship fixup)
這里Entity Framework使用稱為"關系修復"的技術(relationship fixup),即當我們單獨加載關聯實體的時候(Product),這些數據會自動掛載到已經載入的實體(Category),但這種關系修復并不是總是會生效,比如在多對多的關系中就不會這樣處理。
關于Clear()
然后我們打印出來Category中有多少Product,之后使用Clear()方法清空category對象的關聯集合Products。
Clear方法會移除Category和Product的關聯關系,但Product集合數據并沒有刪除,依然存在上下文的內存中,只是它不再與Category關聯。
后面我們又重新使用Load加載Product但category.Products.Count()的值依然為0。這正好說明Clear方法的本質。
這是因為Load默認使用MergeOption.AppendOnly的方式加載數據,找不到Product實例了(被Clear了)。然后我們使用MergeOption.OverwriteChanges的方式才將數據重新關聯
關于MergeOption(實體合并選項)
MergeOption枚舉共有4個選項
1.MergeOption.AppendOnly
在現有的關系基礎上合并
2.MergeOption.OverwriteChanges
OverwriteChanges模式,會從數據庫中更新當前上下文實例的值(使用同一個實體對象的實例,如category)。
當你想要恢復實體在上下文的改變,從數據庫刷新實體時,使用這個模式就非常有用。
3.MergeOption.NoTracking
無追蹤模式不會跟蹤對象的變化,也不會意識到對象已經被加載到當前上下文
NoTracking可以應用到一個實體的導航屬性(關聯實體屬性),但這個實體也必須使用NoTracking
反過來,NoTracking應用到某個實體時,這個實體的導航屬性會忽略默認的AppendOnly模式而使用NoTracking模式
4.MergeOption.PreserveChanges
PreserveChanges選項本質上與OverwriteChanges選項相反,
PreserveChanges模式會更新查詢結果集(若數據庫中有變化),但不會更新在當前上下文內存中的值
PS: 也許你已經注意到,這里我們使用了ObjectContext對象,而不是context對象,這是因為DbContext不能直接使用MergeOption類型,所以必須使用ObjectContext對象
關于性能提升
在任何時候,關聯實體集合的數量被限制時,使用Load方法,會有助于我們提升程序的性能,比如加載一個Category中的5條Product。
在少量場景中,我們需要整個關聯集合時,也可以使用Load
PS:當一個實體在Added(添加)、Deleted(刪除)、Detected(分離)狀態時,Load方法無法使用。
關于Entity Framework性能提升的方法還有很多,當然,我們必須根據自己的項目實際情況來做優化。不同的應用場景,選擇不同的優化方案
示例代碼:點此下載
轉載自:http://www.rzrgm.cn/jayshsoft/p/entity-framework-explicit-loading.html

浙公網安備 33010602011771號