Подход TPT - Table Per Type

Данное руководство устарело. Актуальное руководство: Руководство по Entity Framework Core 7

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

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

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

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

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

namespace HelloApp
{
    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; }
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Manager> Managers { get; set; }
        public ApplicationContext()
        {
            Database.EnsureDeleted();
            Database.EnsureCreated();
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=inheritedb;Trusted_Connection=True;");
        }
    }
}

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

CREATE TABLE [dbo].[Users] (
    [Id]   INT            IDENTITY (1, 1) NOT NULL,
    [Name] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[Employees] (
    [Id]     INT NOT NULL,
    [Salary] INT NOT NULL,
    CONSTRAINT [PK_Employees] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_Employees_Users_Id] FOREIGN KEY ([Id]) REFERENCES [dbo].[Users] ([Id])
);
CREATE TABLE [dbo].[Managers] (
    [Id]          INT            NOT NULL,
    [Departament] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Managers] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_Managers_Users_Id] FOREIGN KEY ([Id]) REFERENCES [dbo].[Users] ([Id])
);

Здесь мы видим, что все свойства базового класса 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:

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; }
	public DbSet<Employee> Employees { get; set; }
	public DbSet<Manager> Managers { get; set; }
	public ApplicationContext()
	{
		Database.EnsureDeleted();
		Database.EnsureCreated();
	}
	
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=inheritedb;Trusted_Connection=True;");
	}
	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<Employee>().ToTable("Employees");
		modelBuilder.Entity<Manager>().ToTable("Managers");
	}
}

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850