Entity Framework中实现继承的策略

在面向对象编程中,继承是一个重要的概念,它提供了代码复用的能力,这是软件设计原则中非常重要的实践之一。在类级别实现继承是相对直接的,但如何在数据库级别实现继承呢?例如,派生类包含了基类的所有属性,那么派生类的表是否也应该包含基类表的所有属性呢?本文将介绍在Entity Framework中处理继承的三种策略:表继承、类型继承和具体类型继承。

表继承(Table per Hierarchy)

表继承的概念是,对于一个继承层次结构,数据库中只创建一个表。例如,如果有一个基类A和一个从A派生的类B,那么在数据库中只为这个继承层次结构创建一个表。这意味着对于单个层次结构,只有一个表。以下是实现表继承的代码示例:

using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; namespace CodeFirst { public class Person { [Key] public int PersonId { get; set; } [Required] [MaxLength(10)] public string Name { get; set; } public string surname { get; set; } } public enum FriendType { Close, NotClose, FamilyFriend } public class Friend : Person { public Int32 Person { get; set; } public FriendType FriendType { get; set; } } public class TestContext : DbContext { public TestContext() : base("DBConnectionString") { } public DbSet Person { get; set; } } }

在这个例子中,通过继承Person类来实现一个层次结构。Entity Framework能够智能地实现表继承,而不需要任何外部指令。

在构造函数中传递连接字符串,连接字符串需要在web.config文件中配置。在类中,只初始化Person类,而不是Friend类。这是Entity Framework的智能之处。如果填充基类,那么相关的派生类也会自动配置在单个表中。以下是web.config文件中的连接字符串示例:

<connectionStrings> <add name="DBConnectionString" connectionString="Data Source=SERVERNAME;Initial Catalog=PersonDB;Integrated Security=true" providerName="System.Data.SqlClient"/> </connectionStrings>

一旦运行应用程序,就会看到数据库被创建,并且表被生成,如下所示:

如果仔细查看表结构,会看到一个额外的Discriminator列。为什么创建了额外的列?在代码中并没有实现它。Entity Framework智能地创建了这个字段来区分表。概念是这样的:Person对象和Friend对象都将保存在单个表中,那么如何区分它们呢?怎么知道特定行包含的数据是Person对象还是Friend对象。为了区分它们,引入了Discriminator列。以下是保存Person类对象的代码:

static void Main(string[] args) { using (var ctx = new TestContext()) { // Save Object of Person table. ctx.Person.Add(new Person { Name = "Sourav", surname = "Kayal" }); ctx.SaveChanges(); } }

查看表数据,可以看到"Person"已经保存在Discriminator列的值中,所以记录包含Person类的对象。

可能会想:是否可以更改默认的"Discriminator"列名称?或者更改它的值?是的,这是可能的。使用Fluent API可以做到这一点。以下是修改后的代码:

public class TestContext : DbContext { public TestContext() : base("DBConnectionString") { } public DbSet Person { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity().Map(m => m.Requires("ObjectType").HasValue("FriendType")); modelBuilder.Entity().Map(m => m.Requires("ObjectType").HasValue("PersonType")); } }

现在,当保存某些对象的数据时,例如Friend对象,它将保存值"Friend Type",而不是之前的"Friend"。让尝试使用以下代码保存数据:

static void Main(string[] args) { using (var ctx = new TestContext()) { ctx.Person.Add(new Friend { Name = "Foo", surname = "Bar", PersonId = 1, FriendType = FriendType.Close }); ctx.SaveChanges(); } }

查看数据库结构。看到"FriendType"已经保存在表中,因为保存了Friend类的对象。

现在已经学习了创建表和Discriminator列的概念,将学习如何从表中获取数据。要从这样的表中获取数据,可以使用多态查询和非多态查询。尝试两者:

多态查询 - 按照概念,它将在单个查询中提取所有相关的层次结构数据。这里提取与Person对象相关的所有数据。由于Friend是从Person派生的,Friend的信息也会自动提取。以下是示例代码:

using (var ctx = new TestContext()) { var data = from tab in ctx.Person select tab; List<Person> persons = data.ToList(); }

看到两行数据都从表中提取出来,所以得到了Friend和Person对象。

非多态查询 - 在这个查询中,将提取特定的列值。这里只提取"Friend"对象。

using (var ctx = new TestContext()) { // Get All Friend From Person Table var data = from tab in ctx.Person.OfType<Friend>() select tab; List<Friend> persons = data.ToList(); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485