В прошлой статье был рассмотрен подход TPH - одна таблица для всей иерархии наследования классов. Но также EntityFramework Core позволяет использовать другой подход - TPT или Table Per Type, который предполагает создание отдельной таблицы для каждого класса из иерархии. Для реализации подхода TPT можно использовать два способа: атрибуты или Fluent API.
С помощью атрибута [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 с помощью метода 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]
.
Начиная с версии 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 } }