Наследование

EF может сопоставлять иерархию типов .NET с базой данных. Это позволяет создавать сущности .NET в коде обычным образом, используя базовые и производные типы и позволяя EF легко создавать соответствующую схему базы данных, выдавать запросы и т. д. Фактические сведения о сопоставлении иерархии типов являются зависимыми от поставщика; на этой странице описывается поддержка наследования в контексте реляционной базы данных.

Сопоставление иерархии типов сущностей

По соглашению EF не будет автоматически сканировать базовые или производные типы; Это означает, что если требуется сопоставить тип CLR в иерархии, необходимо явно указать этот тип в модели. Например, указание только базового типа иерархии не приведет к неявному включению всех его вложенных типов EF Core.

В следующем примере предоставляется DbSet для Blog и его подкласс RssBlog . Если Blog имеет любой другой подкласс, он не будет включаться в модель.

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

Примечание

Столбцы базы данных автоматически допускают значение null при необходимости при использовании сопоставления «одна таблица». Например, RssUrl столбец допускает значение null, так как у обычных Blog экземпляров нет этого свойства.

если вы не хотите предоставлять DbSet для одной или нескольких сущностей в иерархии, можно также использовать API Fluent, чтобы убедиться, что они включены в модель.

Совет

Если вы не полагаетесь на соглашения, можно явно указать базовый тип с помощью HasBaseType . Также можно использовать .HasBaseType((Type)null) для удаления типа сущности из иерархии.

Конфигурация таблиц на иерархию и дискриминатор

По умолчанию EF сопоставляет наследование, используя шаблон «одна таблица на иерархию ». Функция «подтаблица» использует одну таблицу для хранения данных всех типов в иерархии, а столбец дискриминатора используется для указания типа, представляемого каждой строкой.

Приведенная выше модель сопоставлена со следующей схемой базы данных (Обратите внимание на Discriminator столбец, созданный неявно, который определяет, какой тип Blog хранится в каждой строке).

Screenshot of the results of querying the Blog entity hierarchy using table-per-hierarchy pattern

Можно настроить имя и тип столбца дискриминатора, а также значения, которые используются для обнаружения каждого типа в иерархии.

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

При запросе производных сущностей, использующих шаблон «подстановка», EF Core добавляет предикат к столбцу дискриминатора в запросе. Этот фильтр гарантирует, что мы не получаем дополнительные строки для базовых типов или родственных типов, отсутствующих в результатах. Этот предикат фильтра пропущен для базового типа сущности, так как при выполнении запроса для базовой сущности будут получены результаты для всех сущностей в иерархии. При материализации результатов из запроса, если мы получаем значение дискриминатора, которое не сопоставлено ни с одним из типов сущностей в модели, создается исключение, поскольку неизвестно, как материализовать результаты. Эта ошибка возникает только в том случае, если база данных содержит строки со значениями дискриминатора, которые не сопоставлены в модели EF. Если у вас есть такие данные, можно пометить дискриминатор в EF Core модели как неполное, чтобы указать, что необходимо всегда добавлять предикат фильтра для запроса любого типа в иерархии. IsComplete(false) вызов в конфигурации дискриминатора помечает сопоставление как неполное.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasDiscriminator()
        .IsComplete(false);
}

Общие столбцы

По умолчанию, если два одноуровневого типа сущности в иерархии имеют свойство с одинаковым именем, они будут сопоставлены с двумя отдельными столбцами. Однако, если их тип идентичен, они могут быть сопоставлены с одним столбцом базы данных:

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

Примечание

поставщики реляционных баз данных, такие как SQL Server, не будут автоматически использовать предикат дискриминатора при запросе общих столбцов при использовании приведения. Запрос Url = (blog as RssBlog).Url также возвращает Url значение для строк одного уровня Blog . Чтобы ограничить запрос RssBlog сущностями, необходимо вручную добавить фильтр в дискриминатор, например Url = blog is RssBlog ? (blog as RssBlog).Url : null .

Конфигурация типа "один таблица на тип"

Примечание

Функция "Таблица для каждого типа" (TPT) была представлена в EF Core 5,0. Тип данных "Таблица — конкретный" (TPC) поддерживается EF6, но еще не поддерживается 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
);

Примечание

При переименовании ограничения PRIMARY KEY новое имя будет применено ко всем таблицам, сопоставленным с иерархией, а будущие версии EF будут разрешать Переименование ограничения только для конкретной таблицы, если проблема 19970 исправлена.

При использовании групповой конфигурации можно получить имя столбца для определенной таблицы, вызвав метод 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 отображает неограниченную производительность по сравнению с подиерархией. Дополнительные сведения см. в документации по производительности.