Подход TPT - Table Per Type

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

В прошлой статье был рассмотрен подход TPH - одна таблица для всей иерархии наследования классов. Но также EntityFramework Core позволяет использовать другой подход - TPT или Table Per Type, который предполагает создание отдельной таблицы для каждого класса из иерархии. Для реализации подхода TPT можно использовать два способа: атрибуты или Fluent API.

Применение TPT на основе атрибутов

С помощью атрибута [Table] мы можем указать для каждого класса свою таблицу:

using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
}
[Table("Employees")]
public class Employee : User
{
    public int Salary { get; set; }
}
[Table("Managers")]
public class Manager : User
{
    public string? Departament { get; set; }
}


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

В этом случае в бд будут создаваться следующие три таблицы:

CREATE TABLE "Users" (
	"Id"	INTEGER NOT NULL,
	"Name"	TEXT,
	CONSTRAINT "PK_Users" PRIMARY KEY("Id" AUTOINCREMENT)
);
CREATE TABLE "Employees" (
	"Id"	INTEGER NOT NULL,
	"Salary"	INTEGER NOT NULL,
	CONSTRAINT "FK_Employees_Users_Id" FOREIGN KEY("Id") REFERENCES "Users"("Id") ON DELETE CASCADE,
	CONSTRAINT "PK_Employees" PRIMARY KEY("Id" AUTOINCREMENT)
);
CREATE TABLE "Managers" (
	"Id"	INTEGER NOT NULL,
	"Departament"	TEXT,
	CONSTRAINT "FK_Managers_Users_Id" FOREIGN KEY("Id") REFERENCES "Users"("Id") ON DELETE CASCADE,
	CONSTRAINT "PK_Managers" PRIMARY KEY("Id" AUTOINCREMENT)
);

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

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

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

Применение TPT на основе Fluent API

Также мы можем настроить TPT с помощью метода ToTable() во Fluent API:

using Microsoft.EntityFrameworkCore;

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

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");
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Employee>().ToTable("Employees");
        modelBuilder.Entity<Manager>().ToTable("Managers");
    }
}

В остальном применение классов будет аналогично примеру с атрибутом [Table].

Метод UseTptMappingStrategy

Начиная с версии EF Core 7.0 также можно вызвать метод UseTptMappingStrategy для базовой сущности иерархии (в примере выше - класса User):

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");
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().UseTptMappingStrategy();    // устанавливаем подход TPT
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850