《Entity Framework 6 Recipes》中文翻譯系列 (44) ------ 第八章 POCO之POCO中使用值對象和對象變動通知

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

8-4  POCO中使用值對象(Complex Type--也叫複合類型)屬性

問題數據庫

  你想在POCO中使用值對象。框架

解決方案ide

  假設你有如圖8-5所示的模型。在模型中,屬性Name是一個值對象。學習

圖8-5. 一個包含employee的模型,屬性Name是一個值對象,它由FirstName和LastName複合而成ui

 

   POCO支持值對象,當你重構兩個或多個實體屬性到一個值對象時,一個新的類在默認狀況下被生成,這個類就是這個值對象的類型。一個類型爲這個值對象類型的屬性同時也被添加到主實體中。只有類被支持,由於實體框架在保存值對象時生成了它們。代碼清單8-6演示了,使用值對象類型的Name屬性來表示員工的姓和名。this

代碼清單8-6. 在POCO中使用值對象spa

class Program
    {
        static void Main(string[] args)
        {
            RunExample();
        }

        static void RunExample()
        {
            using (var context = new EFRecipesEntities())
            {
                context.Employees.Add(new Employee
                {
                    Name = new Name
                    {
                        FirstName = "Annie",
                        LastName = "Oakley"
                    },
                    Email = "aoakley@wildwestshow.com"
                });
                context.Employees.Add(new Employee
                {
                    Name = new Name
                    {
                        FirstName = "Bill",
                        LastName = "Jordan"
                    },
                    Email = "BJordan@wildwestshow.com"
                });
                context.SaveChanges();
            }

            using (var context = new EFRecipesEntities())
            {
                foreach (var employee in
                         context.Employees.OrderBy(e => e.Name.LastName))
                {
                    Console.WriteLine("{0}, {1} email: {2}",
                                       employee.Name.LastName,
                                       employee.Name.FirstName,
                                       employee.Email);
                }
            }
    
            Console.WriteLine("Enter input:");
            string line = Console.ReadLine();
            if (line == "exit")
            {
                return;
            };
        }
    }

    public partial class Employee
    {
        public Employee()
        {
            this.Name = new Name();
        }
    
        public int EmployeeId { get; set; }
        public string Email { get; set; }
    
        public Name Name { get; set; }
    }

    public partial class Name
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public partial class EFRecipesEntities : DbContext
    {
        public EFRecipesEntities()
            : base("name=EFRecipesEntities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public DbSet<Employee> Employees { get; set; }
    }

代碼清單8-6的輸出以下:翻譯

Jordan, Bill email: BJordan@wildwestshow.com
Oakley, Annie email: aoakley@wildwestshow.com

 原理代理

  當你在POCO中使用值對象時,請記住下面兩條:

    一、值對象必須是一個類;

    二、繼承不能用於值對象;

  在實體框架中,值對象不能使用變化跟蹤。對值對象的修改,不能體如今變化跟蹤中。這注意着,若是你在一個值對象的屬性上將其標記爲virtual,也不會得到變化跟蹤代理的支持。全部的變化跟蹤都是基於快照的。

  當你使用值對象刪除一個還未從數據庫中加載的實體時,你須要建立一個值對象的實例。在實體框架中,值對象的實例是實體的一部分,它不支持null值。代碼清單8-7演示了一種處理刪除的方法。

代碼清單8-7. 刪除一個包含值對象的實體

int id = 0;
            using (var context = new EFRecipesEntities())
            {
                var emp = context.Employees.Where(e =>
                            e.Name.FirstName.StartsWith("Bill")).FirstOrDefault();
                id = emp.EmployeeId;
            }

            using (var context = new EFRecipesEntities())
            {
                var empDelete = new Employee
                {
                    EmployeeId = id,
                    Name = new Name
                    {
                        FirstName = string.Empty,
                        LastName = string.Empty
                    }
                };
                context.Employees.Attach(empDelete);
                context.Employees.Remove(empDelete);
                context.SaveChanges();
            }

            using (var context = new EFRecipesEntities())
            {
                foreach (var employee in
                         context.Employees.OrderBy(e => e.Name.LastName))
                {
                    Console.WriteLine("{0}, {1} email: {2}",
                                       employee.Name.LastName,
                                       employee.Name.FirstName,
                                       employee.Email);
                }
            }

  在代碼清單8-7中,咱們首先查找到Bill Jordan的EmployeeId。由於咱們要演示,刪除事先沒有加載到上下文對象中的Bill,因此,咱們建立了一個新的上下文對象,演示經過給定Bill的EmployeeId來刪除他。咱們須要建立一個Employee實體的實例。這是由於Name屬性不能爲空,給FirstName和LastName設置了什麼值沒關係。咱們經過給值對象屬性賦值一個Name類型的實例(Dummy)來知足這個要求。當咱們調用了方法Attach(),Remove()和SaveChanges()後,就會刪除這個實體。

 

8-5  對象變動通知

問題

  你正在使用POCO,在你的對象發生改變時,你想獲得實體框架和對象狀態管理的通知。

解決方案

  假設你有如圖8-6所示的模型。

圖8-6. 一個包含實體donor和donation的模型

  這個模型表示捐款人和他們的捐款。由於有一些捐款是匿名的,因此donor和donation之間的關係是0..1 to *。

  咱們想修改實體,好比,將一個donation從一個donor移動到另外一個donor,同時獲得實體框架和對象管理器關於這些變更的通知。另外,咱們想實體框架憑藉這些通知,修正被變更影響了的關係。 在示例中,若是修改了捐款項對應的捐款人,咱們但願實體框架能修正兩邊的關係。代碼清單8-8對此進行了演示。

  代碼清單8-8的關鍵部分是,咱們將全部的屬性都標記爲virtual,設置每一個集合的類型爲ICollection<T>。這樣作,主要是容許實體框架爲每個POCO實體建立一個代理,在代理類中實現變化跟蹤。當咱們建立一個POCO實體的實例時,實體框架會動態地建立一個派生至實體的類,這個類充當實體的代理。這個代理重寫了實體中標記爲virtual的屬性,增長了一些勾子。當屬性被訪問時,這些勾子會自動地執行。這項技術被用來實現延遲加載和對象變化跟蹤。注意實體框架不會爲讓代理什麼也不作的實體生成代理。這句話的意思是,你能夠將實體設置爲 sealed 或者不包含virtual標記的屬性,這樣就能夠避免代理的生成。

代碼清單8-8. 將全部的屬性都標記爲virtual,設置每一個集合的類型爲ICollection<T>,以此獲取代理類的變化跟蹤功能

 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                 var donation = context.Donations.Create();
13                 donation.Amount = 5000M;
14 
15                 var donor1 = context.Donors.Create();
16                 donor1.Name = "Jill Rosenberg";
17                 var donor2 = context.Donors.Create();
18                 donor2.Name = "Robert Hewitt";
19 
20                 //把捐款歸給jill,並保存
21                 donor1.Donations.Add(donation);
22                 context.Donors.Add(donor1);
23                 context.Donors.Add(donor2);
24                 context.SaveChanges();
25 
26                 // 如今把捐款歸給Rebert
27                 donation.Donor = donor2;
28 
29                 // 報告
30                 foreach (var donor in context.Donors)
31                 {
32                     Console.WriteLine("{0} has given {1} donation(s)", donor.Name,
33                                        donor.Donations.Count().ToString());
34                 }
35                 Console.WriteLine("Original Donor Id: {0}",
36                     context.Entry(donation).OriginalValues["DonorId"]);
37                 Console.WriteLine("Current Donor Id: {0}",
38                                    context.Entry(donation).CurrentValues["DonorId"]);
39             }
40         }
41     }
42     public partial class Donation
43     {
44         public int DonationId { get; set; }
45         public Nullable<int> DonorId { get; set; }
46         public decimal Amount { get; set; }
47     
48         public virtual Donor Donor { get; set; }
49     }
50     public partial class Donor
51     {
52         public Donor()
53         {
54             this.Donations = new HashSet<Donation>();
55         }
56     
57         public int DonorId { get; set; }
58         public string Name { get; set; }
59     
60         public virtual ICollection<Donation> Donations { get; set; }
61     }
62     public partial class EFRecipesEntities : DbContext
63     {
64         public EFRecipesEntities()
65             : base("name=EFRecipesEntities")
66         {
67         }
68     
69         protected override void OnModelCreating(DbModelBuilder modelBuilder)
70         {
71             throw new UnintentionalCodeFirstException();
72         }
73     
74         public DbSet<Donation> Donations { get; set; }
75         public DbSet<Donor> Donors { get; set; }
76     }

代碼清單8-8輸出以下:

Jill Rosenberg has given 0 donation(s)
Robert Hewitt has given 1 donation(s)
Original Donor Id: 1
Current Donor Id: 2

原理

  做爲默認方式,實體框架使用基於快照的方法來檢測POCO實體的變動。若是你在POCO實體中更改一小點代碼。實體框架建立的變化跟蹤代理都能讓上下文保持同步。

  變化跟蹤給咱們帶來了兩點好處,一個是實體框架獲得變動通知,它能保持對象圖的狀態信息和你的POCO實體同步。意思是說,使用基於快照的方法,不須要花時間來檢查變動。

  另外一點是,當實體框架獲得處於關係兩邊實體中一邊的變動通知時,若是有須要,它能反映到關係的另外一邊。在代碼清單8-8中,咱們將捐款項從一個捐款人移動到另外一個捐款人,實體框架能修正兩個捐款人的捐款項集合。

  實體框架爲POCO實體類生成變化跟蹤的代理須要知足以下條件。

    一、類必須是Public的,不是abstract類,不是sealed類;

    二、須要持久化的屬性必須是virtual標記的,且實現了getter和setter;

    三、你必須將基於集合的導航屬性的類型設爲ICollection<T>,它們不能是一個具體的實現類,也不能是另外一個派生至ICollection<T>的接口;

  一旦你的POCO實體知足這些要求,實體框架就會爲你的POCO實體返回一個代理實例。若是須要建立一個實例,你須要像代碼清單8-8那樣使用DbContext中的Create()方法。這個方法建立一個POCO實體的實例,而且,它會把全部的集合初始化爲EntityCollection的實例。把POCO實體的集合做爲Entitycollection的實例,這是由於它能修正關係。

 


 

實體框架交流QQ羣:  458326058,歡迎有興趣的朋友加入一塊兒交流

謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/VolcanoCloud/

相關文章
相關標籤/搜索