问题描述:
假设有一些表用于保存公共表的额外信息,现在想使用TPT继承对其建模。
解决方案:
假定现在有2个表关联到一个公共表,如下图所示:
Business表和Retail表及eCommerce表间都是一个1对零个或1个的关系,其中Business表是1端。关键的特点是Retail表和eCommerce表扩展了Business表中business的信息。
Business表包含一些任何business都有的公共属性,Retail表和eCommerce表都同它紧密相连。为了建模一个TPT继承,也就是说Retail实体类型和eCommerce实体类型都继承于Business基实体类型,需要实现以下步骤:
1、生成一个派生自DbContext类的类,需要引入System.Data.Entity名称空间,添加默认构造函数,并调用父类的构造函数base("name=connectionStringName")。connectionStringName是EF框架的连接字符串名称,在配置文件中可以找到,没有的话需要手动添加或使用EDM向导的代码优先方式创建。并重写父类的OnModelCreating方法。
2、生成一个Business POCO 实体类型。
[Table("Business",Schema ="Chapter2")] public class Business { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int BusinessId { get; set; } public string Name { get; set; } public string LicenseNumber { get; set; } }
3、生成一个eCommerce POCO 实体类型和Retail POCO 实体类型,它们都继承于Business POCO实体类型。
[Table("eCommerce",Schema ="Chapter2")] public class eCommerce:Business { public string URL { get; set; } } [Table("Retail",Schema ="Chapter2")] public class Retail : Business { public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string ZIPCode { get; set; } }
4、在DbContext子类中添加一个DbSet<Business>自动属性。
原理:
Retail表和eCommerce表是同Business表的1:0..1关系的0..1端。这意味着一个business实体可以没有任何额外的信息,也可以有额外的Retail信息或eCommerce信息。在OOP术语中,描述为一个基类型Business和2个派生类型Retail和eCommerce。
因为这是一个1:0..1的关系,所以在Retail表和eCommerce表中不存在这样的记录,它们在Business表中不存在相关的记录。在面向对象的术语中描述为,派生类的实例包含有基类的属性。派生类扩展基类属性的概念是继承的关键特性之一。在TPT继承中,每一个派生类都表示为一个单独的表。
using (var context = new EF6Recipes8Context()) { var business = new Business { Name = "Corner Dry Cleaning", LicenseNumber = "100x1" }; context.Businesses.Add(business); var retail = new Retail { Name = "Shop and Save", LicenseNumber = "200C", Address = "101 Main", City = "AnyTown", State = "TX", ZIPCode = "76106" }; context.Businesses.Add(retail); var web = new eCommerce { Name = "BuyNow.com", LicenseNumber = "300AB", URL = "www.buynow.com" }; context.Businesses.Add(web); context.SaveChanges(); } using (var context = new EF6Recipes8Context()) { Console.WriteLine("\n--- All Business ---"); foreach (var b in context.Businesses) { Console.WriteLine("{0} (#{1})", b.Name, b.LicenseNumber); } Console.WriteLine("\n---Retail Business ---"); foreach (var r in context.Businesses.OfType<Retail>()) { Console.WriteLine("{0} (#{1}", r.Name, r.LicenseNumber); Console.WriteLine("{0}", r.Address); Console.WriteLine("{0}, {1} {2}", r.City, r.State, r.ZIPCode); } Console.WriteLine("\n--- eCommerce Business ---"); foreach (var e in context.Businesses.OfType<eCommerce>()) { Console.WriteLine("{0} (#{1})", e.Name, e.LicenseNumber); Console.WriteLine("Online address is: {0}", e.URL); } }
上面的代码生成和实例化Business实体类型和2个派生类型,并使用context中的Business实体集合的Add()方法将其添加到context中。
在查询部分,为了访问所有的business实例,我们迭代整个business实体集合;为了获取派生类型,我们调用OfType方法过滤Business实体集合。
TPT是EF支持的3种继承模式中的一个。另外2个是TPH(Table per Hierarchy)和TPCT(Table per Concrete Type)。
TPT继承提供了很大的数据库灵活性,我们能够容易的添加表作为一个派生类型,并按照它们自己的方式添加到模型中。然而,每一个派生类型涉及额外的连接,这将导致性能下降。在实际的应用中,使用TPT时,我们已经看到显著的性能问题当一些派生类被建模时。
而TPH存储整个继承在单个表,它消除了TPT的连接,因此提供更好的性能,但更低的灵活性。
TPCT被EF运行时支持,而不是设计时。