Ответ
EF Core отслеживает изменения всех сущностей, полученных в контексте (с AsTracking()). При вызове SaveChanges() он фиксирует все накопленные изменения в базе данных внутри транзакции.
Что именно происходит:
- Анализ ChangeTracker: EF Core проверяет
DbContext.ChangeTrackerна наличие сущностей в состоянияхAdded,Modified, илиDeleted. - Генерация SQL: Для каждой измененной сущности генерируется соответствующий SQL-команда (
INSERT,UPDATE,DELETE). - Выполнение в транзакции: Все команды выполняются как одна транзакция (если не используется явная транзакция).
- Сохранение или откат: Если все команды выполнены успешно, транзакция коммитится, и состояния сущностей меняются на
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()для повышения производительности.
Ответ 18+ 🔞
Давай разберем, как эта штука работает, а то звучит сложно, а на деле — проще пареной репы.
Представь, что твой DbContext — это такой дотошный завхоз на складе. Каждую коробку (сущность), которую ты через него получаешь с AsTracking() (а это по умолчанию), он начинает пристально отслеживать. Записывает в свою тетрадку, что с ней делают.
А потом, когда ты говоришь SaveChanges(), он берет эту тетрадку и одним махом, в рамках одной транзакции, вносит все изменения на склад (в базу данных). Либо всё успешно применяется, либо, если где-то косяк, откатывается как ни в чём не бывало.
По шагам, что там внутри творится:
- Ревью тетрадки (ChangeTracker). Завхоз листает свою тетрадь и ищет пометки: что добавили (Added), что поменяли (Modified), что списали в утиль (Deleted).
- Пишет команды. Для каждой такой пометки он генерирует SQL-команду:
INSERT,UPDATEилиDELETE. - Работает одной сменой (транзакция). Все эти команды он пытается выполнить как единую операцию. Это, блядь, важно — либо всё, либо ничего.
- Финал. Если всё заебок, транзакция подтверждается, а в тетрадке напротив этих сущностей ставится галочка "актуально". Если где-то пиздец — всё откатывается, и летит исключение.
Вот как это выглядит в коде, на живом примере:
using var db = new AppDbContext();
// Достаём блог из базы. Завхоз сразу начинает за ним следить.
var blog = await db.Blogs.FirstAsync(b => b.Id == 1);
blog.Rating = 5; // Завхоз в тетрадке: "Блог 1 — изменён (Modified)"
blog.Url = "https://newurl.com";
// Подкидываем ему новую коробку (блог). Он её принимает и помечает "новая (Added)".
db.Blogs.Add(new Blog { Url = "https://example.com" });
// Говорим списать старый блог в утиль. Пометка "удалён (Deleted)".
db.Remove(await db.Blogs.FirstAsync(b => b.Id == 99));
// А теперь — волшебный пендель! Завхоз бежит исполнять все накопившиеся поручения.
int affectedRows = await db.SaveChangesAsync();
Console.WriteLine($"Затронуто строк: {affectedRows}"); // Тут будет 3, если всё прошло.
А теперь лайфхаки, чтобы не выстрелить себе в ногу:
SaveChangesAsync()— твой друг. Всегда используй асинхронный вариант, если можешь. Не блокируй главный поток, ебать его в сраку.- Пакетная обработка (Batching). Умный завхоз может несколько мелких поручений (
UPDATE,INSERT) объединить в одну пачку и сбегать на склад один раз, вместо трёх. Производительность растёт, как на дрожжах. - Оптимистическая блокировка. Чтобы два человека не перезаписали изменения друг другу, используй
[ConcurrencyCheck]или поле сTimestamp. Это как сказать: "Если пока я редактировал, кто-то уже тронул запись — кинь мне ошибку, а не тихо её перезапиши". - Не отслеживай лишнее. Если ты просто читаешь данные, чтобы показать их на экране, и не собираешься менять — скажи завхозу не париться. Используй
AsNoTracking(). Производительность взлетит, потому что он не будет тратить силы на ведение своей ебучей тетрадки.