Ответ
Существует два принципиально разных подхода: массовое обновление на стороне БД (эффективно) и обновление через отслеживание изменений (менее эффективно, но иногда необходимо).
1. Массовое обновление (Bulk Update) — EF Core 7.0+
Метод ExecuteUpdate выполняет один SQL-запрос UPDATE непосредственно в базе данных, не загружая сущности в память. Это самый эффективный способ.
// Обновляет LastLogin для всех активных пользователей одним запросом.
await dbContext.Users
.Where(u => u.IsActive)
.ExecuteUpdateAsync(setters => setters
.SetProperty(u => u.LastLogin, DateTime.UtcNow)
.SetProperty(u => u.LoginCount, u => u.LoginCount + 1) // Можно использовать значение столбца
);
// Сгенерированный SQL будет похож на:
// UPDATE [Users] SET [LastLogin] = GETUTCDATE(), [LoginCount] = [LoginCount] + 1 WHERE [IsActive] = 1
Преимущества: Высокая скорость, минимальное потребление памяти, атомарность.
Недостаток: Сущности не загружаются в контекст (DbContext), поэтому с ними не работают механизмы отслеживания изменений и не вызываются события модели (например, SaveChanges).
2. Обновление через контекст изменений
Подходит, когда обновлению должна предшествовать сложная бизнес-логика, требующая загрузки объектов.
// Загружаем сущности в память (может быть тяжело для больших наборов).
var usersToUpdate = await dbContext.Users
.Where(u => u.DepartmentId == departmentId)
.ToListAsync();
// Применяем бизнес-логику.
foreach (var user in usersToUpdate)
{
user.Status = UserStatus.Inactive;
user.DeactivatedOn = DateTime.UtcNow;
// Можно вызвать user.Deactivate(); для инкапсуляции логики.
}
// Все изменения отслеживаются контекстом и применяются одним вызовом.
await dbContext.SaveChangesAsync();
Преимущества: Полный цикл работы с сущностями, вызов валидации, срабатывание интерцепторов. Недостатки: Высокие затраты памяти и времени при работе с тысячами записей (N+1 проблема).
Рекомендация:
- Для простых обновлений многих записей по условию всегда используйте
ExecuteUpdate(EF Core 7+). - Для EF Core 6 и ниже рассмотрите использование библиотек для массовых операций (например,
EFCore.BulkExtensions) или сырых SQL-запросов черезDbContext.Database.ExecuteSqlRaw. - Обновление через контекст оставьте для случаев, где требуется сложная обработка каждой сущности.