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这个 ...
随机推荐
- Caused by: java.lang.NoClassDefFoundError: freemarker/cache/TemplateLoader
1.错误描写叙述 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -he ...
- STM32F030 IO口外部中断应用
//==文件exit.h============================================================ #ifndef __EXIT_H #define __ ...
- android104 帧动画,补间动画,属性动画
##帧动画FrameAnimation* 多张图片快速切换,形成动画效果* 帧动画使用xml定义 package com.itheima.frameanimation; import android. ...
- setvbuf
函数名: setvbuf 用 法: int setvbuf(FILE *stream, char *buf, int type, unsigned size); type : 期望缓冲区的类型: _I ...
- 使用CocoaPods遇到的几个坑,记录一下
最近使用pod的时候升级到1.0.0版本后遇到一些坑,整理一下 首先是CocoaPods报错:The dependency `` is not used in any concrete target ...
- 什么是aop?
这个命题其实是讲了的,但是之前没有做,发现一些面试会问到,结合自己之前的学习经历.简单把这个问题描述一下. aop是跟oop相对应的一个概念.分别是aspect oriented programmin ...
- 动态使用webservice,以及含有ref类型的参数的问题
public class WSHelper { /// < summary> /// 动态调用web服务 /// < /summary> /// < param name ...
- (转)使用OpenVPN的一些注意事项
原文地址:http://www.365mini.com/page/16.htm 本文介绍的只是OpenVPN连接或使用过程中的一些注意事项,如果你尚未下载安装OpenVPN,你可以点击查看OpenVP ...
- <video>和<audio>标签
一.<video>基本格式: <video width=" " heigh="" src=""> </vide ...
- Integer跟int的区别(备份回忆)
int与Integer的区别 int 是基本数据类型Integer是其包装类,注意是一个类.为什么要提供包装类呢???一是为了在各种类型间转化,通过各种方法的调用.否则 你无法直接通过变量转化.比如, ...