Скасувати зміни в сутностях фреймворку


116

це може бути тривіальним питанням, але: Оскільки структура сутності ADO.NET автоматично відстежує зміни (у створених об'єктах) і, таким чином, зберігає початкові значення, то як я можу відкатати зміни, внесені до об'єктів сутності?

У мене є форма, яка дозволяє користувачеві редагувати набір об'єктів "Клієнт" у вигляді сітки.

Тепер у мене є дві кнопки «Прийняти» та «Повернути»: якщо натиснути «Прийняти», я зателефоную, Context.SaveChanges()і змінені об’єкти записуються назад у базу даних. Якщо натиснути кнопку "Повернути", я б хотів, щоб усі об'єкти отримали свої початкові значення властивостей. Який би був код для цього?

Дякую

Відповіді:


69

У EF не відбувається операція відновлення чи скасування змін. Кожна організація має ObjectStateEntryв ObjectStateManager. Запис у штаті містить оригінальні та фактичні значення, тому ви можете використовувати початкові значення для перезапису поточних значень, але це потрібно робити вручну для кожної сутності. Він не буде шанувати зміни властивостей / відносин навігації.

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


4
@LadislavMrnka Безумовно, Context.Refresh()є протилежним прикладом вашого твердження про відсутність операції відновлення? Використання Refresh()здається кращим підходом (тобто легше орієнтуватися на конкретні суб'єкти), ніж розміщення контексту та втрата всіх відслідковуваних змін.
Роб

14
@robjb: Ні. Refresh може оновити лише одне ціле об'єднання або колекцію сутностей, яке ви вручну визначаєте, але функція оновлення впливає лише на прості властивості (не на відносини). Це також не вирішує проблему з доданими або видаленими об'єктами.
Ladislav Mrnka

153

Запит запитів ChangeTracker DbContext щодо брудних предметів. Встановіть стан видалених елементів як незмінний, а додані - до від'єднаних. Для модифікованих елементів використовуйте початкові значення та встановлюйте поточні значення запису. Нарешті встановіть стан зміненого запису незмінним:

public void RollBack()
{
    var context = DataContextFactory.GetDataContext();
    var changedEntries = context.ChangeTracker.Entries()
        .Where(x => x.State != EntityState.Unchanged).ToList();

    foreach (var entry in changedEntries)
    {
        switch(entry.State)
        {
            case EntityState.Modified:
                entry.CurrentValues.SetValues(entry.OriginalValues);
                entry.State = EntityState.Unchanged;
                break;
            case EntityState.Added:
                entry.State = EntityState.Detached;
                break;
            case EntityState.Deleted:
                entry.State = EntityState.Unchanged;
                break;
        }
    }
 }

3
Спасибі - це мені справді допомогло!
Метт

5
Напевно, ви також повинні встановити вихідні значення також для видалених записів. Можливо, ви спершу змінили елемент і видалили його після цього.
Бас де Раад

22
Налаштування Stateдля EntityState.Unchanged також замінить усі значення Original Values, тому немає необхідності викликати SetValuesметод.
Абольфазл Госноддін

10
Прибиральник версія цієї відповіді: stackoverflow.com/a/22098063/2498426
Jerther

1
Мате, це приголомшливо! Єдина зміна, яку я вніс, - це використовувати загальну версію Entries <T> (), щоб вона працювала для моїх сховищ. Це дає мені більше контролю, і я можу відкотитись за типом сутності. Дякую!
Даніель Макей

33
dbContext.Entry(entity).Reload();

Затвердження до MSDN :

Перезавантажує об'єкт із бази даних, перезаписуючи будь-які значення властивостей зі значеннями з бази даних. Після виклику цього методу суб'єкт перебуватиме в незмінному стані.

Зауважте, що повернення через запит до бази даних має деякі недоліки:

  • мережевий трафік
  • Перевантаження БД
  • збільшений час відповіді програми

17

Це працювало для мене:

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

Де itemсуб'єкт замовника повинен бути повернутий.


12

Простий спосіб без відстеження змін. Це має бути швидше, ніж дивитися на всі сутності.

public void Rollback()
{
    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);
}

Час створення єдиного об’єкта права ... який становить пару мс (50 мс). Прокручування колекції може бути швидшою чи довшою, залежно від її розміру. Ефективність роботи O (1) рідко є проблемою порівняно з O (n). Велика нотація
Guish

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

'n' означає кількість об'єктів. Відтворення з'єднання займає близько 50 мс ... O (1) означає, що це завжди той самий час 50ms+0*n= 50ms. O (n) означає, що на продуктивність впливає кількість об'єктів ... на ефективність можливо 2ms+0.5ms*n... тому нижче 96 об'єктів було б швидше, але час збільшуватиметься лінійно із кількістю даних.
Гайш

Якщо ви не збираєтеся вибирати вишню, що повертається назад, це шлях, якщо ви не турбуєтесь про пропускну здатність.
Ентоні Ніколс

6
// Undo the changes of all entries. 
foreach (DbEntityEntry entry in context.ChangeTracker.Entries()) 
{ 
    switch (entry.State) 
    { 
        // Under the covers, changing the state of an entity from  
        // Modified to Unchanged first sets the values of all  
        // properties to the original values that were read from  
        // the database when it was queried, and then marks the  
        // entity as Unchanged. This will also reject changes to  
        // FK relationships since the original value of the FK  
        // will be restored. 
        case EntityState.Modified: 
            entry.State = EntityState.Unchanged; 
            break; 
        case EntityState.Added: 
            entry.State = EntityState.Detached; 
            break; 
        // If the EntityState is the Deleted, reload the date from the database.   
        case EntityState.Deleted: 
            entry.Reload(); 
            break; 
        default: break; 
    } 
} 

Це працювало для мене. Однак ви повинні перезавантажити свої дані з контексту, щоб принести старі дані. Джерело тут


3

"Це працювало для мене:

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

Де itemсуб'єкт замовника має бути повернений ".


Я зробив тести з ObjectContext.Refresh у SQL Azure, і "RefreshMode.StoreWins" запускає запит на базу даних для кожної сутності та викликає витік продуктивності. На основі документації про мікрософт ():

ClientWins: Зміни властивості, внесені до об'єктів у контексті об'єкта, не замінюються значеннями з джерела даних. Під час наступного виклику SaveChanges ці зміни надсилаються до джерела даних.

StoreWins: Зміни властивостей, внесені до об'єктів у контексті об'єкта, замінюються значеннями з джерела даних.

ClientWins також не є хорошою ідеєю, тому що запуск .SaveChanges здійснить "відкинуті" зміни до джерела даних.

Я не знаю, який найкращий спосіб поки що, тому що розміщення контексту та створення нового викликає виняток із повідомленням: "Базовий постачальник не вдається відкрити", коли я намагаюся запустити будь-який запит у новому створеному контексті.

З повагою,

Анріке Клауз


2

Щодо мене, кращий спосіб зробити це - встановити EntityState.Unchangedкожну сутність, на якій потрібно скасувати зміни. Це запевняє, що зміни повернуті на FK і мають трохи більш чіткий синтаксис.


4
Примітка. Зміни повернуться, якщо об'єкт буде змінено знову.
Нік Уолі

2

Я вважав, що це добре працює в моєму контексті:

Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);


1
Я вважаю, що це не дасть змін суті зберігатися під час виклику DbContext.SaveChanges(), але не поверне значення сутності до початкових значень. І якщо стан сутності буде змінено з наступної зміни, можливо, всі попередні модифікації зберігатимуться після збереження?
Карл Г

1
Перевірте це посилання code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 У ньому йдеться про те, що встановлення сутності в стан Unchaged відновлює початкові значення "під обкладинками".
Ганніш

2

Це приклад того, про що говорить Mrnka. Наступний метод перезаписує поточні значення організації з вихідними значеннями і не викликає базу даних. Ми робимо це, використовуючи властивість OriginalValues ​​DbEntityEntry, і використовуємо відображення для загального встановлення значень. (Це працює як EntityFramework 5.0)

/// <summary>
/// Undoes any pending updates 
/// </summary>
public void UndoUpdates( DbContext dbContext )
{
    //Get list of entities that are marked as modified
    List<DbEntityEntry> modifiedEntityList = 
        dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).ToList();

    foreach(  DbEntityEntry entity in modifiedEntityList ) 
    {
        DbPropertyValues propertyValues = entity.OriginalValues;
        foreach (String propertyName in propertyValues.PropertyNames)
        {                    
            //Replace current values with original values
            PropertyInfo property = entity.Entity.GetType().GetProperty(propertyName);
            property.SetValue(entity.Entity, propertyValues[propertyName]); 
        }
    }
}

1

Ми використовуємо EF 4 з контекстом Legacy Object. Жодне з перерахованих вище рішень прямо для мене не відповіло - хоча це DID відповіло на це в довгостроковій перспективі, підштовхуючи мене в правильному напрямку.

Ми не можемо просто розпоряджатися та перебудовувати контекст, тому що деякі об'єкти, які ми зависаємо в пам'яті (чорт, що ледаче завантаження !!), все ще приєднані до контексту, але мають дітей, які ще завантажуються. У цих випадках нам потрібно повернути все до початкових значень, не забиваючи базу даних і не скидаючи існуюче з'єднання.

Нижче наше рішення цього ж питання:

    public static void UndoAllChanges(OurEntities ctx)
    {
        foreach (ObjectStateEntry entry in
            ctx.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached))
        {
            if (entry.State != EntityState.Unchanged)
            {
                ctx.Refresh(RefreshMode.StoreWins, entry.Entity);
            }
        }
    }

Я сподіваюся, що це допомагає іншим.


0

Деякі гарні ідеї вище, я вирішив реалізувати ICloneable, а потім простий метод розширення.

Знайдено тут: Як клонувати загальний список у C #?

Для використання в якості:

ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc);

Таким чином я зміг клонувати свій список організацій товарів, застосувати знижку на кожен товар і не потрібно турбуватися про повернення будь-яких змін у початковій сутності. Не потрібно розмовляти з DBContext і просити оновлення або працювати з ChangeTracker. Ви можете сказати, що я не використовую в повній мірі EF6, але це дуже приємна і проста реалізація і дозволяє уникнути попадання БД. Я не можу сказати, чи це хіт продуктивності.

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