Entity Framework 5 Обновление записи

870

Я изучал различные методы редактирования / обновления записи в Entity Framework 5 в среде ASP.NET MVC3, но пока ни один из них не помечает все нужные мне поля. Я объясню почему.

Я нашел три метода, которым я упомяну плюсы и минусы:

Способ 1 - загрузить исходную запись, обновить каждое свойство

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

Pros

  • Можно указать, какие свойства изменить
  • Представления не должны содержать каждое свойство

Cons

  • 2 х запросов к базе данных, чтобы загрузить оригинал, а затем обновить его

Способ 2 - загрузить исходную запись, установить измененные значения

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

Pros

  • Только измененные свойства отправляются в базу данных

Cons

  • Представления должны содержать каждое свойство
  • 2 х запросов к базе данных, чтобы загрузить оригинал, а затем обновить его

Способ 3 - Присоединить обновленную запись и установить состояние EntityState.Modified

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

Pros

  • 1 х запрос к базе данных для обновления

Cons

  • Не могу указать какие свойства изменить
  • Представления должны содержать каждое свойство

Вопрос

Мой вопрос к вам, ребята; Есть ли чистый способ, которым я могу достичь этого набора целей?

  • Можно указать, какие свойства изменить
  • Представления не должны содержать каждое свойство (например, пароль!)
  • 1 х запрос к базе данных для обновления

Я понимаю, что это довольно незначительная вещь, на которую можно указать, но я могу упустить простое решение этого вопроса. Если не метод один будет преобладать ;-)

Stokedout
источник
13
Использовать ViewModels и хороший картографический движок? Вы получаете только «свойства для обновления», чтобы заполнить ваше представление (а затем обновить). Там все еще будет 2 запроса на обновление (получить оригинал + обновить его), но я бы не назвал это "Con". Если это ваша единственная проблема с производительностью, вы счастливый человек;)
Raphaël Althaus
Спасибо @ RaphaëlAlthaus, очень верный момент. Я мог бы сделать это, но мне нужно создать операцию CRUD для нескольких таблиц, поэтому я ищу метод, который может работать с моделью напрямую, чтобы сохранить мое создание n-1 ViewModel для каждой модели.
Stokedout
3
Что ж, в моем текущем проекте (многие объекты тоже) мы начали с работы над моделями, думая, что потеряем время на работу с ViewModels. Теперь мы перейдем к ViewModels, и с (не пренебрежимо малой) работой инфраструктуры в начале, это намного, намного, намного яснее и проще в обслуживании. И более безопасный (не нужно бояться злых «скрытых полей» или подобных вещей)
Рафаэль Альтхаус
1
И больше никаких (ужасных) ViewBags для заполнения ваших DropDownLists (у нас есть хотя бы один DropDownList почти на всех наших представлениях CRU (D) ...)
Raphaël Althaus
Я думаю, что вы правы, мой плохой за попытку игнорировать ViewModels. Да, ViewBag иногда кажется немного грязным. Я обычно делаю еще один шаг, как описано в блоге Дино Эспозито, и создаю InputModels, немного пояса и фигурные скобки, но это работает довольно хорошо. Просто означает 2 дополнительные модели на модели - доу ;-)
Stokedout

Ответы:

681

Ты ищешь:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
Ладислав Мрнка
источник
59
привет @Ladislav Mrnka, если я хочу обновить все свойства сразу, могу ли я использовать приведенный ниже код? db.Departments.Attach (департамент); db.Entry (отдел) .State = EntityState.Modified; db.SaveChanges ();
Фойзул Карим
23
@ Фойсал: Да, вы можете.
Ладислав Мрнка
5
Одна из проблем этого подхода заключается в том, что вы не можете смоделировать db.Entry (), который является серьезным PITA. У EF есть довольно хорошая насмешливая история в другом месте - это довольно раздражает, что (насколько я могу судить) у них нет такой здесь.
Кен Смит
23
@Foysal Выполнение context.Entry (entity) .State = EntityState.Modified в одиночку - нет необходимости в присоединении. Он будет автоматически присоединен как его измененный ...
HelloWorld
4
@ Sandman4, это означает, что все остальные свойства должны быть там и быть установлены на текущее значение. В некоторых приложениях это невозможно.
Дэн Эспарза
176

Мне очень нравится принятый ответ. Я считаю, что есть еще один способ приблизиться к этому. Допустим, у вас есть очень короткий список свойств, которые вы не хотели бы включать в представление, поэтому при обновлении сущности они будут опущены. Скажем, эти два поля - Пароль и SSN.

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

Этот пример позволяет вам по существу оставить свою бизнес-логику в покое после добавления нового поля в таблицу «Пользователи» и в представление.

СМД
источник
Тем не менее, я получу сообщение об ошибке, если я не укажу значение свойства SSN, хотя для свойства IsModified установлено значение false, оно по-прежнему проверяет свойство на соответствие правилам модели. Поэтому, если свойство помечено как NOT NULL, оно завершится ошибкой, если я не установлю любое значение, отличное от нуля.
RolandoCC
Вы не получите сообщение об ошибке, потому что эти поля не будут в вашей форме. Вы пропускаете поля, которые вы точно не будете обновлять, извлекаете запись из базы данных, используя форму, переданную обратно, прикрепляя ее, и сообщаете записи, что эти поля не изменяются. Проверка модели контролируется в ModelState, а не в контексте. Этот пример ссылается на существующего пользователя, следовательно, "updatedUser". Если ваш SSN является обязательным полем, он был бы там, когда он был впервые создан.
поверхностный монтаж
4
Если я правильно понимаю, «updatedUser» - это экземпляр объекта, уже заполненного FirstOrDefault () или аналогичным, поэтому я обновляю только те свойства, которые я изменил, и устанавливаю для других значение ISModified = false. Это отлично работает. Но то, что я пытаюсь сделать, - это обновить объект, не заполняя его сначала, не выполняя FirstOrDefault () до обновления. Это происходит, когда я получаю сообщение об ошибке, если я не указываю значение для всех обязательных полей, даже если я установил ISModified = false для этих свойств. entry.Property (e => e.columnA) .IsModified = false; Без этой строки ColumnA потерпит неудачу.
RolandoCC
То, что вы описываете, создает новую сущность. Это относится только к обновлению.
поверхностный монтаж
1
RolandoCC, положить db.Configuration.ValidateOnSaveEnabled = false; перед db.SaveChanges ();
Вильки
28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();
Стефано Камисасси
источник
Это кажется действительно хорошим решением - без суеты и суеты; вам не нужно указывать свойства вручную, и он учитывает все маркеры ОП - есть ли какая-то причина, по которой у него больше голосов?
нокарьер
Хотя это не так. У него один из самых больших «минусов», более одного попадания в базу данных. Вам все равно придется загрузить оригинал с этим ответом.
smd
1
@smd, почему вы говорите, что он попадает в базу данных более одного раза? Я не вижу, чтобы это произошло, если только использование SetValues ​​() не дает такого эффекта, но не похоже, что это будет правдой.
парламент
@ Парламент, я думаю, я уже спал, когда писал это. Извиняюсь. Актуальная проблема - переопределение предполагаемого нулевого значения. Если у обновленного пользователя больше нет ссылки на что-либо, было бы неправильно заменить его исходным значением, если вы хотите очистить его.
smd
22

Я добавил дополнительный метод обновления в свой базовый класс репозитория, который похож на метод обновления, сгенерированный Scaffolding. Вместо того, чтобы устанавливать весь объект как «измененный», он устанавливает набор отдельных свойств. (T является универсальным параметром класса.)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

А потом позвонить, например:

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

Мне нравится одна поездка в базу данных. Вероятно, лучше сделать это с моделями представления, чтобы избежать повторения наборов свойств. Я еще этого не сделал, потому что я не знаю, как избежать переноса сообщений валидации моих валидаторов модели представления в мой доменный проект.

Ян Уорбертон
источник
Ага ... отдельный проект для моделей представления и отдельный проект для репозиториев, которые работают с моделями представления.
Ян Уорбертон
11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}
Мэтью Стивен Монкан
источник
Почему бы просто DbContext.Attach (obj); DbContext.Entry (obj) .State = EntityState.Modified;
nelsontruran
Это контролирует set часть оператора обновления.
Танвеер Бадар
4

Просто добавить в список опций. Вы также можете извлечь объект из базы данных и использовать инструмент автоматического сопоставления, такой как Auto Mapper, для обновления частей записи, которую вы хотите изменить.

Боствик
источник
3

В зависимости от вашего варианта применения применяются все вышеперечисленные решения. Вот как я обычно это делаю, однако:

Для серверного кода (например, пакетного процесса) я обычно загружаю сущности и работаю с динамическими прокси. Обычно в пакетных процессах вам необходимо загружать данные в любом случае во время работы службы. Я пытаюсь выполнить пакетную загрузку данных вместо использования метода find, чтобы сэкономить время. В зависимости от процесса я использую оптимистический или пессимистический контроль параллелизма (я всегда использую оптимистичный, за исключением сценариев параллельного выполнения, где мне нужно заблокировать некоторые записи с помощью простых операторов SQL, хотя это случается редко). В зависимости от кода и сценария влияние может быть уменьшено почти до нуля.

Для клиентских сценариев у вас есть несколько вариантов

  1. Используйте просмотр моделей. Модели должны иметь свойство UpdateStatus (не изменено-вставлено-обновлено-удалено). Клиент обязан установить правильное значение для этого столбца в зависимости от действий пользователя (insert-update-delete). Сервер может либо запросить в БД исходные значения, либо клиент должен отправить исходные значения на сервер вместе с измененными строками. Сервер должен прикрепить исходные значения и использовать столбец UpdateStatus для каждой строки, чтобы решить, как обрабатывать новые значения. В этом сценарии я всегда использую оптимистичный параллелизм. Это будет делать только операторы вставки - обновления - удаления, а не какие-либо операции выбора, но может потребоваться некоторый умный код для обхода графика и обновления сущностей (зависит от вашего сценария - приложения). Картограф может помочь, но не обрабатывает логику CRUD.

  2. Используйте такую ​​библиотеку, как breeze.js, которая скрывает большую часть этой сложности (как описано в 1), и попытайтесь приспособить ее к вашему варианту использования.

Надеюсь, поможет

Chriss
источник