Что произойдет при вызове SaveChanges() после изменения отслеживаемой сущности в Entity Framework Core?

«Что произойдет при вызове SaveChanges() после изменения отслеживаемой сущности в Entity Framework Core?» — вопрос из категории Entity Framework, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

EF Core отслеживает изменения всех сущностей, полученных в контексте (с AsTracking()). При вызове SaveChanges() он фиксирует все накопленные изменения в базе данных внутри транзакции.

Что именно происходит:

  1. Анализ ChangeTracker: EF Core проверяет DbContext.ChangeTracker на наличие сущностей в состояниях Added, Modified, или Deleted.
  2. Генерация SQL: Для каждой измененной сущности генерируется соответствующий SQL-команда (INSERT, UPDATE, DELETE).
  3. Выполнение в транзакции: Все команды выполняются как одна транзакция (если не используется явная транзакция).
  4. Сохранение или откат: Если все команды выполнены успешно, транзакция коммитится, и состояния сущностей меняются на Unchanged. При ошибке транзакция откатывается, а исключение пробрасывается выше.

Практический пример:

using var db = new AppDbContext();

// Сущность теперь отслеживается контекстом
var blog = await db.Blogs.FirstAsync(b => b.Id == 1);
blog.Rating = 5; // Состояние меняется на Modified
blog.Url = "https://newurl.com";

// Добавляем новую сущность
db.Blogs.Add(new Blog { Url = "https://example.com" }); // Состояние Added

// Удаляем сущность
db.Remove(await db.Blogs.FirstAsync(b => b.Id == 99)); // Состояние Deleted

// Все три операции (UPDATE, INSERT, DELETE) будут выполнены здесь
int affectedRows = await db.SaveChangesAsync();
Console.WriteLine($"Затронуто строк: {affectedRows}");

Ключевые настройки и best practices:

  • SaveChangesAsync(): Всегда предпочтительнее синхронного варианта для не блокирующего IO.
  • Пакетная обработка (Batching): EF Core может объединять несколько команд в один пакет (roundtrip) для повышения производительности.
  • Оптимистическая блокировка: Используйте свойство с атрибутом [ConcurrencyCheck] или Timestamp, чтобы предотвратить конфликты параллельного обновления.
  • Отключение автоматического отслеживания: В сценариях только для чтения используйте AsNoTracking() для повышения производительности.