• 《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/

  • 相关阅读:
    Dynamic导出解决方案修改其XML信息
    子网格
    官方文档
    ADFS登录页面自定义
    ADFS设置Tokn生命周期
    特征工程
    Pandas
    分类决策树
    Python基本知识
    机器学习的基本概念
  • 原文地址:https://www.cnblogs.com/VolcanoCloud/p/4547839.html
Copyright © 2020-2023  润新知