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();
}    

Плюси

  • Можна вказати, які властивості змінюються
  • Перегляди не повинні містити кожну власність

Мінуси

  • 2 х запити в базі даних, щоб завантажити оригінал, а потім оновити його

Спосіб 2 - Завантажте оригінальний запис, встановіть змінені значення

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

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

Плюси

  • У базу даних надсилаються лише модифіковані властивості

Мінуси

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

Спосіб 3 - Приєднайте оновлений запис і встановіть стан EntityState.Modified

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

Плюси

  • 1 х запит до бази даних для оновлення

Мінуси

  • Неможливо вказати, які властивості змінюються
  • Перегляди повинні містити кожну власність

Питання

Моє запитання до вас, хлопці; Чи є чистий спосіб я досягти цього набору цілей?

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

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


13
Використовувати ViewModels та хороший механізм картографування? Ви отримуєте лише "властивості для оновлення", щоб заповнити свій погляд (а потім оновити). Ще буде два запити щодо оновлення (отримати оригінал + оновити його), але я б не назвав це "Con". Якщо це ваша єдина проблема з виступом, ви щаслива людина;)
Рафаель Алтаус

Дякую @ RaphaëlAlthaus, дуже вірний пункт. Я міг би це зробити, але мені потрібно створити операцію CRUD для ряду таблиць, тому я шукаю метод, який може безпосередньо працювати з моделлю, щоб врятувати мене, створюючи n-1 ViewModel для кожної моделі.
Stokedout

3
Ну а в моєму поточному проекті (також багато організацій) ми почали працювати над моделями, думаючи, що втратимо час на роботу з ViewModels. Зараз ми переходимо до ViewModels, і з (не мізерною) інфраструктурною роботою на початку, це набагато далеко, набагато зрозуміліше і простіше в обслуговуванні. І більш безпечні (не потрібно боятися зловмисних "прихованих полів" чи подібних подібних речей)
Рафаель Алтаус

1
І не більше (жахливо) ViewBags, щоб заповнити ваші DropDownLists (у нас є принаймні один DropDownList майже на всіх наших переглядах CRU (D) ...)
Рафаель Алтаус

Я думаю, ти маєш рацію, моє погане намагання не помітити ViewModels. Так, ViewBag часом здається трохи брудним. Зазвичай я йду на крок далі за блогом Dino Esposito і створюю InputModels теж, ремешок та підтяжки, але це працює досить добре. Просто означає 2 додаткові моделі на моделі - doh ;-)
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
@Foysal: Так, можна.
Ладислав Мрнка

5
Однією з проблем такого підходу є те, що ви не можете знущатися над db.Entry (), що є серйозною PITA. У EF є досить хороша насмішкова історія в інших місцях - це дуже дратує, що (наскільки я можу сказати) у них немає такої.
Кен Сміт

23
@Foysal Doing context.Entry (entity) .State = EntityState.Modified в одиночку достатньо, не потрібно робити вкладення. Він буде автоматично доданий як його змінений ...
HelloWorld

4
@ Sandman4, це означає, що будь-яке інше властивість має бути там і встановити поточне значення. У деяких проектах застосувань це неможливо.
Dan Esparza

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, воно не вдасться, якщо я не встановлю будь-яке значення, відмінне від null.
RolandoCC

Ви не отримаєте помилку, оскільки ці поля не будуть у вашій формі. Ви залишаєте поля, які ви точно не будете оновлювати, захоплюєте запис із бази даних, використовуючи форму, передану назад, приєднавши її, і повідомте, що ці поля не змінюються. Перевірка моделі контролюється в ModelState, а не в контексті. Цей приклад посилається на існуючого користувача, отже, "оновлений користувач". Якщо ваш SSN - це обов'язкове поле, воно було б там, коли воно було створене вперше.
smd

4
Якщо я правильно розумію, "updatedUser" - це екземпляр об'єкта, вже заповненого FirstOrDefault () або подібним, тому я оновлюю лише ті властивості, які я змінив, а інші встановлюю ISModified = false. Це чудово працює. Але те, що я намагаюся зробити, - це оновити об'єкт, не заповнюючи його спочатку, не роблячи жодного FirstOrDefault () перед оновленням. Це коли я отримую помилку, якщо я не вказую значення для всіх запитуваних полів, навіть якщо я встановив ISModified = false для цих властивостей. entry.Property (e => e.columnA) .IsModified = false; Без цього рядка стовпецьА не вийде.
RolandoCC

Те, що ви описуєте, - це створення нової сутності. Це стосується лише оновлення.
smd

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();

Це здається дійсно приємним рішенням - ні мюсю, ні суєти; вам не потрібно вказувати властивості вручну, і це враховує всі кулі ОП - чи є якась причина, що це не має більше голосів?
nocarrier

Незважаючи на це. Він має один з найбільших "мінусів", не один потрапляння до бази даних. Вам все одно доведеться завантажити оригінал з цією відповіддю.
smd

1
@smd чому ти кажеш, що він не раз потрапляє в базу даних? Я не бачу, що це відбувається, якщо використання SetValues ​​() не призведе до цього ефекту, але це не здається, що це було б правдою.
парламент

@par Parlament Я думаю, що, мабуть, спав, коли писав це. Вибачення. Фактична проблема - це перекриття призначеного нульового значення. Якщо оновлений користувач більше не має посилання на щось, було б неправильно замінити його початковим значенням, якщо ви мали намір очистити його.
smd

22

Я додав додатковий метод оновлення до базового класу мого сховища, схожий на метод оновлення, створений Scaffolding. Замість того, щоб встановити весь об'єкт на "модифікований", він встановлює набір окремих властивостей. (Т - загальний параметр класу.)

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

Залежно від випадку використання, застосовуються всі вищезазначені рішення. Ось як це я зазвичай роблю:

Щодо коду на сервері (наприклад, пакетного процесу), я зазвичай завантажую об'єкти та працюю з динамічними проксі-серверами. Зазвичай у пакетних процесах потрібно завантажувати дані будь-коли під час роботи служби. Я намагаюся завантажувати дані замість того, щоб використовувати метод пошуку, щоб заощадити деякий час. Залежно від процесу, я використовую оптимістичний або песимістичний контроль одночасності (я завжди використовую оптимістичний, за винятком паралельних сценаріїв виконання, де мені потрібно заблокувати деякі записи звичайними операторами sql, хоча це рідко). Залежно від коду та сценарію вплив може бути знижений майже до нуля.

Для сценаріїв клієнта у вас є кілька варіантів

  1. Використовуйте моделі перегляду. Моделі повинні мати властивість UpdateStatus (немодифікований-вставлений-оновлений-видалений). Відповідальність за клієнт є встановити правильне значення для цього стовпця залежно від дій користувача (вставити-оновити-видалити). Сервер може запитувати db на вихідні значення або клієнт повинен надсилати оригінальні значення на сервер разом із зміненими рядками. Сервер повинен приєднати початкові значення та використовувати стовпець UpdateStatus для кожного рядка, щоб вирішити, як обробити нові значення. У цьому сценарії я завжди використовую оптимістичну одночасність. Це зробить лише операції вставлення - оновлення - видалення, а не будь-які виділення, але може знадобитися якийсь розумний код для проходження графіка та оновлення об'єктів (залежить від вашого сценарію - додаток). Картограф може допомогти, але не обробляє логіку CRUD

  2. Використовуйте бібліотеку на зразок breeze.js, яка приховує більшу частину цієї складності (як описано в 1), і спробуйте пристосувати її до вашого випадку використання.

Сподіваюся, це допомагає

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.