《Entity Framework 6 Recipes》中文翻译系列 (32) ------ 第六章 继承与建模高级应用之TPH与TPT (1)

翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇

6-6  映射派生类中的NULL条件

问题

  你的表中,有一列允许为null。你想使用TPH创建一个模型,列值为null时,表示一个派生类型,不为null时,表示另一个派生类型。

解决方案

  假设你有一张表,描述医学实验药物。这张表包含一列指示该药是什么时候批准生产的。药在批准生产之前都被认为是实验性的。一但批准生产,它就被认为是药物了。我们就以图6-7中Drug表开始我们这一小节的学习。

图6-7 Drug表中有一列可为null鉴别列,AcceptedDate

  按下面的步骤为Drug表建模:

    1、创建一个派生至DbContext的上下文对象Recipe6Context;

    2、创建POCO实体类Drug、Medicine和Experimental,如代码清单6-17所示;

代码清单6-17. 创建POCO实体Drug、Medicine和Experimental

 1  [Table("Drug", Schema = "Chapter6")]
 2     public abstract class Drug
 3     {
 4         [Key]
 5         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
 6         public int DrugId { get; set; }
 7         public string Name { get; set; }
 8     }
 9
10     public class Experimental : Drug
11     {
12         public string PrincipalResearcher { get; set; }
13
14         public void PromoteToMedicine(DateTime acceptedDate, decimal targetPrice,
15                                   string marketingName)
16         {
17             var drug = new Medicine { DrugId = this.DrugId };
18             using (var context = new Recipe6Context())
19             {
20                 context.Drugs.Attach(drug);
21                 drug.AcceptedDate = acceptedDate;
22                 drug.TargetPrice = targetPrice;
23                 drug.Name = marketingName;
24                 context.SaveChanges();
25             }
26         }
27
28     }
29
30     public class Medicine : Drug
31     {
32         public decimal? TargetPrice { get; set; }
33         public DateTime AcceptedDate { get; set; }
34     }

    3、在上下文对象Recipe6Context中添加一个类型为DbSe<Drug>的属性;

    4、在上下文对象Recipe6Context中重写OnModelCreating方法,让它为临床用药(Medicine)和试验性药物(Experimental)类型配置TPH映射。如代码清单6-18所示;

代码清单6-18.重写OnModelCreating方法,使之配置TPH映射

1 protected override void OnModelCreating(DbModelBuilder modelBuilder)
2         {
3             base.OnModelCreating(modelBuilder);
4             modelBuilder.Entity<Experimental>()
5                         .Map(m => m.Requires("AcceptedDate").HasValue((DateTime?)null));
6             modelBuilder.Entity<Medicine>()
7                         .Map(m => m.Requires(d => d.AcceptedDate).HasValue());
8         }

原理

  在示例中,我们使用null和非null作为条件分别映射,不包含AcceptedDate的试验性药物(Experimental)和包含AcceptedDate的临床用药(Medicine)。和多数继承的例子一样,我们创建一个抽象的基类实体,之所以为抽象,是因为在我们的模型中,不会使用一个未分类的药物。

  有趣的是,在Medicine实体中,我们将鉴别列映射到一个标题属性上。在绝大多数情况下,将鉴别列映射到一个标量属性是被禁止的。然而,在这个示例中,我们使用null和非null作为条件,不仅如此,AcceptedDate还设为不可空,这些对属性值的约束使得该属性可以被映射。

  在代码清单6-19中,我们插入了两个试验性药物,并查询出插入的数据。我们使用AcceptedDate属性为我们提供的机会,可以将一个对象从一个派生类型改变为另一个派生类型。在我们的示例中,我们创建了两个试验性药物,随后将他们中的一个提升为临床用药。

代码清单6-19.插入并获取我们的派生类型实例

 using (var context = new Recipe6Context())
            {
                var exDrug1 = new Experimental { Name = "Nanoxol",
                                       PrincipalResearcher = "Dr. Susan James" };
                var exDrug2 = new Experimental { Name = "Percosol",
                                       PrincipalResearcher = "Dr. Bill Minor" };
                context.Drugs.Add(exDrug1);
                context.Drugs.Add(exDrug2);
                context.SaveChanges();

                // Nanoxol刚获生产批准
                exDrug1.PromoteToMedicine(DateTime.Now, 19.99M, "Treatall");
                context.Entry(exDrug1).State = EntityState.Detached; //不在使用此实例
            }

            using (var context = new Recipe6Context())
            {
                Console.WriteLine("Experimental Drugs");
                foreach (var d in context.Drugs.OfType<Experimental>())
                {
                    Console.WriteLine("\t{0} ({1})", d.Name, d.PrincipalResearcher);
                }

                Console.WriteLine("Medicines");
                foreach (var d in context.Drugs.OfType<Medicine>())
                {
                    Console.WriteLine("\t{0} Retails for {1}", d.Name,
                                       d.TargetPrice.Value.ToString("C"));
                }
            }

代码清单6-19的输出如下:

Experimental Drugs
        Percosol (Dr. Bill Minor)
Medicines
        Treatall Retails for $19.99    

  我们使用PromoteToMedicine()方法将一个试验性药物(Experimental)提升为临床用药(Medicine)。在这个方法的实现中,我们创建了一个Medicine实例,将它附加到一个新的上下文中,并使用适当的值初始化它。一旦这个新的实例被初始化和附加,我们就调用SaveChanges()方法将它保存到数据库中,因为这个实例有一个和试验性药物相同的键(DrugId),实体框架会产生一个update(更新)语句,而不是insert(插入)语句

  我们在POCO类Experimental中实现了这个方法。这让我们可以无缝地在这样的类中添加一个方法,通过这种方式,提供了一种更简洁的实现 。话虽这么说,但我们感兴趣的是,创建一个透明持久化(persistence-ignorant)的POCO实体,能被用在多个上下文对象中,这让我们可以在helper类中实现一个稍微不同的版本。

6-7 使用一个非主键列建模TPT继承映射

问题

  在一个存在的架构中,你有一张或多张表,与一张共享表有一对一的关系,共享表中使用的键在表中不是主键,你想使用TPT对此建模。

解决方案

  假设你的数据库中包含的数据库关系图如图6-8所示。

图6-8 一个包含staff,Principal和Instructor表的关系对象图

  在图6-8中,我们一张职工(Staff)表,它包含员工的姓名(Name),两张包含校长(Principal)、教员(Instructor)信息的表。这里需要引起注意的是,表Principal和Instructor中的主键不是Staff表的外键。在这种类型的关系结构是不能直接使用TPT继承映射的。对于TPT,关联表的主键必须是主表(基表)的外键。同时,还要注意的是,关系是一对一。这是因为我们对Principal和Instructor表中StaffId列创建了唯一索引的约束。

  按下面的步骤,为图6-8中的表及其关系建模:

    1、在你的项目中添加一个ADO.NET Entity Data Model(ADO.NET实体数据模型),并导入表Staff,Principal,和Instructor;

    2、删除实体Principal与Staff之间的关联,实体Instructor与Staff之间的关联;

    3、右键Staff实体,选择Add(增加) ?Inheritance(继承)。选择Staff作为基类,Principal作为派生类。重复前面的操作,选择Staff作为基类,Instructor作为派生类型。

    4、从实体Instructor和Principal中删除属性StaffId;

    5、右键实体Staff,选择Properties(属性)。设置Abstract(抽象)属性为True。这会让实体Staff成为一个抽象类;

    6、因为StaffId在表Principal和Instructor中不是主键,所以我们不能使用默认表映射来映射实体Principal和Instructor,或者Staff。依次选择每个表,并在映射详细信息窗口删除表映射。

    7、使用代码清单6-20中的代码创建存储过程,我们会将这些存储过程映射到实体Principal和Instrucotr中的插入、更新和删除操作;

代码清单6-20. 实体Instructor和Principal插入、更新和删除动作的存储过程

 1 create procedure [chapter6].[InsertInstructor]
 2 (@Name varchar(50), @Salary decimal)
 3 as
 4 begin
 5 declare @staffid int
 6 insert into Chapter6.Staff(Name) values (@Name)
 7 set @staffid = SCOPE_IDENTITY()
 8 insert into Chapter6.Instructor(Salary,StaffId) values (@Salary,@staffid)
 9 select @staffid as StaffId,SCOPE_IDENTITY() as InstructorId
10 end
11 go
12 create procedure [chapter6].[UpdateInstructor]
13 (@Name varchar(50), @Salary decimal, @StaffId int, @InstructorId int)
14 as
15 begin
16 update Chapter6.Staff set Name = @Name where StaffId = @StaffId
17 update Chapter6.Instructor set Salary = @Salary where InstructorId = @InstructorId
18 end
19 go
20 create procedure [chapter6].[DeleteInstructor]
21 (@StaffId int)
22 as
23 begin
24 delete Chapter6.Staff where StaffId = @StaffId
25 delete Chapter6.Instructor where StaffId = @StaffId
26 end
27 go
28 create procedure [Chapter6].[InsertPrincipal]
29 (@Name varchar(50),@Salary decimal,@Bonus decimal)
30 as
31 begin
32 declare @staffid int
33 insert into Chapter6.Staff(Name) values (@Name)
34 set @staffid = SCOPE_IDENTITY()
35 insert into Chapter6.Principal(Salary,Bonus,StaffId) values
36 (@Salary,@Bonus,@staffid)
37 select @staffid as StaffId, SCOPE_IDENTITY() as PrincipalId
38 end
39 go
40 create procedure [Chapter6].[UpdatePrincipal]
41 (@Name varchar(50),@Salary decimal, @Bonus decimal, @StaffId int, @PrincipalId int)
42 as
43
44 begin
45 update Chapter6.Staff set Name = @Name where StaffId = @StaffId
46 update Chapter6.Principal set Salary = @Salary, Bonus = @Bonus where
47 PrincipalId = @PrincipalId
48 end
49 go
50 create procedure [Chapter6].[DeletePrincipal]
51 (@StaffId int)
52 as
53 begin
54 delete Chapter6.Staff where StaffId = @StaffId
55 delete Chapter6.Principal where StaffId = @StaffId
56 end

    8、右键设计器窗口,选择Update Model from Database(从数据库中更新模型)。添加第7步创建的存储过程;

    9、选择实体Principal,并查看Mapping Details window(映射详细信息窗口)。单击Map Entity to Function(映射实体到函数)按钮。这个按钮是在映射详细信息窗口左边最下边的一个按钮。映射Insert、Update和Delete动作到存储过程。确保映射插入动作的result columns(结果列)StaffId和PrincipalId(如图6-9)。

图6-9 为实体Principal映射Insert,Update和Delete动作

    10、在上实体Instructor重复第9步。确保映射插入动作的result columns(结果列)StaffId和PrincipalId。

  在解决方案浏览器中右键.edmx文件,选择Open With(打开方式) ?XML Editor(XML文本编辑器)。这将关闭设计器窗口并在XML编辑器中打开.edmx文件。滚动到映射怪中的标签<EntityContainerMapping>。将代码清单6-21中的查询视图(QueryView)插入到标签<EntitySetMapping>中。

 1             <EntitySetMapping Name="Staffs">
 2                 <QueryView>
 3                     select value
 4                     case
 5                     when (i.StaffId is not null) then
 6                     Apress.EF6Recipes.BeyondModelingBasics.Recipe7.Instructor(s.StaffId,s.Name,i.InstructorId,i.Salary)
 7                     when (p.StaffId is not null) then
 8                     Apress.EF6Recipes.BeyondModelingBasics.Recipe7.Principal(s.StaffId,s.Name,p.PrincipalId,p.Salary,p.Bonus)
 9                     END
10                     from ApressEF6RecipesBeyondModelingBasicsRecipe7StoreContainer.Staff as s
11                     left join ApressEF6RecipesBeyondModelingBasicsRecipe7StoreContainer.Instructor as i
12                     on s.StaffId = i.StaffId
13                     left join ApressEF6RecipesBeyondModelingBasicsRecipe7StoreContainer.Principal as p
14                     on s.StaffId = p.StaffId
15                 </QueryView>
16             </EntitySetMapping>

原理

  使用TPT继承映射,实体框架要求基类表中的外键是派生类中的主键。在我们的示例中,每个派生类表有自己独立的主键。

  为了创建TPT继承映射模型,在概念层,实体Principal和Instructor继承自实体Staff。接下来,我们删除导入表时创建的映射。然后我们使用一个QueryView表达式来创建一个新的映射。使用QueryView将Insert、Update和Delete动作放入我们的代码中。为了处理这些动作,我们在数据库中创建了额外的存储过程。

  我们使用QueryView将映射派生类中的标量属性到数据库表中。QueryView中的关键部分是case语句。这里有两种情况,我们有一个Principal和一个Instructor。如果Instructor的StaffId非null时,我们就得到一个Instructor实例;如果Principal的StaffId为null时,我们就得到一个Principal实例。表达式剩下的部分是,引入派生类表中的行。

  代码清单6-22插入一位校长和一位教员到数据库表中。

代码清单6-22. 从模型中插入和获取

 1 using (var context = new Recipe7Context())
 2             {
 3                 var principal = new Principal
 4                 {
 5                     Name = "Robbie Smith",
 6                     Bonus = 3500M,
 7                     Salary = 48000M
 8                 };
 9                 var instructor = new Instructor
10                 {
11                     Name = "Joan Carlson",
12                     Salary = 39000M
13                 };
14                 context.Staffs.Add(principal);
15                 context.Staffs.Add(instructor);
16                 context.SaveChanges();
17             }
18
19             using (var context = new Recipe7Context())
20             {
21                 Console.WriteLine("Principals");
22                 Console.WriteLine("==========");
23                 foreach (var p in context.Staffs.OfType<Principal>())
24                 {
25                     Console.WriteLine("\t{0}, Salary: {1:C}, Bonus: {2:C}",
26                                        p.Name, p.Salary,
27                                        p.Bonus);
28                 }
29                 Console.WriteLine("Instructors");
30                 Console.WriteLine("===========");
31                 foreach (var i in context.Staffs.OfType<Instructor>())
32                 {
33                     Console.WriteLine("\t{0}, Salary: {1:C}", i.Name, i.Salary);
34                 }
35             }

代码清单6-22的输出如下:

Principals
==========
        Robbie Smith, Salary: $48,000.00, Bonus: $3,500.00
Instructors
===========
        Joan Carlson, Salary: $39,000.00

实体框架交流QQ群:  458326058,欢迎有兴趣的朋友加入一起交流

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

时间: 2024-10-25 00:12:16

《Entity Framework 6 Recipes》中文翻译系列 (32) ------ 第六章 继承与建模高级应用之TPH与TPT (1)的相关文章

《Entity Framework 6 Recipes》中文翻译系列 (33) ------ 第六章 继承与建模高级应用之TPH与TPT (2)

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-8  嵌套的TPH建模 问题 你想使用超过一层的TPH继承映射为一张表建模. 解决方案 假设你有一张员工(Employee)表,它包含各种类型的员工,比如,钟点工,雇员.如图6-10所示. 图6-10 包含各种类型的员工表 Employee表包含钟点工,雇员,提成员工,这是雇员下面的一个子类型.按下面的步骤,使用派生类型HourlyEmployee,SalariedEmployee和Sa

《Entity Framework 6 Recipes》中文翻译系列 (35) ------ 第六章 继承与建模高级应用之TPH继承映射中使用复合条件

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-11  TPH继承映射中使用复合条件 问题 你想使用TPH为一张表建模,建模中使用的复杂条件超过了实框架能直接支持的能力. 解决方案 假设我们有一张Member表,如图6-15所示.Member表描述了我们俱乐部的会员信息.在我们的模型中,我们想使用TPH为派生类,AdultMember(成人会员).SeniorMember(老年人会员)和TeenMember(青少年会员)建模. 图6-

《Entity Framework 6 Recipes》中文翻译系列 (30) ------ 第六章 继承与建模高级应用之多对多关联

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第六章  继承与建模高级应用 现在,你应该对实体框架中基本的建模有了一定的了解,本章将帮助你解决许多常见的.复杂的建模问题,并解决你可能在现实中遇到的建模问题. 本章以多对多关系开始,这个类型的关系,无论是在现存系统还是新项目的建模中都非常普遍.接下来,我们会了解自引用关系,并探索获取嵌套对象图的各种策略.最后,本章以继承的高级建模和实体条件结束. 6-1  获取多对多关联中的链接表 问题

《Entity Framework 6 Recipes》中文翻译系列 (36) ------ 第六章 继承与建模高级应用之TPC继承映射

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-12  TPC继承映射建模 问题 你有两张或多张架构和数据类似的表,你想使用TPC继承映射为这些表建模. 解决方案 假设我们有如图6-18所示的表. 图6-18 表Toyota和BMW有相似的结构,它们可以成为派生至实体Car的派生类型 在图6-18中,表Toyota和BMW有相似的架构(Schema),并描述类似的数据.BMW表只多了额外的一列,它用一bit值来指示对应的实例是否具有避

《Entity Framework 6 Recipes》中文翻译系列 (34) ------ 第六章 继承与建模高级应用之多条件与QueryView

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-10  创建一个多条件过滤 问题 你想使用多个条件为实体过滤表中的行. 解决方案 假设你有一张处理网站订单的表,如图6-13所示. 图6-13 表WebOrder包含网站订单的信息 假设我们有这样一个业务需求,WebOrder中的实例为,2012年以后的,2010年到2012年之间未删除的,2010年以前的订单金额大于200美元的.这样的复杂过滤条件不能使用映射详细信息窗口中有限制的条件

《Entity Framework 6 Recipes》中文翻译系列 (37) ------ 第六章 继承与建模高级应用之独立关联与外键关联

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-13  在基类中应用条件 问题 你想从一个已存在的模型中的实体派生一个新的实体,允许基类被实例化. 解决方案 假设你有如图6-20所示的模型. 图6-20 包含Invoice实体的模型 这个模型只包含一个单独的实体Invoice(发货单).我们想从Invoice派生一个新的实体,它表示删除掉的发货单.这将允许我们以更清晰的业务逻辑来分别对有效的发货单和已删除掉的发货进行不同的操作.按下面

《Entity Framework 6 Recipes》中文翻译系列 (31) ------ 第六章 继承与建模高级应用之自引用关联

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-4  使用TPH建模自引用关系 问题 你有一张自引用的表,它代表数据库上不同类型但关联的对象.你想使用TPH为此表建模. 解决方案 假设你有一张如图6-5所示的表,它描述了关于人的事,人通常会有一个心中英雄,他最能激发自己.我们用一个指向Person表中的另一个行的引用来表示心中的英雄. 图6-5  包含不同角色的Person表 在现实中,每个人都会有一个角色,有的是消防员,有的是教师,

《Entity Framework 6 Recipes》中文翻译系列 (16) -----第三章 查询之左连接和在TPH中通过派生类排序

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-10应用左连接 问题 你想使用左外连接来合并两个实体的属性. 解决方案 假设你有如图3-11所示的模型. 图3-11 一个包含Product和TopSelling的模型 畅销产品有一个与之关联的TopSelling实体.当然,不是所有的产品都是畅销产品.这就是为什么关系为零或者1.当一个产品为畅销产品时,与之关联的topSelling实体包含一个用户评级.你想查找和呈现所有的产品,和与之

《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述 (转)

微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF版本更新太快,没人愿意去花时间翻译国外关于EF的书籍.使用Entity Framework开发已经有3年多了,但用得很肤浅,最近想深入学习,只好找来英文书<Entity Framework 6 Recipes>第二版,慢慢啃.首先需要说明的是,我英文不好,只是为了学习EF.把学习的过程写成博客,一