翻譯的初衷以及爲何選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇html
問題數據庫
你正在使用POCO,想從數據庫獲取原始對象。框架
解決方案ide
假設你有如圖8-7所示的模型。你正在離線環境下工做,你想應用在獲取客戶端修改以前,使用Where從句和FirstDefault()方法從數據庫中獲取原始對象。性能
圖8-7.包含一個單獨實體Item的模型學習
按代碼清單8-9的方式,在獲取實體以後,使用新值更新實體並將其保存到數據庫中。ui
代碼清單8-9. 獲取最新添加的實體並使用Entry()方法替換它的值this
class Program { static void Main(string[] args) { RunExample(); } static void RunExample() { int itemId = 0; using (var context = new EFRecipesEntities()) { var item = new Item { Name = "Xcel Camping Tent", UnitPrice = 99.95M }; context.Items.Add(item); context.SaveChanges(); //爲下一步保存itemId itemId = item.ItemId; Console.WriteLine("Item: {0}, UnitPrice: {1}", item.Name, item.UnitPrice.ToString("C")); } using (var context = new EFRecipesEntities()) { //假設這是一個更新,咱們獲取的item使用一個新的價格 var item = new Item { ItemId = itemId, Name = "Xcel Camping Tent", UnitPrice = 129.95M }; var originalItem = context.Items.Where(x => x.ItemId == itemId).FirstOrDefault<Item>(); context.Entry(originalItem).CurrentValues.SetValues(item); context.SaveChanges(); } using (var context = new EFRecipesEntities()) { var item = context.Items.Single(); Console.WriteLine("Item: {0}, UnitPrice: {1}", item.Name, item.UnitPrice.ToString("C")); } Console.WriteLine("Enter input as exit to exit.:"); string line = Console.ReadLine(); if (line == "exit") { return; }; } } public partial class Item { public int ItemId { get; set; } public string Name { get; set; } public decimal UnitPrice { get; set; } } public partial class EFRecipesEntities : DbContext { public EFRecipesEntities() : base("name=EFRecipesEntities") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } public DbSet<Item> Items { get; set; } }
代碼清單的輸出以下:spa
Item: Xcel Camping Tent, UnitPrice: $99.95 Item: Xcel Camping Tent, UnitPrice: $129.95
原理翻譯
在代碼清單8-9中,咱們插入一個item到模型中並保存到數據庫中。而後咱們僞裝接收到了一個更新的item,也許是來至一個Silverlight客戶端。
接下來,咱們要更新這個item到數據庫中。爲了實現這個操做,咱們從數據庫中獲取這個對象到上下文中。在這個過程當中,咱們使用了where從句和FirstOrDefault方法。而後,咱們使用方法Entry(),它能訪問整個實體,而且,能在實體上應用不少方法。咱們應用了CureentValues.SetVaules方法,用從客戶端接受到的新值來替換原始值。最後,咱們調用SaveChages保存實體。
問題
你想手工同步你的POCO類和更化跟蹤器。
更化跟蹤器會訪問實體框架正在存儲的,且正在被跟蹤的實體的信息。這些信息不僅是你存儲在實體屬性中的值,還包含實體的當前狀態、從數據庫獲取的原始值、哪些屬性被修改了以及別的數據。變化跟蹤器提供額外操做的訪問,這些操做能在實體上執行,例如,從新從數據庫中加載值 ,以確保你有最新數據。
實體框架有兩種不的方法來跟蹤你的對象:基於快照的變化跟蹤和變化跟蹤代理。
基於快照的變化跟蹤
POCO類不包含任何的,當屬性值發生更改時,通知實體框架的邏輯。由於,當一個屬性的值發生變化時,沒有任何能夠通知的方式。當實體框架第一次遇到一個對象時,它會爲每個屬性的值在內存中生成一個快照。當一個對象從查詢中返回,或者咱們添加一個對象到DbSet中時,快照就被生成了。當實體框架須要知道發生了哪些變化時,它會瀏覽每個對象並用他們的當前值和快照比較。這個處理過程是被一個在變化跟蹤器中名爲DetectChanges的方法觸發的。
變化跟蹤代理
變化跟蹤的另外一項技術是變化跟蹤代理,它能使實體框架具備接收變動通知的能力。 變化跟蹤代理是使用實現延遲加載時動態建立代理的機制來實現的,這個動態建立的代理,不僅提供延遲加載的功能,當對象發生改變時,它還能通知上下文對象。爲了使用變化跟蹤代理,你建立的類必須知足,實體框架能在運行時建立一個派生至你的POCO類的動態類型,在這個類型中,它重載了每一個屬性。這個動態類型叫作動態代理,在重載屬性中包含當屬性值發生改變時,通知實體框架的邏輯。
基於快照的變化跟蹤,依賴實體框架在變化發生時能執行檢測。DbContext API的默認行爲是,經過DbContext上的事件來自動執行檢測。DetectChanges不只僅是更新上下文的狀態管理信息,這些狀態管理信息能讓更改持久化到數據庫中,當你有一個引用類型導航屬性,集合類型導航屬性和外鍵的組合時,它還能執行關係修正。清晰認識變化何時被檢測,這些變化要作什麼,以及如何控制它們是很是重要的。
實體框架須要知道變動最明顯的時間是在執行SaveChanges期間。還有不少地方也須要知道變動的狀況。好比,詢問更化跟蹤器實體對象的當前狀態時,它須要掃描並檢測任何發生的變動。掃描和檢沒並不僅發生在咱們討論的問題中,當你執行DbContext中的不少API都能引發DetectChanges方法的運行。在絕大多數狀況下,DetectChanges方法足夠快,不會引發性能問題。可是,若是在內存中有大量的對象或者在DbContext中連續不斷地執行操做,自變動檢測行爲就會成爲性能關注點。幸運的是,有選項能夠把這個自動變動檢測關閉掉,並在須要時手工調用它。但稍有不慎,可能會致使意想不到的後果。實體框架精心地爲你提供了關閉自動更變檢測的功能。若是在性能差的地方使用它時,你將負擔起調用DetectChages方法的責任。一旦這個部分的代碼執行完畢,就得經過DbContext.Configuration.AutoDetectChangesEnable標識把它啓用。
解決方案
假設你有如圖8-8所示的模型。它描述了演示者和爲各類會議準備的演講。
圖8-8. 演講者和他準備的演講之間多對多關係模型
首先須要注意的是,在咱們的模型中,Speaker和 Talk之間是多對多關聯。咱們經過獨立關聯來實現(在數據庫中使用SpeakerTalk連接表)這個模型,讓它支持一個演講者對應多場演講,一場演講對應多位演講者。
咱們想手工同步對象圖和變化跟蹤器。經過調用DetectChanges()方法來實現。同時,將演示同步是如何進行的。
按代碼清單8-10的方法,手工同步你的POCO實體對象圖和變化跟蹤器。
代碼清單8-10. 當須要手工同步變化跟蹤器時,顯式使用DetectChages()方法
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 RunExample(); 6 } 7 8 static void RunExample() 9 { 10 using (var context = new EFRecipesEntities()) 11 { 12 context.Configuration.AutoDetectChangesEnabled = false; 13 var speaker1 = new Speaker { Name = "Karen Stanfield" }; 14 var talk1 = new Talk { Title = "Simulated Annealing in C#" }; 15 speaker1.Talks = new List<Talk> { talk1 }; 16 17 //關聯還沒有完成 18 Console.WriteLine("talk1.Speaker is null: {0}", 19 talk1.Speakers == null); 20 21 context.Speakers.Add(speaker1); 22 23 //如今關聯已經修正 24 Console.WriteLine("talk1.Speaker is null: {0}", 25 talk1.Speakers == null); 26 Console.WriteLine("Number of added entries tracked: {0}", 27 context.ChangeTracker.Entries().Where(e => e.State == System.Data.Entity.EntityState.Added).Count()); 28 context.SaveChanges(); 29 //修改talk的標題 30 talk1.Title = "AI with C# in 3 Easy Steps"; 31 Console.WriteLine("talk1's state is: {0}", 32 context.Entry(talk1).State); 33 context.ChangeTracker.DetectChanges(); 34 Console.WriteLine("talk1's state is: {0}", 35 context.Entry(talk1).State); 36 context.SaveChanges(); 37 } 38 39 using (var context = new EFRecipesEntities()) 40 { 41 foreach (var speaker in context.Speakers.Include("Talks")) 42 { 43 Console.WriteLine("Speaker: {0}", speaker.Name); 44 foreach (var talk in speaker.Talks) 45 { 46 Console.WriteLine("\tTalk Title: {0}", talk.Title); 47 } 48 } 49 } 50 } 51 } 52 public partial class Speaker 53 { 54 public int SpeakerId { get; set; } 55 public string Name { get; set; } 56 public ICollection<Talk> Talks { get; set; } 57 } 58 public partial class Talk 59 { 60 public int TalkId { get; set; } 61 public string Title { get; set; } 62 public System.DateTime CreateDate { get; set; } 63 public System.DateTime RevisedDate { get; set; } 64 public ICollection<Speaker> Speakers { get; set; } 65 } 66 public partial class EFRecipesEntities : DbContext 67 { 68 public EFRecipesEntities() 69 : base("name=EFRecipesEntities") 70 { 71 } 72 73 protected override void OnModelCreating(DbModelBuilder modelBuilder) 74 { 75 throw new UnintentionalCodeFirstException(); 76 } 77 78 public DbSet<Speaker> Speakers { get; set; } 79 public DbSet<Talk> Talks { get; set; } 80 81 public override int SaveChanges() 82 { 83 var changeSet = this.ChangeTracker.Entries().Where(e => e.Entity is Talk); 84 if (changeSet != null) 85 { 86 foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Added).Select(a => a.Entity as Talk)) 87 { 88 entry.CreateDate = DateTime.UtcNow; 89 entry.RevisedDate = DateTime.UtcNow; 90 } 91 foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Modified).Select(a => a.Entity as Talk)) 92 { 93 entry.RevisedDate = DateTime.UtcNow; 94 } 95 } 96 return base.SaveChanges(); 97 } 98 }
代碼清單8-10的輸出以下:
talk1.Speaker is null: True talk1.Speaker is null: False Number of added entries tracked: 2 talk1's state is: Unchanged talk1's state is: Modified Speaker: Karen Stanfield Talk Title: AI with C# in 3 Easy Steps
原理
代碼清單8-10中的代碼有點複雜,讓咱們一步一步的來說解。首先,關閉自動跟蹤,建立speaker和talk實例,而後把talk添加到speaker的導航屬性集合Talks中。此時,talk已是speaker的導航屬性集合Talks的一部分,可是speaker還不是talk的導航屬性集合Speakers的一部分。關聯中的另外一邊尚未被修正。
接下來,咱們使用Add方法,將speaker1添加到上下文中。從輸出的第二行能夠看出,如今,talk的導航屬性集合Speakers已經正確。實體框架已經修正了關聯中的另外一邊。在這裏實體框架作了兩件事,第一件事是它通知對象狀態管理器,有三個對象被建立,雖然最終輸出結果不是三個,這是由於它把多對多關聯看做是一個獨立關係,而不是一個單獨的實體。所以,輸出結果爲2。這些對象分別是speaker和talk。沒有多對多關聯對應的對象。這是由於變化跟蹤器沒有返回獨立關係的狀態。第二件事是實體框架修正了talk中的導航屬性Speakers。
當咱們調用SaveChages()方法時,實體框架會使用重載版本的Savechanges。在這個方法中,咱們更新屬性 CreateDate和RevisedDate。在調用SaveChanges()方法以前,實體框架會調用DetectChanges()來查找發生的變動。在代碼清單8-10中,咱們重寫了SaveChages()方法。
DetectChanges()方法依賴一個快照來比較每個實體的每個屬性的原始值和當前值。這個過程能判斷出對象圖中那些變化發生了。對於一個大的對象圖,這個比較過程可能會比較耗時。
實體框架交流QQ羣: 458326058,歡迎有興趣的朋友加入一塊兒交流
謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/VolcanoCloud/