Отменить изменения в сущностных сущностях
Это может быть тривиальным вопросом, но: Поскольку инфраструктура сущности 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();