alexeyfv

Опубликовано

- 3 мин чтения

Cтратегия отображения типов и производительность EF

Performance C# Entity Framework SQL Server
img of 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 { }

Схемы баз данных для этой иерархии будут следующими:

TPHTPCTPTTPH с JSON
Диаграмма 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 — Влияние количества свойств и сущностей на время вставки

Отношение времени вставки TPH к TPH с JSON по количеству сущностей

Рисунок 4 — Отношение времени вставки TPH к TPH с JSON по количеству сущностей

Отношение времени вставки TPH к TPH с JSON по количеству свойств

Рисунок 5 — Отношение времени вставки TPH к TPH с JSON по количеству свойств

Причины ухудшения производительности TPH:

  1. EF Core создаёт отдельный INSERT для каждого типа.
  2. EF Core создаёт параметр для каждого свойства.

Для TPH с JSON создаётся один INSERT, а количество параметров в 4 раза меньше.

Выборка данных

Для выборки (SELECT) ситуация чуть иная:

Влияние количества свойств и сущностей на время выборки

Рисунок 6 — Влияние количества свойств и сущностей на время выборки

Отношение времени выборки TPH к TPH с JSON по количеству сущностей

Рисунок 7 — Отношение времени выборки TPH к TPH с JSON по количеству сущностей

Отношение времени выборки TPH к TPH с JSON по количеству свойств

Рисунок 8 — Отношение времени выборки TPH к TPH с JSON по количеству свойств

Если свойств меньше 8, TPH с JSON быстрее, если больше — выигрывает обычный TPH.

Вывод

Результаты можно свести в таблицу:

МестоСтратегияПлюсыМинусы
1TPH с JSONЛучшая производительность почти во всех случаяхТребует дополнительного кода, индексирования JSON
1TPHСтандартная стратегия EF Core, хорошая производительностьУхудшение производительности при большом количестве типов
2TPCЧуть лучше при вставке данныхЧуть хуже при выборке данных
3TPT???Худшая производительность