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这个 ...
随机推荐
- Oracle 11g client的安装和配置。
数据库和client在不同的机器之上. 在安装之前,在安装Oracle数据库的server上导航到以下的文件夹. 将listener.ora和tnsnames.ora中的host中的localhost ...
- vector中的resize与 reserve
void reserve (size_type n); reserver函数用来给vector预分配存储区大小,即capacity的值 ,但是没有给这段内存进行初始化.reserve 的参数n是推荐预 ...
- bootstrap系列整理
去年的九月份做第一版文档站时, 就开始尝试使用bootstrap ,由于当时对node 还一知半解,大部分精力放在nodejs上,bootstrap只是拿来就用,起步文档都没看: 当别人提到 Norm ...
- hdu 1587 Flowers
Flowers Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Su ...
- 异步任务(AsyncTask)
Android的UI线程主要负责处理用户的按键事件.用户触屏事件及屏幕绘图事件等,因此开发者的其他操作不应该.也不能阻塞UI线程,否则UI界面将会变得停止响应——用户感觉非常糟糕.(总之,开发者需要牢 ...
- js数组&&字符串&&定时器2
一.系统时间对象Date 方法 描述 Date() 返回当日的日期和时间. getDate() 从 Date 对象返回一个月中的某一天 (1 ~ 31). getDay() 从 Date 对象返回一周 ...
- 在List中找出最大值的两种方法
先说需求:找出一个对象List中,某个属性值最大的对象. 1.定义对象 private class A { public int ID { get; set; } public string Name ...
- 在 ServiceModel 客户端配置部分中,找不到引用协定“PmWs.PmWebServiceSoap”的默认终结点元素
System.Exception: ConfigManager.LoadConfigurationFromDb ServiceFactory.GetPmWebService 在 ServiceMode ...
- 第17条:实现description方法
自定义类需要自己覆写description方法,否则打印信息时就会调用NSObject类所实现的默认方法.(如果不覆写,只会输出类名和对象的内存地址的信息,这只有在判断两个指针是否指向同一对象时才有用 ...
- cinder
source /root/openrc 显示云硬盘: cinder list 这只是查看了admin租户下的,要查看所有租户下的云硬盘: cinder list --all-tenant 后台手动强行 ...