Итак, во-первых, давайте уберем самую важную вещь:
Вы правы.В вашем примере вам не нужно вручную звонить db.Entry(category).State = EntityState.Modified
.Это потому, что вы загружаете записи (категории) из контекста выше.Это называется «подключенным сценарием», где DbContext
знает о сущностях, отслеживает их .Это то же самое, например, в приложении ASP.NET Core, где контекст является общим для HTTP-запроса.
Любое изменение, которое вы вносите в область действия using (var db = new LakshyaContext())
, будет известно контексту при вызове SaveChanges
.
Теперь, работая над отключенными сценариями (как вы сказали, UnTracked сущности), мы должны копнуть немного глубже.
Чтобы понять это, сначала вам нужно знать, как DbContext
знать, что изменилось.Возьмем следующий пример:
using (var context = new MyContext())
{
// loads the book by it's ISBN
var book = context.Books
.Single(p => p.ISBN == "123456");
// Do changes
book.Price = 30;
// Save changes
context.SaveChanges();
}
Как он узнал, что Price
изменился?так как это просто обычное свойство auto в классе Book
?Магия лежит в основе метода DetectChanges
.
В некоторых конкретных случаях DbContext
вызывает метод DetectChanges
.Наиболее очевидным является случай, когда вызывается SaveChanges
.На верхнем уровне он работает следующим образом:
-
DbContext
делает снимок каждой загружаемой сущности - Когда вызывается
SaveChanges
, он продолжает вызыватьDetectChanges
, который сделает волшебство, чтобы выяснить, что изменилось или нет. DbContext
затем отправляет правильные команды в БД.
На этом этапемы знаем ответственность DetectChanges
.Теперь важная часть - знать, когда вызывается DetectChanges
(кроме уже известных нам SaveChanges).Это важно, чтобы окончательно ответить на ваш вопрос «Заказать».Из связанной статьи от Артур Викерс
Методы, которые вызывают DetectChanges:
- DbSet.Find
- DbSet.Local
- DbSet.Remove
- DbSet.Add
- DbSet.Attach
- DbContext.SaveChanges
- DbContext.GetValation
- DbContext.Entry
- DbChangeTracker.Entries
Давайте рассмотрим этот код, который демонстрирует сценарий «отключен».
public Task UpdateBook()
{
Book book = null;
// Just loads the book from this context
using (var context = new MyContext())
{
book = context.Books
.Single(p => p.ISBN == "123456");
}
// Starts a new context where the book is going to be updated
using (var anotherContext = new MyContext())
{
// Changed the price - remember this is not loaded from this context!
book.Price = 40;
// THIS IS KEY: This will call `DetectChanges`
// This entity will be tracked by the context now
db.Entry(book).State = EntityState.Modified
// Update will occur normally
db.SaveChanges();
}
}
Когда мы переходим ко второму DbContext,
, оно не осознает нашу book
сущность.Мы меняем цену и затем звоним db.Entry(book).State = EntityState.Modified
.В этот момент DbContext
начнет отслеживать его и вызывается DetectChanges
.Продолжение вызова SaveChanges
будет работать, как и ожидалось.
Если бы мы сделали обратное, вызов db.Entry(book).State = EntityState.Modified
до фактического изменения цены будет ... все равно будет работать!
Почему?Что ж, ручное изменение состояния объекта с помощью db.Entry(book).State
добавит объект в контекст, что означает, что он начнет отслеживать его на предмет изменений.Таким образом, даже если мы вызовем db.Entry(book).State
и затем применим изменения к объекту, это не будет иметь значения, потому что вызов SaveChanges
в конце вызовет снова DetectChanges
, и так как он уже был вызван ранее,для сущности уже был моментальный снимок.
Один из способов проверить это поведение самостоятельно - запустить приведенный выше код с включенным ведением журнала для DbContext
:
// Calling db.Entry.. produces this log:
DetectChanges starting for 'MyContext'.
Microsoft.EntityFrameworkCore.ChangeTracking:Debug: DetectChanges completed for 'MyContext'.
Context 'MyContext' started tracking 'Book' entity.
// Calling SaveChanges produces this log:
SaveChanges starting for 'MyContext'
DetectChanges starting for 'MyContext'.
DetectChanges completed for 'MyContext'.
Opening connection to database 'BooksDB'
Beginning transaction with isolation
...
Теперь некоторыепримечания:
Приведенное выше обновление в отключенном сценарии приведет к обновлению ALL COLUMNS в таблице.Это может быть не то, что вы ожидали.Есть способы предотвратить это. Подробнее здесь
DetectChanges
делает много вещей внутри, не только применяя слияния к изменениям.Он заботится об иностранных ключах, обновляет ссылки на свойства навигации и многое другое, а также выполняет «исправление».
Больше ресурсов для чтения: (особенно материалы от Артура Виккерса!)
Секреты DetectChanges Часть 1: Что делает DetectChanges?
Секреты DetectChanges Часть 2: Когда автоматически вызывается DetectChanges?
Возможная проблема с кэшированием состояния объекта Состояние объекта EF Core 2.0.2
Работа с отключенным графом сущностей в Entity Framework Core
Entity Framework Core TrackGraph для отключенных данных