翻譯的初衷以及爲何選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇javascript
問題html
你想測試關聯實體或實體集合是否已經加載到上下文中,另外你想使用Code-First來管理數據訪問。java
解決方案數據庫
假設你有如圖5-26所示的概念模型併發
圖5-26 一個包含projects,managers和contractors的模型app
在Visual Studio中添加一個名爲Recipe11的控制檯應用,並確保引用了實體框架6的庫,NuGet能夠很好的完成這個任務。在Reference目錄上右鍵,並選擇 Manage NeGet Packages(管理NeGet包),在Online頁,定位並安裝實體框架6的包。這樣操做後,NeGet將下載,安裝和配置實體框架6的庫到你的項目中。框架
接下來咱們建立三個實體對象:Contractor,Manager,Project,複製代碼清單5-27中的屬性到這三個類中。ide
代碼清單5-27. 實體類性能
public class Contractor { public int ContracterID { get; set; } public string Name { get; set; } public int ProjectID { get; set; } public virtual Project Project { get; set; } } public class Manager { public Manager() { Projects = new HashSet<Project>(); } public int ManagerID { get; set; } public string Name { get; set; } public virtual ICollection<Project> Projects { get; set; } } public class Project { public Project() { Contractors = new HashSet<Contractor>(); } public int ProjectID { get; set; } public string Name { get; set; } public int ManagerID { get; set; } public virtual ICollection<Contractor> Contractors { get; set; } public virtual Manager Manager { get; set; } }
接下來,建立一個名爲Recipe11Context的類,並將代碼清單5-24中的代碼添加到其中,並確保其派生到DbContext類。學習
代碼清單5-28. 上下文
1 public class Recipe11Context : DbContext 2 { 3 public Recipe11Context() 4 : base("Recipe11ConnectionString") 5 { 6 //禁用實體框架的模型兼容 7 Database.SetInitializer<Recipe11Context>(null); 8 } 9 10 public DbSet<Contractor> Contractors { get; set; } 11 public DbSet<Manager> Managers { get; set; } 12 public DbSet<Project> Projects { get; set; } 13 14 protected override void OnModelCreating(DbModelBuilder modelBuilder) 15 { 16 modelBuilder.Entity<Contractor>().ToTable("Chapter5.Contractor"); 17 modelBuilder.Entity<Manager>().ToTable("Chapter5.Manager"); 18 modelBuilder.Entity<Project>().ToTable("Chapter5.Project"); 19 20 // 顯示映射實體鍵 21 modelBuilder.Entity<Contractor>().HasKey(x => x.ContracterID); 22 } 23 }
接下來添加App.Config文件到項目中,並使用代碼清單5-25中的代碼添加到文件的ConnectionStrings小節下。
代碼清單5-29. 鏈接字符串
<connectionStrings>
<add name="Recipe11ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
實體框架公佈了一個IsLoaded屬性。只要它100%的肯定,指定的實體或實體集合所有已經加載且在上下文中有效時,它的值便爲True。圖5-26中的模型表示,項目(Project),項目的管理者(manager)以及項目的承包商(Contractors)。按代碼清單5-30所示,測試關聯實體是否加載到上下文對象中。
代碼清單5-30.使用IsLoaded判斷一個實體或實體集合是否已加載到上下文中
using (var context = new Recipe11Context()) { var man1 = new Manager {Name = "Jill Stevens"}; var proj = new Project {Name = "City Riverfront Park", Manager = man1}; var con1 = new Contractor {Name = "Robert Alvert", Project = proj}; var con2 = new Contractor {Name = "Alan Jones", Project = proj}; var con3 = new Contractor {Name = "Nancy Roberts", Project = proj}; context.Projects.Add(proj); context.SaveChanges(); } using (var context = new Recipe11Context()) { var project = context.Projects.Include("Manager").First(); if (context.Entry(project).Reference(x => x.Manager).IsLoaded) Console.WriteLine("Manager entity is loaded."); else Console.WriteLine("Manager entity is NOT loaded."); if (context.Entry(project).Collection(x => x.Contractors).IsLoaded) Console.WriteLine("Contractors are loaded."); else Console.WriteLine("Contractors are NOT loaded."); Console.WriteLine("Calling project.Contractors.Load()..."); context.Entry(project).Collection(x => x.Contractors).Load(); if (context.Entry(project).Collection(x => x.Contractors).IsLoaded) Console.WriteLine("Contractors are now loaded."); else Console.WriteLine("Contractors failed to load."); } Console.WriteLine("Press <enter> to continue..."); Console.ReadLine();
代碼清單5-30的輸出以下:
Manager entity is loaded. Contractors are NOT loaded. Calling project.Contractors.Load()... Contractors are now loaded.
原理
咱們使用Include()方法,從數據庫中預先加載Project實體和與它關聯的Manager。
查詢以後,咱們經過reference()方法獲取關聯實體Manager的引用和檢查IsLoaded屬性的值,來判斷manager實例是否加載。由於這是一個實體引用(引用一個單獨的父實體),調用Entry()方法返回DbEntityEntry類型上的Reference方法,它返回類型上IsLoaded屬性有效。由於我已經加載了Projects和Manager,因此,該屬性返回True.
接下來咱們檢查Contractor實體集合是否加載。由於咱們沒有在Include()方法中預先加載它,也沒有使用Load()方法直接加載過它,因此它沒有被加載。只要一加載它。IsLoaded屬性便會被設置爲True.
默認行爲,延遲加載,開啓時,當關聯實體或實體集合被引用時,IsLoaded屬性便會被設置爲True。延遲加載會讓實體框架在實體或實體集合被引用時,自動加載。顯式加載和延遲加載有些相似,只是它不是自動的。開發人員必須顯式地使用Load()方法來加載實體,它讓開發人員能夠徹底本身控制是否加載,什麼時候加載關聯實體。
IsLoaded確切的含義,比看起來更讓人迷惑。IsLoaded被調用Load()方法的查詢設置,也被隱式的關係跨度設置。當你查詢一個實體時,會隱式查詢關聯實體Key。若是這個隱式查詢的結果是一個null值,IsLoaded會被設置成True,意思是數據庫沒有關聯的實體。當咱們顯示加載關係並發現沒有關聯實體時,IsLoaded一樣會被設置成True. (譯註:這裏可能會有點難理解,由於涉及到了一個術語:關係跨度(Relationship Span)的理解,它是指EF加載實體時老是一塊兒返回外鍵值,以此來避免一些列的問題)
問題
你想直接加載關聯實體,不依賴默認的延遲加載功能。
解決方案
假設你有如圖5-27所示的概念模型
圖5-27 一個包含實體 doctor、appointment、patient的模型
圖5-27描述的模型,表示醫生(doctor)和他的患者(patient),以及預定(appointment)。代碼清單5-31,顯示加載關聯實體。
代碼清單5-31. 使用Load()方法
1 using (var context = new EFRecipesEntities()) 2 { 3 var doc1 = new Doctor { Name = "Joan Meyers" }; 4 var doc2 = new Doctor { Name = "Steven Mills" }; 5 var pat1 = new Patient { Name = "Bill Rivers" }; 6 var pat2 = new Patient { Name = "Susan Stevenson" }; 7 var pat3 = new Patient { Name = "Roland Marcy" }; 8 9 var app1 = new Appointment 10 { 11 Date = DateTime.Today, 12 Doctor = doc1, 13 Fee = 109.92M, 14 Patient = pat1, 15 Reason = "Checkup" 16 }; 17 var app2 = new Appointment 18 { 19 Date = DateTime.Today, 20 Doctor = doc2, 21 Fee = 129.87M, 22 Patient = pat2, 23 Reason = "Arm Pain" 24 }; 25 var app3 = new Appointment 26 { 27 Date = DateTime.Today, 28 Doctor = doc1, 29 Fee = 99.23M, 30 Patient = pat3, 31 Reason = "Back Pain" 32 }; 33 34 context.Appointments.Add(app1); 35 context.Appointments.Add(app2); 36 context.Appointments.Add(app3); 37 38 context.SaveChanges(); 39 } 40 41 using (var context = new EFRecipesEntities()) 42 { 43 // 禁用延遲加載,由於咱們要顯式加載子實體 44 context.Configuration.LazyLoadingEnabled = false; 45 46 var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers"); 47 48 if (!context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded) 49 { 50 context.Entry(doctorJoan).Collection(x => x.Appointments).Load(); 51 Console.WriteLine("Dr. {0}'s appointments were explicitly loaded.", 52 doctorJoan.Name); 53 } 54 55 Console.WriteLine("Dr. {0} has {1} appointment(s).", 56 doctorJoan.Name, 57 doctorJoan.Appointments.Count()); 58 59 foreach (var appointment in context.Appointments) 60 { 61 if (!context.Entry(appointment).Reference(x => x.Doctor).IsLoaded) 62 { 63 context.Entry(appointment).Reference(x => x.Doctor).Load(); 64 Console.WriteLine("Dr. {0} was explicitly loaded.", 65 appointment.Doctor.Name); 66 } 67 else 68 Console.WriteLine("Dr. {0} was already loaded.", 69 appointment.Doctor.Name); 70 } 71 72 Console.WriteLine("There are {0} appointments for Dr. {1}", 73 doctorJoan.Appointments.Count(), 74 doctorJoan.Name); 75 76 doctorJoan.Appointments.Clear(); 77 78 Console.WriteLine("Collection clear()'ed"); 79 Console.WriteLine("There are now {0} appointments for Dr. {1}", 80 doctorJoan.Appointments.Count(), 81 doctorJoan.Name); 82 83 context.Entry(doctorJoan).Collection(x => x.Appointments).Load(); 84 85 Console.WriteLine("Collection loaded()'ed"); 86 87 Console.WriteLine("There are now {0} appointments for Dr. {1}", 88 doctorJoan.Appointments.Count().ToString(), 89 doctorJoan.Name); 90 91 //目前,DbContext 沒有API去刷新實體,但底層的ObjectContext有,執行下面的動做。 92 var objectContext = ((IObjectContextAdapter)context).ObjectContext; 93 var objectSet = objectContext.CreateObjectSet<Appointment>(); 94 objectSet.MergeOption = MergeOption.OverwriteChanges; 95 objectSet.Load(); 96 97 Console.WriteLine("Collection loaded()'ed with MergeOption.OverwriteChanges"); 98 99 Console.WriteLine("There are now {0} appointments for Dr. {1}", 100 doctorJoan.Appointments.Count(), 101 doctorJoan.Name); 102 } 103 104 105 //演示先加載部分實體集合,而後再加載剩下的 106 using (var context = new EFRecipesEntities()) 107 { 108 // 禁用延遲加載,由於咱們要顯式加載子實體 109 context.Configuration.LazyLoadingEnabled = false; 110 111 //加載第一個doctor而後只附加一個appointment 112 var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers"); 113 context.Entry(doctorJoan).Collection(x => x.Appointments).Query().Take(1).Load(); 114 115 //注意,這裏IsLoaded返回False,由於全部的實體尚未被加載到上下文 116 var appointmentsLoaded = context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded; 117 118 Console.WriteLine("Dr. {0} has {1} appointments loaded.", 119 doctorJoan.Name, 120 doctorJoan.Appointments.Count()); 121 122 //當我須要加載剩下的appointments,只須要簡單的調用Load()來加載它們 123 context.Entry(doctorJoan).Collection(x => x.Appointments).Load(); 124 Console.WriteLine("Dr. {0} has {1} appointments loaded.", 125 doctorJoan.Name, 126 doctorJoan.Appointments.Count()); 127 } 128 129 Console.WriteLine("Press <enter> to continue..."); 130 Console.ReadLine();
代碼清單5-31的輸出以下:
Dr. Joan Meyers's appointments were explicitly loaded. Dr. Joan Meyers has 2 appointment(s). Dr. Joan Meyers was already loaded. Dr. Steven Mills was explicitly loaded. Dr. Joan Meyers was already loaded. There are 2 appointments for Dr. Joan Meyers Collection clear()'ed There are now 0 appointments for Dr. Joan Meyers Collection loaded()'ed There are now 0 appointments for Dr. Joan Meyers Collection loaded()'ed with MergeOption.OverwriteChanges There are now 2 appointments for Dr. Joan Meyers Dr. Joan Meyers has 1 appointments loaded. Dr. Joan Meyers has 2 appointments loaded. Press <enter> to continue...
譯註:書的結果有誤,這是我(付燦)運行示例後的輸出。
原理
插入一些簡單的數據到數據庫以後,咱們顯式地禁用了延遲加載特徵,由於咱們要顯式控制關聯實體的加載。有兩種方式禁用延遲加載:
一、設置Context.Configuration對象的LazyLoadingEnabled屬性爲False。它會禁用上下文中全部實體對象的延遲加載。
二、在每一個實體類中移除導航屬性的virtual修飾關鍵字。這種方法會禁用相應實體的延遲加載,這樣就能讓你顯式控制延遲加載。
咱們先獲取一個Doctor實體。若是你使用了顯式加載,這將是使用IsLoaded屬性檢查關聯實體或實體集合是否加載的一個好實踐。在代碼中,咱們檢查doctor對象的appointments是否加載。若是沒有,咱們使用Load()方法加載它們。
在foreach循環中,咱們枚舉了appointments,檢查與它關聯的doctor是否加載。注意輸出,這時只有一個醫生被加載,別的沒有被加載。這是由於咱們的第一個查詢只獲取了一個doctor。在獲取appointmetns的過程當中,實體框架會鏈接醫生(doctor)和他的預定(appintments),這個過程被稱爲(非正式的)Relationship fixup(譯註:這些概念雖然已經產生不少年,但中文資料關於它的介紹幾乎沒有,只看到一位兄弟把它翻譯爲「關係創建」,我的以爲它能表達這個詞的含義,就借用了)。Relationship fixup 不會創建好全部的關聯,特別是多對關聯的實體。
咱們打印出doctor關聯實體集合appointments已加載的數量。而後咱們調用Clear()方法,清空doctorJoan關聯實體集合。這個方法會清除掉doctorJoan和appointments間的關係。有趣的是,它並不會把實例從內存中移除;這些實例仍在上下文中--它們只是不在跟Doctor實體鏈接。
使人奇怪的是,調用Load()方法從新加載appointemts後,從輸出咱們看到,doctorJoan的關聯集合沒有對象。發生了什麼呢?原來是由於Load()方法須要使用一個控制如何加載實例進入上下文的參數。該參數的默認值是MergeOption.AppendOnly,它只是簡單地把不存在上下文中的實體對象加載到上下文中。在咱們示例中,沒有沒有真正地把實體對象從上下文中移除。在使用Clear()方法時,只是將實體對象從關聯集合中移除,而沒有從上下文中移除。當咱們使用Load()方法從新加載時,因爲使用了默認的MergeOption.AppendOnly選項,又沒有發現新的實例,全部沒有實體對象被加載到上下文中(譯註:關聯實體集合天然也不會添加,但注意這裏的Load()方法是生成了SQL查詢語句,產生了數據庫交互,並從數據庫獲取了相應的數據的)。其它的合併選項包含:NoTracking,OverwriteChanges,和PreserveChages。當咱們使用OverwriteChanges選項時,appointments出如今Doctor實體對象的關聯集合Appointments中了。
注意,咱們在代碼中是如何進入底層,經過ObjectContext上下文對象,訪問實體框架中MergeOption行爲的。MergeOption在DbContext中不被直接支持。你可能會回想起,咱們使用實體框架時,有兩個上下文對象可使用。在實體框架6中,首選是使用DbContext。它提供了直觀,易於使用的,遺留ObectContext上下文對象的外觀模式。如代碼中所示,能夠經過顯式轉換,仍然可使用ObjectContext。
與AppendOnly一塊兒,MegeOption類型公佈了另外三個選項:
一、NoTracking選項會關閉加載實例的對象狀態跟蹤。使用NoTracking選項,實體框架將再也不跟蹤對象的變化,同時也再也不知道對象是否已經加載到上下文中。若是對象使用NoTracking選項加載,那麼它能夠被用於對象的導航屬性上。NoTracking有一個額外的反作用。若是咱們使用NoTracking選項加載一個Doctor實體,那麼,使用Load()方法加載appointments時,無論默認行爲AppendOnly,仍然會使用NoTracking。
二、OverwriteChanges選項會使用從數據庫獲取的數據更新當前對象的值,實體框架會繼續使用同一個實體對象。這個選項在你想放棄上下文中對實體對象的修改,並使用數據庫中的數據來刷新它時特別管用。這個選項很是有用,例如,你的應用正在實現一個撤消操做的功能。
三、PreserveChanges選項,本質上是OverwriteChanges選項的對立選項。當數據庫中有改變時,它會更新實體對象的值。可是當內存裏的值發生改變時,它不會更新實體對象的值。一個實體對象在內存中被修改,它不會被刷新。更準確地說,在內存中修改實體對象時,它的當前值(cruuent value)不會改變,可是,若是數據庫有改變時,它的初始值(original value)會被更新。
當你使用Load()方法時,這裏有一些限制。實體狀態爲Added,Deleted,或者是Detached時,不能調用Load()方法。
不管在何時,只要想限制關聯實體集合中實體的加載數量,Load()方法對性能的提高都有幫助。例如,咱們的醫生有大量的預定,可是在不少時候,他只能處理一部分。在極罕見的狀況下會處理整個集合,我能夠簡單的調用Load()方法加載剩下的appointments實例。如代碼清單5-32所示。
代碼清單5-32.演示加載部分關聯實體集合
//演示先加載部分實體集合,而後再加載剩下的 using (var context = new EFRecipesEntities()) { // 禁用延遲加載,由於咱們要顯式加載子實體 context.Configuration.LazyLoadingEnabled = false; //加載第一個doctor而後只附加一個appointment var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers"); context.Entry(doctorJoan).Collection(x => x.Appointments).Query().Take(1).Load(); //注意,這裏IsLoaded返回False,由於全部的實體尚未被加載到上下文 var appointmentsLoaded = context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded; Console.WriteLine("Dr. {0} has {1} appointments loaded.", doctorJoan.Name, doctorJoan.Appointments.Count()); //當我須要加載剩下的appointments,只須要簡單的調用Load()來加載它們 context.Entry(doctorJoan).Collection(x => x.Appointments).Load(); Console.WriteLine("Dr. {0} has {1} appointments loaded.", doctorJoan.Name, doctorJoan.Appointments.Count()); }
代碼清單5-31的輸出以下:
Dr. Joan Meyers has 1 appointments loaded. Dr. Joan Meyers has 2 appointments loaded.
實體框架交流QQ羣: 458326058,歡迎有興趣的朋友加入一塊兒交流
謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/VolcanoCloud/