Что такое дискриминатор (Discriminator) в Entity Framework Core?

«Что такое дискриминатор (Discriminator) в Entity Framework Core?» — вопрос из категории Entity Framework, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Дискриминатор — это специальный столбец, который Entity Framework Core автоматически добавляет в таблицу базы данных при использовании стратегии наследования TPH (Table Per Hierarchy). Значение в этом столбце определяет, к какому конкретному типу-наследнику относится каждая строка в таблице.

Зачем это нужно? TPH позволяет хранить всю иерархию классов в одной таблице, что упрощает запросы (не нужны JOIN), но требует способа различать типы. Дискриминатор решает эту проблему.

Пример настройки:

// Модель
public abstract class BillingDetail
{
    public int Id { get; set; }
    public string Owner { get; set; }
    public string Number { get; set; }
}

public class BankAccount : BillingDetail
{
    public string BankName { get; set; }
    public string Swift { get; set; }
}

public class CreditCard : BillingDetail
{
    public int CardType { get; set; }
    public string ExpiryMonth { get; set; }
}

// Конфигурация в DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BillingDetail>()
        .HasDiscriminator<string>(nameof(BillingDetailType)) // Имя столбца
        .HasValue<BankAccount>("BankAccount") // Значение для BankAccount
        .HasValue<CreditCard>("CreditCard");  // Значение для CreditCard
}

Результирующая таблица в БД:

CREATE TABLE BillingDetails (
    Id INT PRIMARY KEY,
    Owner NVARCHAR(MAX),
    Number NVARCHAR(MAX),
    BankName NVARCHAR(MAX), -- NULL для CreditCard
    Swift NVARCHAR(MAX),    -- NULL для CreditCard
    CardType INT,           -- NULL для BankAccount
    ExpiryMonth NVARCHAR(MAX), -- NULL для BankAccount
    BillingDetailType NVARCHAR(MAX) NOT NULL -- Дискриминатор
);

Важные моменты:

  • Имя столбца и значения можно кастомизировать, как показано выше.
  • Тип дискриминатора может быть string, int, enum.
  • При запросе к DbSet<BillingDetail> EF Core автоматически добавляет в SQL условие WHERE Discriminator = '...' в зависимости от того, запрашиваете вы базовый класс или конкретного наследника.
  • Недостаток TPH: Столбцы, специфичные для наследников, должны допускать NULL, что может привести к "раздуванию" таблицы при глубоких иерархиях.