《Entity Framework 6 Recipes》中文翻譯系列 (45) ------ 第八章 POCO之獲取原始對象與手工同步對象圖和變化跟蹤器

翻譯的初衷以及爲何選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇html

8-6  獲取原始對象

問題數據庫

  你正在使用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保存實體。

 

8-7  手工同步對象圖和變化跟蹤器

問題

  你想手工同步你的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/

相關文章
相關標籤/搜索