Отменить изменения в сущностях Entity framework
это может быть тривиальный вопрос, но: поскольку ADO.NET Entity framework автоматически отслеживает изменения (в созданных сущностях) и поэтому сохраняет исходные значения, как я могу откатить изменения, внесенные в объекты сущности?
У меня есть форма, которая позволяет пользователю редактировать набор "заказчиком" лиц в виде сетки.
теперь у меня есть две кнопки " Принять "и" вернуться": если" принять " нажата, я называю Context.SaveChanges()
и измененные объекты записываются обратно в базу данных. Если щелкнуть "Revert", я хотел бы, чтобы все объекты получили свои исходные значения свойств. Какой будет код для этого?
спасибо
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, но это очень хорошая и простая реализация и позволяет избежать попадания в БД. Я не могу сказать, есть ли у этого хит производительности.