Ответ
Метод Update() в DbSet<T> помечает переданную сущность и все её навигационные свойства (которые не null) как изменённые (EntityState.Modified). Это сигнализирует EF Core, что при вызове SaveChanges() для этой сущности должен быть выполнен SQL-запрос UPDATE.
Как это работает:
- Если сущность с таким первичным ключом ещё не отслеживается контекстом, она будет отслеживаться со состоянием
Modified. - Если сущность уже отслеживается,
Update()перезапишет её текущие значения в отслеживаемом объекте. - Все связанные сущности в графе также будут отслеживаться и помечены как
Modified.
Пример обновления:
// Допустим, это данные, пришедшие с клиента (например, из формы редактирования)
var updatedProduct = new Product { Id = 10, Name = "New Monitor", Price = 299.99, CategoryId = 5 };
// Помечаем сущность и все её заполненные свойства как изменённые
context.Products.Update(updatedProduct);
// При SaveChanges() EF Core сгенерирует примерно:
// UPDATE [Products] SET [Name] = @p0, [Price] = @p1, [CategoryId] = @p2
// WHERE [Id] = @p3;
await context.SaveChangesAsync();
Критически важные нюансы:
- Генерируется полный UPDATE: Все свойства сущности будут включены в запрос, даже если фактически изменилось только одно. Это может привести к конфликтам параллелизма и избыточным операциям.
- Для частичного обновления используйте Attach(): Более эффективный паттерн —
Attach()+ явная пометка изменённых свойств.context.Products.Attach(updatedProduct); context.Entry(updatedProduct).Property(p => p.Price).IsModified = true; // Обновляем только цену - Массовые обновления: Для обновления многих строк без их загрузки в память используйте
ExecuteUpdate()(EF Core 7+):await context.Products .Where(p => p.Price < 50) .ExecuteUpdateAsync(setters => setters.SetProperty(p => p.Price, p => p.Price * 1.1));
Ответ 18+ 🔞
А, ну это про тот самый Update(), который всех так радует, пока не наступит на грабли по самые помидоры. Слушай, сейчас разложу по полочкам, как этот зверь работает, чтобы потом не орать "почему у меня всё обновилось, я же только цену поменять хотел!".
Вот смотри, в чём суть: когда ты вызываешь Update() на DbSet, Entity Framework Core берёт твою сущность и помечает её и всю её родню (навигационные свойства, которые не null) как изменённые. То есть он кричит контексту: "Эй, эта хрень и всё, что к ней прицеплено, надо переписать в базе!". При SaveChanges() для неё полетит обычный UPDATE на все поля.
Как это на практике выглядит:
- Если контекст про такую сущность с таким ключом ещё не слышал — он начинает её отслеживать и сразу ставит флажок "изменена".
- Если уже отслеживает — просто перезатрёт старые значения в отслеживаемом объекте новыми.
- И самое весёлое: все связанные сущности в этом графе тоже получат флажок "изменена". Представь, что ты обновляешь товар, а заодно и его категорию, если она в объекте была. Неожиданно, да?
Вот тебе живой пример, как все это обычно делают:
// Допустим, пришли данные из формы на фронте
var updatedProduct = new Product { Id = 10, Name = "Новый Монитор", Price = 299.99, CategoryId = 5 };
// Бах! И всё обновил!
context.Products.Update(updatedProduct);
// На SaveChanges() EF Core сгенерит что-то типа:
// UPDATE [Products] SET [Name] = @p0, [Price] = @p1, [CategoryId] = @p2
// WHERE [Id] = @p3;
await context.SaveChangesAsync();
А теперь, внимание, главные подводные камни, об которые все расшибают лбы:
-
Генерируется полный UPDATE на все поля. Даже если ты поменял только цену, а имя осталось тем же — в запрос полезут ВСЕ свойства. Это, во-первых, лишняя работа, а во-вторых, может привести к пиздецу с оптимистичной блокировкой, если ты её используешь. Представь, что кто-то параллельно поменял описание товара, а ты лепишь
UPDATEсо старым описанием — будет конфликт версий, ёпта! -
Для частичного обновления есть более хитрая схема:
Attach()+ ручная пометка. Так делают профи, когда хотят обновить только то, что действительно поменялось:// Присоединяем сущность к контексту, но не помечаем изменённой context.Products.Attach(updatedProduct); // А теперь вручную тычем пальцем: "Вот это поле — да, его трогаем" context.Entry(updatedProduct).Property(p => p.Price).IsModified = true; // Всё, только цена обновится. Чисто, аккуратно, без сюрпризов. -
Если надо обновить дохуя строк и не грузить их в память (массовое обновление), то
Update()— это вообще не вариант. Ты что, будешь по одной ихUpdate()-ить? С EF Core 7+ появилсяExecuteUpdate()— вот это настоящая магия:// Всё дешевле 50 баксов подорожало на 10%. Один запрос, ни одной сущности в памяти. await context.Products .Where(p => p.Price < 50) .ExecuteUpdateAsync(setters => setters.SetProperty(p => p.Price, p => p.Price * 1.1));
Короче, Update() — это как кувалда. Иногда нужно именно ей, но чаще всё-таки лучше взять скальпель. Подумай, что тебе на самом деле нужно: переписать всё подчистую или точечно поправить пару полей.