EF Core и Dapper решают пересекающиеся, но не одинаковые задачи. С каждым релизом EF Core сокращает отставание по производительности, но у Dapper по-прежнему есть своя ниша.
Что нового в EF Core
Compiled queries убирают overhead на трансляцию повторяющихся запросов.
private static readonly Func<AppDb, Guid, Task<UserDto?>> GetById =
EF.CompileAsyncQuery((AppDb db, Guid id) =>
db.Users.AsNoTracking()
.Where(u => u.Id == id)
.Select(u => new UserDto(u.Id, u.Email))
.FirstOrDefault());
ExecuteUpdateAsync / ExecuteDeleteAsync позволяют делать set-based операции без материализации сущностей.await db.Orders
.Where(o => o.Status == "Pending" && o.CreatedAtUtc < cutoff)
.ExecuteUpdateAsync(s =>
s.SetProperty(o => o.Status, _ => "Expired"));
Улучшенная поддержка raw SQL с проекцией в DTO, JSON-колонки, батчинг. В итоге для типичных API-ридов с небольшими DTO разница с Dapper составляет около 10–30%, а при доминировании latency на стороне БД она вообще не заметна.
Где Dapper всё ещё выигрывает
Кастомные джоины, CTE, оконные функции, db-specific хинты. Read-mostly микросервисы без change tracking. Хранимые процедуры. Минимальный jitter на p95–p99.
Прагматичный подход
Большинство команд используют гибрид. EF Core по умолчанию для всего с
Select + AsNoTracking. Для топ 2–5 горячих запросов пробуют compiled queries. Если мало, реализуют только их через Dapper за интерфейсом (IOrderQueries), а остальное на EF.Типичные ошибки
Возврат сущностей вместо DTO из API. Lazy loading без контроля. N+1 из-за навигационных свойств. Отсутствие
AsNoTracking на ридах. Generic repository, который прячет фичи EF.Compiled EF vs non-compiled экономит 0.5–2.5 мс на вызов. Если compiled query укладывается в SLO в пределах 15% от Dapper и проще в поддержке, оставляйте EF.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view