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

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

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

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

Спасибо

Ответ 1

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

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

Ответ 2

Query 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

dbContext.Entry(entity).Reload();

Включение MSDN:

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

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

  • сетевой трафик
  • Перегрузка DB
  • увеличенное время отклика приложения

Ответ 4

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

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

Где item - объект клиента, подлежащий возврату.

Ответ 5

Простой способ без отслеживания изменений. Он должен быть быстрее, чем смотреть на все сущности.

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

Ответ 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; 
    } 
} 

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

Ответ 7

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

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

Где item - объект клиента, подлежащий возврату.


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

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

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

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

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

С уважением,

Henrique Clausing

Ответ 8

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

Ответ 9

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

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

Ответ 10

Это пример того, о чем говорит Мннка. Следующий метод перезаписывает текущие значения сущности с исходными значениями, а не вызывает базу данных. Мы делаем это, используя свойство 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]); 
        }
    }
}

Ответ 11

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

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

Используется как:

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

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

Ответ 12

Мы используем 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);
            }
        }
    }

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

Ответ 13

context.TEntity.Local.Clear();