Как ведет себя EF Core Modified Entity State? - PullRequest
0 голосов
/ 20 января 2019

Имеет ли значение, что мы помещаем состояние объекта = изменено после изменений или до внесения изменений?

using (var db = new LakshyaContext())
{
    foreach (var category in db.Categories)
    {
        db.Entry(category).State = EntityState.Modified; // before
        category.Count = 25; //Making Changes
        db.Entry(category).State = EntityState.Modified; //After
    }

    db.SaveChanges();
}

1 Ответ

0 голосов
/ 23 апреля 2019

Итак, во-первых, давайте уберем самую важную вещь:

Вы правы.В вашем примере вам не нужно вручную звонить 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.На верхнем уровне он работает следующим образом:

  1. DbContext делает снимок каждой загружаемой сущности
  2. Когда вызывается SaveChanges, он продолжает вызыватьDetectChanges, который сделает волшебство, чтобы выяснить, что изменилось или нет.
  3. 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 для отключенных данных

...