继承
EF可以将.net类型层次结构映射到数据库。这允许你像往常一样用代码编写你的.net实体,使用基类型和派生类型,并让EF无缝地创建适当的数据库模式,发出查询等。类型层次结构映射的实际细节依赖于提供程序;本页描述关系数据库上下文中的继承支持。
实体类型层次结构映射
按照约定,EF不会自动扫描基类或派生类类型;这意味着如果您想要映射层次结构中的CLR类型,您必须在您的模型上显式地指定该类型。例如,只指定层次结构的基类型不会导致EF Core隐式包含它的所有子类型。
下面的示例公开了一个用于Blog及其子类RssBlog的DbSet。如果Blog有任何其他子类,它将不包括在模型中:
class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<RssBlog> RssBlogs { get; set; } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class RssBlog : Blog { public string RssUrl { get; set; } }
当使用TPH映射时,数据库列将根据需要自动变为可为空。例如,RssUrl列是可空的,因为Blog实例没有这个属性。
如果不想为层次结构中的一个或多个实体公开DbSet,也可以使用Fluent API确保它们包含在模型中。如果您不依赖约定,您可以使用HasBaseType显式地指定基类型。还可以使用. hasbasetype ((Type)null)从层次结构中删除实体类型。
1、每个层次结构一个表(TPH)和鉴别器配置
默认情况下,EF使用分层表又叫‘每个层次结构一个表’(TPH)模式映射继承。TPH使用一个表来存储层次结构中所有类型的数据,并使用一个鉴别器列(Discriminator)来标识每行代表的类型。
上面的模型映射到以下数据库模式(注意隐式创建的Discriminator列,它标识每行中存储的Blog类型)。
可以配置鉴别器列的名称和类型以及用于标识层次结构中的每种类型的值:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator<string>("blog_type") .HasValue<Blog>("blog_base") .HasValue<RssBlog>("blog_rss"); }
在上述示例中,EF 在层次结构的基实体上将鉴别器隐式添加为 影子属性 。 此属性可以配置为类似于任何其他属性:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property("Discriminator") .HasMaxLength(200); }
最后,鉴别器还可以映射到实体中的常规 .NET 属性:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator(b => b.BlogType); modelBuilder.Entity<Blog>() .Property(e => e.BlogType) .HasMaxLength(200) .HasColumnName("blog_type"); }
在查询使用TPH模式的派生实体时,EF Core在查询中添加鉴别器列上的谓词。此筛选器确保我们不会在结果中为基类型或兄弟类型获得任何额外的行。对于基本实体类型,此筛选器谓词将被跳过,因为查询基本实体将获得层次结构中所有实体的结果。当从查询具体化结果时,如果我们遇到一个discriminator值,它没有映射到模型中的任何实体类型,我们抛出一个异常,因为我们不知道如何具体化结果。此错误仅发生在数据库包含discriminator值的行,这些行没有映射到EF模型中。如果您有这样的数据,那么您可以将EF核心模型中的discriminator映射标记为incomplete,以表明我们应该始终为查询层次结构中的任何类型添加过滤谓词。IsComplete(false)调用鉴别器配置标志映射不完整:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator() .IsComplete(false); }
1、共享列
默认情况下,当层次结构中的两个同级实体类型具有相同的属性时,它们将映射到两个单独的列。 但是,如果它们的类型相同,则可以映射到相同的数据库列:
public class MyContext : DbContext { public DbSet<BlogBase> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasColumnName("Url"); modelBuilder.Entity<RssBlog>() .Property(b => b.Url) .HasColumnName("Url"); } } public abstract class BlogBase { public int BlogId { get; set; } } public class Blog : BlogBase { public string Url { get; set; } } public class RssBlog : BlogBase { public string Url { get; set; } }
2、每种类型一个表 (TPT)
EF Core 5.0 中引入了每种类型一个表 (TPT) 功能。 EF6 支持每个具体的表类型 (TPC) ,但 EF Core 尚不支持。
在 TPT 映射模式下,所有类型都映射到单独一个表。 仅属于某个基类型或派生类型的属性存储在映射到该类型的一个表中。 映射到派生类型的表还存储将派生表与基表联接的外键。
modelBuilder.Entity<Blog>().ToTable("Blogs"); modelBuilder.Entity<RssBlog>().ToTable("RssBlogs");
EF 将为上述模型创建下列数据库架构:
CREATE TABLE [Blogs] ( [BlogId] int NOT NULL IDENTITY, [Url] nvarchar(max) NULL, CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId]) ); CREATE TABLE [RssBlogs] ( [BlogId] int NOT NULL, [RssUrl] nvarchar(max) NULL, CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId]), CONSTRAINT [FK_RssBlogs_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([BlogId]) ON DELETE NO ACTION );
如果使用批量配置,您可以通过调用GetColumnName(IProperty, StoreObjectIdentifier)来检索特定表的列名:
foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var tableIdentifier = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); Console.WriteLine($"{entityType.DisplayName()}\t\t{tableIdentifier}"); Console.WriteLine(" Property\tColumn"); foreach (var property in entityType.GetProperties()) { var columnName = property.GetColumnName(tableIdentifier.Value); Console.WriteLine($" {property.Name,-10}\t{columnName}"); } Console.WriteLine(); }
在许多情况下,TPT表现出低于TPH的性能。