Отменить изменения в сущностях Entity framework


это может быть тривиальный вопрос, но: поскольку ADO.NET Entity framework автоматически отслеживает изменения (в созданных сущностях) и поэтому сохраняет исходные значения, как я могу откатить изменения, внесенные в объекты сущности?

У меня есть форма, которая позволяет пользователю редактировать набор "заказчиком" лиц в виде сетки.

теперь у меня есть две кнопки " Принять "и" вернуться": если" принять " нажата, я называю Context.SaveChanges() и измененные объекты записываются обратно в базу данных. Если щелкнуть "Revert", я хотел бы, чтобы все объекты получили свои исходные значения свойств. Какой будет код для этого?

спасибо

12 94

12 ответов:

в EF нет операции возврата или отмены изменений. Каждая сущность имеет ObjectStateEntry на ObjectStateManager. Запись состояния содержит исходные и фактические значения, поэтому вы можете использовать исходные значения для перезаписи текущих значений, но вы должны сделать это вручную для каждой сущности. Он не будет возвращать изменения в свойствах / отношениях навигации.

общий способ "отменить изменения" - это удаление контекста и перезагрузка сущностей. Если вы хотите избежать перезагрузки, вы должны создать клоны сущностей и изменить их клоны в новом контексте объекта. Если пользователь отменит изменения, у вас все еще будут исходные объекты.

запрос 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;
        }
    }
 }
dbContext.Entry(entity).Reload();

Accroding to MSDN:

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

обратите внимание, что возврат через запрос к базе данных имеет некоторые недостатки:

  • сетевой трафик
  • ДБ перегрузки
  • увеличенный ответ применения время

это сработало для меня:

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

здесь item является объектом клиента, который должен быть возвращен.

простой способ без отслеживания каких-либо изменений. Это должно быть быстрее, чем смотреть на каждую сущность.

public void Rollback()
{
    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);
}
// 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; 
    } 
} 

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

"это сработало для меня:

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

здесь item является объектом клиента, который должен быть возвращен."


Я сделал тесты с ObjectContext.Обновление в SQL Azure и "RefreshMode.StoreWins " запускает запрос к базе данных для каждого объекта и вызывает утечку производительности. На основе документации Microsoft ():

ClientWins : изменения свойств объектов в контексте объекта не заменяются значениями из источника данных. На при следующем вызове SaveChanges эти изменения отправляются в источник данных.

StoreWins : изменения свойств объектов в контексте объекта заменяются значениями из источника данных.

ClientWins тоже не очень хорошая идея, потому что стрельба .SaveChanges зафиксирует "отброшенные" изменения в источнике данных.

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

С уважением,

Энрики От Clausing

Что касается меня, лучший способ сделать это-установить EntityState.Unchanged на каждом объекте, который вы хотите отменить изменения. Это гарантирует, что изменения будут возвращены на FK и имеет немного более четкий синтаксис.

Я нашел, что это работает нормально в моем контексте:

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

Это пример того, о чем говорит 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]); 
        }
    }
}

мы используем EF 4, с контекстом устаревшего объекта. Ни одно из вышеперечисленных решений прямо не ответило на это для меня, хотя в конечном итоге оно ответило на это, подтолкнув меня в правильном направлении.

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

ниже наше решение этой же проблемы:

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

Я надеюсь, что это помогает другим.

некоторые хорошие идеи выше, я решил реализовать ICloneable, а затем простой метод расширения.

найти здесь: как клонировать общий список в C#?

для использования в качестве:

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

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