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


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

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

問題

  你想在POCO中使用值對象。

解決方案

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

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

 

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

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

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中使用值對象時,請記住下面兩條:

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

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

  在實體框架中,值對象不能使用變化跟蹤。對值對象的修改,不能體現在變化跟蹤中。這注意着,如果你在一個值對象的屬性上將其標記為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實體類生成變化跟蹤的代理需要滿足如下條件。

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

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

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

  一旦你的POCO實體滿足這些要求,實體框架就會為你的POCO實體返回一個代理實例。如果需要創建一個實例,你需要像代碼清單8-8那樣使用DbContext中的Create()方法。這個方法創建一個POCO實體的實例,並且,它會把所有的集合初始化為EntityCollection的實例。把POCO實體的集合作為Entitycollection的實例,這是因為它能修正關系。

 


 

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

謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM