継承Inheritance

EF では、.NET 型階層をデータベースにマップできます。EF can map a .NET type hierarchy to a database. これにより、基本型と派生型を使用して、通常どおりに .NET エンティティをコードに記述し、EF によって適切なデータベーススキーマの作成、クエリの実行などをシームレスに行うことができます。型階層のマッピング方法の実際の詳細は、プロバイダーに依存します。このページでは、リレーショナルデータベースのコンテキストでの継承のサポートについて説明します。This allows you to write your .NET entities in code as usual, using base and derived types, and have EF seamlessly create the appropriate database schema, issue queries, etc. The actual details of how a type hierarchy is mapped are provider-dependent; this page describes inheritance support in the context of a relational database.

現時点では、EF Core では、階層単位 (TPH) のパターンのみがサポートされています。At the moment, EF Core only supports the table-per-hierarchy (TPH) pattern. TPH は、1つのテーブルを使用して階層内のすべての型のデータを格納します。また、識別子列を使用して、各行が表す型を識別します。TPH uses a single table to store the data for all types in the hierarchy, and a discriminator column is used to identify which type each row represents.

注意

EF6 によってサポートされている、型ごとのテーブル (TPT) と具象型 (TPC) は、EF Core ではまだサポートされていません。The table-per-type (TPT) and table-per-concrete-type (TPC), which are supported by EF6, are not yet supported by EF Core. TPT は EF Core 5.0 に対して計画されている主な機能です。TPT is a major feature planned for EF Core 5.0.

エンティティ型階層のマッピングEntity type hierarchy mapping

慣例により、EF は基本型または派生型を自動的にスキャンしません。これは、階層内の CLR 型をマップする必要がある場合は、モデルでその型を明示的に指定する必要があることを意味します。By convention, EF will not automatically scan for base or derived types; this means that if you want a CLR type in your hierarchy to be mapped, you must explicitly specify that type on your model. たとえば、階層の基本データ型だけを指定しても、EF Core によってそのすべてのサブ型が暗黙的に含まれるとは限りません。For example, specifying only the base type of a hierarchy will not cause EF Core to implicitly include all of its sub-types.

次の例では、とそのサブクラスの DbSet を公開して Blog RssBlog います。The following sample exposes a DbSet for Blog and its subclass RssBlog. Blogに他のサブクラスがある場合は、モデルには含まれません。If Blog has any other subclass, it will not be included in the model.

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; }
}

このモデルは、次のデータベーススキーマにマップされます (それぞれの行に格納されているブログの種類を識別する、暗黙的に作成された識別子の列に注意してください)。This model is mapped to the following database schema (note the implicitly-created Discriminator column, which identifies which type of Blog is stored in each row):

image

注意

TPH マッピングを使用する場合、データベース列は必要に応じて自動的に null 許容になります。Database columns are automatically made nullable as necessary when using TPH mapping. たとえば、 RssUrl列は null 値を許容します。通常のブログインスタンスにはそのプロパティがないためです。For example, the RssUrl column is nullable because regular Blog instances do not have that property.

階層内の1つ以上のエンティティに対して DbSet を公開しない場合は、Fluent API を使用してモデルに含まれていることを確認することもできます。If you don't want to expose a DbSet for one or more entities in the hierarchy, you can also use the Fluent API to ensure they are included in the model.

ヒント

規則に依存しない場合は、を使用して基本型を明示的に指定でき HasBaseType ます。If you don't rely on conventions, you can specify the base type explicitly using HasBaseType. また、を使用し .HasBaseType((Type)null) て、階層からエンティティ型を削除することもできます。You can also use .HasBaseType((Type)null) to remove an entity type from the hierarchy.

識別子の構成Discriminator configuration

識別子の列の名前と種類と、階層内の各型を識別するために使用される値を構成できます。You can configure the name and type of the discriminator column and the values that are used to identify each type in the hierarchy:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasDiscriminator<string>("blog_type")
        .HasValue<Blog>("blog_base")
        .HasValue<RssBlog>("blog_rss");
}

上の例では、EF によって、階層の基本エンティティに対して暗黙的にシャドウプロパティとして識別子が追加されています。In the examples above, EF added the discriminator implicitly as a shadow property on the base entity of the hierarchy. このプロパティは、次のように構成できます。This property can be configured like any other:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property("Discriminator")
        .HasMaxLength(200);
}

また、識別子は、エンティティ内の通常の .NET プロパティにもマップできます。Finally, the discriminator can also be mapped to a regular .NET property in your entity:

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");
}

共有列Shared columns

既定では、階層内の2つの兄弟エンティティ型に同じ名前のプロパティがある場合、2つの異なる列にマップされます。By default, when two sibling entity types in the hierarchy have a property with the same name, they will be mapped to two separate columns. ただし、型が同一の場合は、同じデータベース列にマップできます。However, if their type is identical they can be mapped to the same database column:

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; }
}