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