这可能是一个微不足道的问题但是:由于ADO.NET实体框架会自动跟踪更改(在生成的实体中)并因此保留原始值,如何回滚对实体对象所做的更改?
我有一个表单,允许用户在网格视图中编辑一组“客户”实体。
现在我有两个按钮“Accept”和“Revert”:如果单击“Accept”,我调用Context.SaveChanges()
并将更改的对象写回数据库。如果单击“Revert”,我希望所有对象获取其原始属性值。那个代码是什么?
由于
答案 0 :(得分:134)
查询DbContext的ChangeTracker以查找脏项。将已删除的项目状态设置为未更改,并添加要分离的项目。对于修改的项目,使用原始值并设置条目的当前值。最后将修改后的条目状态设置为不变:
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;
}
}
}
答案 1 :(得分:65)
EF中没有恢复或取消更改操作。每个实体在ObjectStateEntry
中都有ObjectStateManager
。状态条目包含原始值和实际值,因此您可以使用原始值覆盖当前值,但必须手动为每个实体执行此操作。它不会反映导航属性/关系的变化。
“还原更改”的常用方法是处理上下文和重新加载实体。如果要避免重新加载,则必须创建实体克隆并在新对象上下文中修改这些克隆。如果用户取消更改,您仍将拥有原始实体。
答案 2 :(得分:28)
dbContext.Entry(entity).Reload();
正在加载MSDN:
从数据库重新加载实体,用数据库中的值覆盖任何属性值。该实体将处于未更改状态 调用此方法后的状态。
请注意,将请求还原到数据库有一些缺点:
答案 3 :(得分:17)
这对我有用:
dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);
item
是要还原的客户实体。
答案 4 :(得分:12)
无需跟踪任何更改即可轻松实现。它应该比查看每个实体更快。
public void Rollback()
{
dataContext.Dispose();
dataContext= new MyEntities(yourConnection);
}
答案 5 :(得分: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;
}
}
它对我有用。但是,您必须从上下文重新加载数据以带来旧数据。来源here
答案 6 :(得分:3)
“这对我有用:
dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);
item
是要还原的客户实体。“
我在SQL Azure中使用ObjectContext.Refresh进行了测试,并且“RefreshMode.StoreWins”针对每个实体针对数据库触发查询并导致性能泄漏。基于microsoft documentation():
ClientWins:对象上下文中对象所做的属性更改不会替换为数据源中的值。在下次调用SaveChanges时,这些更改将发送到数据源。
StoreWins:对象上下文中对象所做的属性更改将替换为数据源中的值。
ClientWins也不是一个好的ideia,因为触发.SaveChanges将对数据源进行“丢弃”更改。
我还不知道最好的方法是什么,因为当我尝试在创建的新上下文上运行任何查询时,处理上下文并创建新的上下文会导致消息异常:“基础提供程序在打开时失败”。
的问候,
Henrique Clausing
答案 7 :(得分:2)
就我而言,更好的方法是在要撤消更改的每个实体上设置EntityState.Unchanged
。这样可以确保更改在FK上还原,并且语法更加清晰。
答案 8 :(得分:2)
我发现这在我的背景下工作得很好:
Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);
答案 9 :(得分:2)
这是Mrnka所谈论的一个例子。以下方法使用原始值覆盖实体的当前值,不调出数据库。我们通过使用DbEntityEntry的OriginalValues属性来完成此操作,并使用反射以通用方式设置值。 (这适用于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]);
}
}
}
答案 10 :(得分:0)
上面的一些好主意,我选择实现ICloneable然后是一个简单的扩展方法。
在此处找到:How do I clone a generic list in C#?
用作:
ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc);
通过这种方式,我能够克隆我的产品实体列表,对每个项目应用折扣,而不必担心还原原始实体的任何更改。无需与DBContext交谈并要求刷新或使用ChangeTracker。您可能会说我没有充分利用EF6,但这是一个非常好用且简单的实现,可以避免数据库命中。我不知道这是否会影响性能。
答案 11 :(得分:0)
我们正在使用带有Legacy Object上下文的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);
}
}
}
我希望这有助于其他人。