Опубликовано
- 3 мин чтения
Cтратегия отображения типов и производительность EF
Entity Framework (EF) — это популярный ORM-фреймворк для работы с базами данных в .NET-приложениях. EF поддерживает разные стратегии отображения сущностей, каждая из которых влияет на схему базы данных и взаимодействие EF с иерархией типов .NET. В этой статье мы рассмотрим, как эти стратегии влияют на производительность в SQL Server.
Стратегии отображения в Entity Framework
В официальной документации Microsoft описаны три стратегии:
- Table-per-hierarchy (TPH);
- Table-per-class (TPC);
- Table-per-type (TPT).
Из моего опыта существует ещё одна стратегия, которую я называю TPH с JSON. Суть её в том, что свойства дочерних классов сериализуются в JSON и сохраняются в одну колонку.
Рассмотрим следующую иерархию:
public class Root { }
public class ChildA : Root { }
public class ChildB : Root { }
public class ChildC : Root { }
Схемы баз данных для этой иерархии будут следующими:
| TPH | TPC | TPT | TPH с JSON |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
Описание:
- В
TPHвсе свойства всех сущностей находятся в одной таблице. - В
TPCдочерние таблицы не связаны с родительской таблицей. - В
TPTдочерние таблицы содержат только специфичные для типа столбцы и связаны с родительской. - В
TPH с JSONвсе специфичные свойства упакованы в одну колонкуPayload, а тип хранится в колонкеPayloadType.
Теперь посмотрим, как это влияет на производительность.
Бенчмарк
В проекте бенчмарка есть 4 экземпляра DbContext, по одному для каждой стратегии. TPH, TPC, TPT стандартные. Для TPH с JSON требуется дополнительный обёрточный класс:
public class RootWrapper : Root
{
public string Payload { get; set; } = string.Empty;
[NotMapped]
public Root OriginalEntity { get; set; }
public PayloadTypes PayloadType { get; set; }
public enum PayloadTypes
{
Root,
ChildA,
ChildB,
ChildC,
}
}
Для генерации тестовых данных использовалась библиотека Bogus, для замеров — BenchmarkDotNet.
Проводились два теста: на INSERT и SELECT данных.
Результаты
Вот результаты. Как видно:
TPTпоказывает худшие результаты дляSELECTиINSERT.TPCчуть быстрее при выборке, но медленнее при вставке по сравнению сTPH.TPH с JSONпоказал лучшую производительность.
Рисунок 1 — Время выполнения вставки данных
Рисунок 2 — Время выполнения выборки данных
TPH против TPH с JSON
Чтобы разобраться, почему TPH с JSON быстрее TPH, я провёл дополнительный бенчмарк: разные сущности содержали от 3 до 21 свойства.
Рисунок 3 — Влияние количества свойств и сущностей на время вставки
Рисунок 4 — Отношение времени вставки TPH к TPH с JSON по количеству сущностей
Рисунок 5 — Отношение времени вставки TPH к TPH с JSON по количеству свойств
Причины ухудшения производительности TPH:
- EF Core создаёт отдельный
INSERTдля каждого типа. - EF Core создаёт параметр для каждого свойства.
Для TPH с JSON создаётся один INSERT, а количество параметров в 4 раза меньше.
Выборка данных
Для выборки (SELECT) ситуация чуть иная:
Рисунок 6 — Влияние количества свойств и сущностей на время выборки
Рисунок 7 — Отношение времени выборки TPH к TPH с JSON по количеству сущностей
Рисунок 8 — Отношение времени выборки TPH к TPH с JSON по количеству свойств
Если свойств меньше 8, TPH с JSON быстрее, если больше — выигрывает обычный TPH.
Вывод
Результаты можно свести в таблицу:
| Место | Стратегия | Плюсы | Минусы |
|---|---|---|---|
| 1 | TPH с JSON | Лучшая производительность почти во всех случаях | Требует дополнительного кода, индексирования JSON |
| 1 | TPH | Стандартная стратегия EF Core, хорошая производительность | Ухудшение производительности при большом количестве типов |
| 2 | TPC | Чуть лучше при вставке данных | Чуть хуже при выборке данных |
| 3 | TPT | ??? | Худшая производительность |



