DbSet.Attach(entity) vs DbContext.Entry(entity).State = EntityState.Modified

Когда я нахожусь в отключенном сценарии и получаю dto от клиента, который я сопоставляю с сущностью для его сохранения, я делаю это:

context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();

Для чего тогда DbSet.Attach(entity)

или почему я должен использовать метод .Attach, когда EntityState.Modified уже прикрепляет объект?

Ответ 1

Когда вы выполняете context.Entry(entity).State = EntityState.Modified;, вы не только присоединяете объект к DbContext, но также отмечаете всю сущность как грязную. Это означает, что если вы выполните context.SaveChanges(), EF сгенерирует оператор обновления, который обновит все поля объекта.

Это не всегда желательно.

С другой стороны, DbSet.Attach(entity) привязывает объект к контексту без, обозначая его грязным. Это эквивалентно выполнению context.Entry(entity).State = EntityState.Unchanged;

При подключении этого способа, если вы не приступите к обновлению свойства объекта, при следующем вызове context.SaveChanges() EF не будет генерировать обновление базы данных для этого объекта.

Даже если вы планируете сделать обновление для сущности, если у объекта много свойств (столбцы db), но вы хотите только обновить несколько, вам может показаться выгодным сделать DbSet.Attach(entity), и затем обновите только несколько свойств, требующих обновления. Выполнение этого способа приведет к созданию более эффективного оператора обновления из EF. EF будет только обновлять измененные вами свойства (в отличие от context.Entry(entity).State = EntityState.Modified;, который приведет к обновлению всех свойств/столбцов)

Соответствующая документация: Добавить/Прикрепить и сущности.

Пример кода

Скажем, у вас есть следующее сущность:

public class Person
{
    public int Id { get; set; } // primary key
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Если ваш код выглядит так:

context.Entry(personEntity).State = EntityState.Modified;
context.SaveChanges();

Созданный SQL будет выглядеть примерно так:

UPDATE person
SET FirstName = 'whatever first name is',
    LastName = 'whatever last name is'
WHERE Id = 123; -- whatever Id is.

Обратите внимание, что указанный выше оператор обновления будет обновлять все столбцы независимо от того, действительно ли вы изменили значения или нет.

В отличие от этого, если ваш код использует "нормальный" Attach следующим образом:

context.People.Attach(personEntity); // State = Unchanged
personEntity.FirstName = "John"; // State = Modified, and only the FirstName property is dirty.
context.SaveChanges();

Затем сгенерированный оператор обновления отличается:

UPDATE person
SET FirstName = 'John'
WHERE Id = 123; -- whatever Id is.

Как вы можете видеть, оператор update только обновляет значения, которые были фактически изменены после присоединения объекта к контексту. В зависимости от структуры вашей таблицы это может оказать положительное влияние на производительность.

Теперь, какой вариант лучше для вас, целиком зависит от того, что вы пытаетесь сделать.

Ответ 2

Когда вы используете метод DbSet.Update, Entity Framework отмечает все свойства вашего объекта как EntityState.Modified, поэтому отслеживает их. Если вы хотите изменить только некоторые свои свойства, не все из них, используйте DbSet.Attach. Этот метод делает все ваши свойства EntityState.Unchanged, поэтому вы должны сделать свои свойства, которые хотите обновить EntityState.Modified. Таким образом, когда приложение попадает на DbContext.SaveChanges, оно будет работать только с модифицированными свойствами.