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

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

6-11  TPH继承映射中使用复合条件

问题

  你想使用TPH为一张表建模,建模中使用的复杂条件超过了实框架能直接支持的能力。

解决方案

  假设我们有一张Member表,如图6-15所示。Member表描述了我们俱乐部的会员信息。在我们的模型中,我们想使用TPH为派生类,AdultMember(成人会员)、SeniorMember(老年人会员)和TeenMember(青少年会员)建模。

图6-15  描述俱乐部会员的Member表

  实体框架支持TPH继承映射的条件有=、is null和is not null。像<、between、和>这样简单的表达式都不能支持。我们现在的情况是, 一个会员的年龄少于20就是一位青少年会员(我们俱乐部会员最小的年龄为13)。一个会员的年龄在20到55之间就是成年会员。正如你预料的那样,如果年龄超过55就是一位老年会员。按下面的步骤为Member表和三个派生类型建模:

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

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

    3、使用代码清单6-31中的代码创建存储过程。我们将使用它处理继承至Member实体的insert,update和delete动作;

代码清单6-31. Insert,Update和Delete动作使用的存储过程

 1 create procedure [Chapter6].[InsertMember]
 2 (@Name varchar(50), @Phone varchar(50), @Age int)
 3 as
 4 begin
 5     insert into Chapter6.Member (Name, Phone, Age) 
 6     values (@Name,@Phone,@Age)
 7     select SCOPE_IDENTITY() as MemberId
 8 end
 9 
10 create procedure [Chapter6].[UpdateMember]
11 (@Name varchar(50), @Phone varchar(50), @Age int, @MemberId int)
12 as
13 begin
14     update Chapter6.Member set Name=@Name, Phone=@Phone, Age=@Age
15     where MemberId = @MemberId
16 end
17 
18 
19 create procedure [Chapter6].[DeleteMember]
20 (@MemberId int)
21 as
22 begin
23     delete from Chapter6.Member where MemberId = @MemberId
24 end

    4、右键设计器,并选择Update Model from Database(从数据库更新模型)。在更新向导中,选择上一步创建的三个存储过程;

    5、右键设计器,并选择Add(新增)➤Entity(实体)。命名新实体为Teen,并设置它的基类为Member。重复这一步,创建另外两个派生类型Adult和Senior;

    6、选择Member实体,并查看Mapping Details window(映射详细信息窗口).单击映射到Member,并选择<Delete>,这会删除Member表的映射;

   

    7、选择Teen实体,并查看Mapping Details window(映射详细信息窗口)。单击Map Entity to Function(映射实体到函数)按钮。这个按钮是在映射详细信息窗口左边最下边的一个按钮。映射Insert、Update和Delete动作到存储过程。prperty/parameter(属性/参数)映射会自动填入。然而,存储过程InsertMember的返回值必须映射到MemberId属性。这是实体框架用于在插入操作后获取标识列MemberId值的途径。正确映射如图6-16所示。

图6-16  为Teen实体映射插入、更新和删除动作

    8、为实体Adult和Senior重复上一步操作;

  在解决方案浏览器中右键.edmx文件,选择Open With(打开方式) ➤XML Editor(XML文本编辑器),这将会在XML文本编辑器中打开.edmx文件。

    9、在C-S映射一节,将代码清单6-32中的EntitySetMapping插入到标签<EntityContainerMapping>中。

代码清单6-32.映射Member表到派生类型Teen、Adult和Senior的QueryView

 1   <EntitySetMapping Name="Members">
 2             <QueryView>
 3               select value
 4               case
 5               when m.Age &lt; 20 then
 6               Apress.EF6Recipes.BeyondModelingBasics.Recipe11.Teen(m.MemberId,m.Name,m.Phone,m.Age)
 7               when m.Age between 20 and 55 then
 8               Apress.EF6Recipes.BeyondModelingBasics.Recipe11.Adult(m.MemberId,m.Name,m.Phone,m.Age)
 9               when m.Age > 55 then
10               Apress.EF6Recipes.BeyondModelingBasics.Recipe11.Senior(m.MemberId,m.Name,m.Phone,m.Age)
11               end
12               from ApressEF6RecipesBeyondModelingBasicsRecipe11StoreContainer.Member as m
13             </QueryView>
14   </EntitySetMapping>

最终的模型如图6-17所示。

图6-17. Member和他的派生类型Senior,Adult和Teen的最终模型

 

原理 

  当使用TPH继承映射建模时,实体框架只支持有限的条件集。在本节中,我们通过QueryView定义了Member表和派生类型:Senior,Adult,和Teen的映射,扩展了实体框架支持的条件。如代码清单6-32所示。

  不幸的是,使用QueryView也会付出代价。因为我得自己定义映射,还有担起了实现派生类型插入、更新和删除动作的责任。这在我们的示例中还不算困难。

  在代码清单6-31中,我们定义了存储过程来处理插入、删除和更新。我们只需要创建一个实现集,这是因为这些动作的目标均是底层的Member表。在本节中,我们在数据库中使用存储过程来实现这些动作。我们还可以在.edmx文件中实现。

  在设计器中,我们将存储过程映射到每个派生类型的Insert,Update和Delete动作上。这完成了在使用QueryView时需要的额外工作。

  代码清单6-33演示了,从模型中插入和获取数据。在这里,我们为每个派生类型插入一个实例。在获取时,我们一起打印了会员的电话号码,青少年会员除外。

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

 1 using (var context = new Recipe11Context())
 2             {
 3                 var teen = new Teen
 4                 {
 5                     Name = "Steven Keller",
 6                     Age = 17,
 7                     Phone = "817 867-5309"
 8                 };
 9                 var adult = new Adult
10                 {
11                     Name = "Margret Jones",
12                     Age = 53,
13                     Phone = "913 294-6059"
14                 };
15                 var senior = new Senior
16                 {
17                     Name = "Roland Park",
18                     Age = 71,
19                     Phone = "816 353-4458"
20                 };
21                 context.Members.Add(teen);
22                 context.Members.Add(adult);
23                 context.Members.Add(senior);
24                 context.SaveChanges();
25             }
26 
27             using (var context = new Recipe11Context())
28             {
29                 Console.WriteLine("Club Members");
30                 Console.WriteLine("============");
31                 foreach (var member in context.Members)
32                 {
33                     bool printPhone = true;
34                     string str = string.Empty;
35                     if (member is Teen)
36                     {
37                         str = " a Teen";
38                         printPhone = false;
39                     }
40                     else if (member is Adult)
41                         str = "an Adult";
42                     else if (member is Senior)
43                         str = "a Senior";
44                     Console.WriteLine("{0} is {1} member, phone: {2}", member.Name,
45                                        str, printPhone ? member.Phone : "unavailable");
46                 }
47             }

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

Members of our club
===================
Steven Keller is a Teen member, phone: unavailable
Margret Jones is an Adult member, phone: 913 294-6059
Roland Park is a Senior member, phone: 816 353-4458

  这里需要注意的是,没有设计时,或者运行时检查,来验证派生类型的年龄。这样的话,完全有可能创建一个Teen的实例,设置他的年龄为74---很明显,这不是一个青少年会员。在查询时,它却被实现化为一个老年会员---这可能会得罪我们的青少年会员 。

  我们能在修改被提交到数据库前引入验证。为了实现这个操作,我们在创建上下对象时注册SavingChanges事件。并在这个事件中执行验证。如代码清单6-34所示。

代码清单6-34.在SavingChanges事件中处理验证

 1  public partial class Recipe11Context
 2     {
 3         public override int SaveChanges()
 4         {
 5             //译注:书使用的是下面注释的这句,因为这是原书的第二版,
 6             //书中的代码很可能是第一版时的,没被更新而遗留下来的。
 7             //this.SavingChanges += new EventHandler(Validate)  
 8             Validate();            
 9             return base.SaveChanges();
10         }
11 
12         public void Validate()
13         {
14             var entities = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager
15                                 .GetObjectStateEntries(EntityState.Added |
16                                                         EntityState.Modified)
17                                 .Select(et => et.Entity as Member);
18             foreach (var member in entities)
19             {
20                 if (member is Teen && member.Age > 19)
21                 {
22                     throw new ApplicationException("Entity validation failed");
23                 }
24                 else if (member is Adult && (member.Age < 20 || member.Age >= 55))
25                 {
26                     throw new ApplicationException("Entity validation failed");
27                 }
28                 else if (member is Senior && member.Age < 55)
29                 {
30                     throw new ApplicationException("Entity validation failed");
31                 }
32             }
33         }
34 
35     }

  在代码清单6-34中,当调用SaveChanges()方法时,我们的Validate()方法将检查每个新增或修改的对象。对于每一个实体的实例,我们验证它的属性Age是否和它的类型对应。当发现一个验证错误时,我们简单地抛出一个异常。

  在第十二章,我们将用几个小节来介绍,更改被提交到数据库前的事件处理和验证。


 

 

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

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

 

posted @ 2015-06-01 15:07  china_fucan  阅读(1181)  评论(4编辑  收藏  举报