Entity Framework Code First 映射继承关系
转载 http://www.th7.cn/Program/net/201301/122153.shtml
Code First如何处理类之间的继承关系。Entity Framework Code First有三种处理类之间继承关系的方法,我们将逐一介绍这三种处理方法。
1.Table Per Hierarchy(TPH): 只建立一个表,把基类和子类中的所有属性都映射为表中的列。
2.Table Per Type(TPT): 为基类和每个子类建立一个表,每个与子类对应的表中只包含子类特有的属性对应的列。
3.Table Per Concrete Type(TPC):为每个子类建立一个表,每个与子类对应的表中包含基类的属性对应的列和子类特有属性对应的列。
1.Table Per Hierarchy(TPH)
在这种处理方式中,Entity Framework Code First为基类和所有子类建立一个表,基类和子类中的所有属性都映射为表中的一个列。Entity Framework Code First默认在这个表中建立一个叫做Discriminator的列,类型是nvarchar,长度是128。Entity Framework Code First会在存储基类或子类的时候,把类名作为Discriminator列的值。
在我们前面的示例程序中,由于我们要记录订单是被谁创建的,以及是被谁批准的,我们新增了一个SalesPerson类。
public class SalesPerson {
public string EmployeeID { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public DateTime HiredDate { get; set; }
}
并且在Order类中增加了两个SalesPerson的实例用于记录订单的创建人和批准人。
public SalesPerson CreatedBy { get; set; }public SalesPerson ApprovedBy { get; set; }
我们后来细化了我们的业务流程:订单是由销售员创建的;当客户要求的订单折扣过高时,需要销售经理的审批;经理每个月都有固定的折扣审批总额。销售员和销售经理都属于销售人员。这是一个典型的继承关系。

根据我们细化之后的业务流程,我们创建了两个新的类, SalesMan和SalesManager
public class SalesMan : SalesPerson {
public decimal DiscountLimit { get; set; }
}
public class SalesManager : SalesPerson {
public decimal DiscountAmountPerMonth { get; set; }
}
由于创建订单的时候涉及到了复杂的业务逻辑,需要为订单指定Customer和SalesMan, 我们新建了一个factory类用于创建订单。
public static class OrderFactory{
public static Order CreateNewOrder(Customer customer, SalesMan createUser) { Order order = new Order();
order.Customer = customer;
order.CreatedDate = DateTime.Now;
order.CreatedBy = createUser;
order.ApprovedBy = null;
return order; }
}
我们新建一个单元测试方法用于测试我们新的销售人员继承关系以及新的订单factory类。
[TestMethod]
public void CanAddOrderWithSalesMan() {
OrderSystemContext unitOfWork = new OrderSystemContext(); ProductRepository productRepository = new ProductRepository(unitOfWork); OrderRepository orderRepository = new OrderRepository(unitOfWork); CustomerRepository customerRepository = new CustomerRepository(unitOfWork); SalesMan salesman = new SalesMan {
EmployeeID = "2012001",
Gender = "M",
Name = "Eric",
HiredDate = DateTime.Parse("2010-5-19") };
Customer customer = customerRepository.GetCustomerById("120104198403082113");
Order order = OrderFactory.CreateNewOrder(customer, salesman); order.AddNewOrderItem(productRepository.GetProductCatalogById(1).GetProductInStock(), 5100);
orderRepository.AddNewOrder(order);
unitOfWork.CommitChanges(); }
执行完我们的测试程序之后,我们可以打开SQL Server去看一下Code First默认情况下是如何处理类之间的继承关系的。

Code First默认会把基类和子类的所有属性都映射成一个表中的列,并且会增加一个Discriminator列标识存进去的是哪个类的实例。

如果你不喜欢Discriminator这个有点奇怪的名字,你可以自己定义Discriminator列的名字以及它的类型。我们使用map方法定义该列的名字和类型。我们可以将它命名为Title。
public class SalesPersonValueObjectConfiguration: EntityTypeConfiguration<SalesPerson> {
public SalesPersonValueObjectConfiguration()
{ HasKey(p => p.EmployeeID).Property(p => p.EmployeeID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); Property(p => p.Name).IsRequired().HasMaxLength(100);
Property(p => p.Gender).IsRequired().HasMaxLength(1);
Map<SalesMan>(salesman => { salesman.Requires("Title").HasValue("SalesMan"); });
Map<SalesManager>(manager => { manager.Requires("Title").HasValue("Sales Manager"); }); } }
Map方法中传入的类型参数是子类的类名,Requires用于指定Discriminator列的名字,HasValue用于指定它的类型和每个子类对应的值。
我们可以重新执行我们的测试程序,然后打开SQL Server,去看一下新建的数据库表结构。


这个列的类型不仅可以是字符串,还可以是bit标志位,比如说我们把区分salesman和salemanager的列设为bit型,列的名字叫做IsManager.
Map<SalesMan>(salesman => { salesman.Requires("IsManager").HasValue(false); });
Map<SalesManager>(manager => { manager.Requires("IsManager").HasValue(true); });
我们只需要把HasValue中传入的值变为true和false,Code First会自动把IsManager列的类型设置为bit。


2.Table Per Type(TPT)
在这种处理方式中,Entity Framework Code First会为每个基类和子类建立一个表,子类的表中只包含子类特有的属性。
我们可以使用Map方法强制让Code First使用TPT方式,因为Code First默认使用的是TPC方式。
public class SalesPersonValueObjectConfiguration: EntityTypeConfiguration<SalesPerson> { public SalesPersonValueObjectConfiguration() { HasKey(p => p.EmployeeID).Property(p => p.EmployeeID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.Name).IsRequired().HasMaxLength(100);
Property(p => p.Gender).IsRequired().HasMaxLength(1);
Map<SalesMan>(salesman => { salesman.ToTable("SalesMan"); }); Map<SalesManager>(manager => { manager.ToTable("Manager"); }); }
}
我们通过使用ToTable方法,让Code First为每个子类型建立一个表,表的名字就是ToTable方法中传入的参数值,子类对应的表中的主键与基类对应的表中的主键名字相同,同时它还是指向基类对应的表的外键。
我们还使用上面的那个测试方法来测试一下Code First按照TPT的方式建立的数据表结构。

3.Table Per Concrete Type(TPC)
在这种处理方式中,Entity Framework Code First为每一个子类建立一个表,在子类对应的表中除了子类特有的属性外还有基类的属性对应的表。
和TPT一样,我们也需要通过Map方法进行设置。
public class SalesPersonValueObjectConfiguration: EntityTypeConfiguration<SalesPerson> {
public SalesPersonValueObjectConfiguration() {
HasKey(p => p.EmployeeID).Property(p => p.EmployeeID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.Name).IsRequired().HasMaxLength(100);
Property(p => p.Gender).IsRequired().HasMaxLength(1);
Map<SalesMan>(salesman => { salesman.ToTable("SalesMan");
salesman.MapInheritedProperties(); });
Map<SalesManager>(manager => { manager.ToTable("Manager"); manager.MapInheritedProperties(); }); } }
通过MapInheritedProperties方法就可以强制Code First使用TPC方式。
我们重新编译之后执行我们原来的测试方法,可以得到不同的数据表结构,Code First不会为基类建立表,而是为每个子类都建立一个表,将子类的内容和基类的内容都存储到各个子类对应的表中。

PS:如果你的基类是abstract,效果也是一样的。
最后需要探讨的一个问题是我们在实际项目中应该使用哪种方式呢?
1.不推荐使用TPC(Type Per Concrete Type),因为在TPC方式中子类中包含的其他类的实例或实例集合不能被映射为表之间的关系。你必须通过手动地在类中添加依赖类的主键属性,从而让Code First感知到它们之间的关系,而这种方式是和使用Code First的初衷相反的。
2.从查询性能上来说,TPH会好一些,因为所有的数据都存在一个表中,不需要在数据查询时使用join。
3.从存储空间上来说,TPT会好一些,因为使用TPH时所有的列都在一个表中,而表中的记录不可能使用所有的列,于是有很多列的值是null,浪费了很多存储空间。
4.从数据验证的角度来说,TPT好一些,因为TPH中很多子类属性对应的列是可为空的,就为数据验证增加了复杂性。
Entity Framework Code First 映射继承关系的更多相关文章
- Entity Framework Code First关系映射约定
本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...
- Entity Framework Code First主外键关系映射约定
本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...
- Entity Framework Code First关系映射约定【l转发】
本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...
- Entity Framework Code First属性映射约定
Entity Framework Code First与数据表之间的映射方式有两种实现:Data Annotation和Fluent API.本文中采用创建Product类为例来说明tity Fram ...
- 补习知识:Entity Framework Code First属性映射约定
Entity Framework Code First与数据表之间的映射方式有两种实现:Data Annotation和Fluent API.本文中采用创建Product类为例来说明tity Fram ...
- Entity Framework Code First属性映射约定 转载https://www.cnblogs.com/libingql/p/3352058.html
Entity Framework Code First属性映射约定 Entity Framework Code First与数据表之间的映射方式有两种实现:Data Annotation和Flue ...
- MVC使用Entity Framework Code First,用漂亮表格显示1对多关系
部门和职员是1对多关系.用一个表格列出所有部门,并且在每行显示该部门下的所有职员名称.如下: 部门和职员的Model: using System.Collections.Generic; namesp ...
- 使用 Entity Framework Code First
使用 Entity Framework Code First 在家闲着也是闲着,继续写我的[ASP.NET MVC 小牛之路]系列吧.在该系列的上一篇博文中,在显示书本信息列表的时候,我们是在程序代码 ...
- Entity Framework Code first(转载)
一.Entity Framework Code first(代码优先)使用过程 1.1Entity Framework 代码优先简介 不得不提Entity Framework Code First这个 ...
随机推荐
- AndroidPN中的心跳检测
在AndroidPN客户端里存在着心跳检测功能.就是每隔一段时间客户端向服务器端发送一个消息,以检测连接是否正常,发送的消息内容为: <presence id="h09Ke-13&qu ...
- 设置tomcat内存
设置Tomcat启动的初始内存其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4. 可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置 三.实例,以下给 ...
- [GIF] Shape Objects in GIF Loop Coder
This lesson is a quick tour of the predefined shape objects in GIF Loop Coder. function onGLC(glc) { ...
- C、Shell、Perl基于Tomcat开发CGI程序环境配置
基于Tomcat7.0版本号配置CGI开发环境,步聚例如以下: 以我的Tomcat7安装文件夹为例:TOMCA_HOME = /Users/yangxin/Documents/devToos/java ...
- flex脚本的申明
//脚本申明的格式 <fx:Script> <![CDATA[ ]]></fx:Script> //程序完成的时候自动调用的事件 cre ...
- 关于调用系统照相机Activity被销毁问题解决
<activity android:name=".visitplan_finish" android:co ...
- JQuery对象与DOM对象分析
一.定义: DOM对象(文档对象模型):暂时这么理解:通过JavaScript获取的HTML元素,称为DOM对象.如:var domID=document.getElementById("i ...
- Javaweb入门20160301 ---xml入门
一.xml语法 1.文档声明 用来声明xml的基本属性,用来指挥解析引擎如何去解析当前xml 通常一个xml都要包含并且只能包含一个文档声明 xml的文档必须在整个xml的最前面,在文档声明之前不能有 ...
- J2EE、J2SE、J2ME
http://developer.51cto.com/art/200906/130453.htm 本文介绍Java的三大块:J2EE.J2SE和J2ME.J2SE就是Java2的标准版,主要用于桌面应 ...
- dbms_job和dbmsi_job
工作中可能遇到这样的情况,在A用户下有一个不用的job,但是dba不知道A用户的密码,怎么删除这个job呢. 相信大部分人都会尝试在sys用户下用dbms_job.remove()命令去删除它,但 ...