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

Подход TPH - Table Per Hierarchy

Последнее обновление: 31.01.2023

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

Например, у нас есть следующая иерархия классов:

public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
}

public class Employee : User
{
    public int Salary { get; set; }
}
public class Manager : User
{
    public string? Departament { get; set; }
}

Есть базовый класс User, который представляет пользователя и от которого наследуются класс Employee - класс работника и Manager - класс управляющего.

Определим контекст данных:

using Microsoft.EntityFrameworkCore;

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; } = null!; 
    public DbSet<Employee> Employees { get; set; } = null!;
    public DbSet<Manager> Managers { get; set; } = null!;
    public ApplicationContext()
    {
        Database.EnsureDeleted();
        Database.EnsureCreated();
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=helloapp.db");
    }
}

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

Сгенерированная база данных будет содержать для всех типов одну таблицу Users. Кроме всех свойств классов User, Employee и Manager здесь также появляется еще один столбец - Discriminator. Он имеет тип nvarchar (то есть строка), а в качестве значения он принимает название класса, к которому относится строка в таблице. В итоге в бд будет создаваться следующая таблица:

CREATE TABLE "Users" (
	"Id"	INTEGER NOT NULL,
	"Name"	TEXT,
	"Discriminator"	TEXT NOT NULL,
	"Salary"	INTEGER,
	"Departament"	TEXT,
	CONSTRAINT "PK_Users" PRIMARY KEY("Id" AUTOINCREMENT)
);

Пример использования:

using (ApplicationContext db = new ApplicationContext())
{
    User user1 = new User { Name = "Tom" };
    User user2 = new User { Name = "Bob" };
    db.Users.Add(user1);
    db.Users.Add(user2);

    Employee employee = new Employee { Name = "Sam", Salary = 500 };
    db.Employees.Add(employee);

    Manager manager = new Manager { Name = "Robert", Departament = "IT" };
    db.Managers.Add(manager);

    db.SaveChanges();

    var users = db.Users.ToList();
    Console.WriteLine("Все пользователи");
    foreach (var user in users)
    {
        Console.WriteLine(user.Name);
    }

    Console.WriteLine("\n Все работники");
    foreach (var emp in db.Employees.ToList())
    {
        Console.WriteLine(emp.Name);
    }
	
    Console.WriteLine("\nВсе менеджеры");
    foreach (var man in db.Managers.ToList())
    {
        Console.WriteLine(man.Name);
    }
}

Консольный вывод:

Все пользователи
Tom
Bob
Sam
Robert

Все работники
Sam

Все менеджеры
Robert

И в итоге данные таблицы Users в бд будут выглядеть следующим образом:

TPH in Entity Framework Core и C#

При необходимости мы даже можем добавить в класс User свойство Discriminator:

public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? Discriminator { get; set; }
}

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

Метод UseTphMappingStrategy

Стратегия TPH применяется для создания таблиц в EF Core по умолчанию, и каких-то дополнительных настроек нам не надо настраивать. Но, начиная, с EF Core мы также можем явным образом указать, что мы хотим использовать эту стратегию с помощью метода UseTphMappingStrategy() для базового типа иерархии классов:

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; } = null!;
    
    // ................
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().UseTphMappingStrategy();  // TPH
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850