2011-03-29 81 views
87

这可能是一个微不足道的问题,但是:由于ADO.NET实体框架自动跟踪更改(在生成的实体中)并因此保留原始值,因此我如何回滚对实体对象所做的更改?撤消实体框架实体中的更改

我有一个表格,允许用户在网格视图中编辑一组“客户”实体。

现在我有两个按钮“接受”和“恢复”:如果“接受”被点击时,我打电话Context.SaveChanges()和改变对象写入到数据库中。如果单击“恢复”,我希望所有对象都能获得其原始属性值。那会是什么代码?

谢谢

回答

57

在EF中没有恢复或取消更改操作。每个实体在ObjectStateManager中有ObjectStateEntry。状态条目包含原始值和实际值,因此您可以使用原始值来覆盖当前值,但必须为每个实体手动执行。它不会对导航属性/关系的变化表示敬意。

“恢复更改”的常见方式是处理上下文并重新加载实体。如果您想避免重新加载,您必须创建实体克隆并在新的对象上下文中修改这些克隆。如果用户取消更改,您仍将拥有原始实体。

+2

谢谢。这似乎是我的一些通用解决方案的候选人,虽然... – MartinStettner 2011-03-29 06:21:01

+4

@LadislavMrnka当然'Context.Refresh()'是你的声明没有回复操作的反例吗?使用'Refresh()'似乎是一种更好的方法(即更容易针对特定实体),而不是处理上下文并丢失所有跟踪的更改。 – Rob 2012-02-14 19:21:57

+14

@robjb:不。刷新只能刷新您手动定义的单个实体或实体集合,但刷新功能仅影响简单属性(而非关系)。它也不能解决添加或删除实体的问题。 – 2012-02-14 20:38:17

2

对于我来说,更好的方法是将EntityState.Unchanged设置为您想撤消更改的每个实体。这可以确保FK上的更改得到恢复,并且语法更清晰。

+4

注意:如果实体再次更改,则更改将返回。 – 2012-03-05 16:03:05

16

这为我工作:

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

哪里item是客户实体被还原。

3

“这为我工作:

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

哪里item是客户实体被还原。”


我已经在SQL Azure中与ObjectContext.Refresh所做的试验,而“RefreshMode.StoreWins”火灾的查询与数据库为每个实体,并导致业绩泄漏。基于微软文档():

ClientWins:在对象上下文中对对象所做的属性更改不会被来自数据源的值替换。在下次调用SaveChanges时,这些更改将发送到数据源。

StoreWins:对对象上下文中对象所做的属性更改将使用数据源中的值替换。

ClientWins既不是一个好的理想,因为发射.SaveChanges将提交对数据源的“丢弃”更改。

我不知道什么是最好的方法,因为处置上下文并创建一个新的上下文会导致一个异常消息:当底层提供程序尝试在创建的新上下文上运行任何查询时,“底层提供程序在打开时失败” 。

问候,

恩里克克劳辛

2

我发现这在我的情况下是工作的罚款:

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

+1

我相信这会阻止对实体的更改继续调用'DbContext.SaveChanges()',但它不会将实体值返回到原始值。如果实体状态从以后的更改中被修改,可能所有先前的修改都会在保存后保留下来? – 2012-12-26 04:37:34

+1

检查此链接http://code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 它说,将一个实体设置为Unchaged状态会恢复“隐藏”下的原始值。 – Hannish 2013-10-21 13:56:41

25
dbContext.Entry(entity).Reload(); 

Accroding到MSDN

重新加载数据库中的实体,使用数据库中的值覆盖任何属性值。调用此方法后,实体将处于未更改 状态。

注意,通过请求数据库恢复也有一些缺点:

  • 网络流量
  • DB过载
  • 增加应用程序响应时间
109

查询的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

谢谢 - 这真的帮助了我! – Matt 2013-10-22 11:13:03

+0

你是最棒的! – 2013-11-18 18:15:04

+4

您应该也可以将原始值设置为已删除的条目。有可能你先改变了一个项目然后删除它。 – 2014-03-07 20:21:49

11

简单的方法没有跟踪任何更改。它应该比查看每个实体更快。

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

确实很简单,但性能? – majkinetor 2014-07-01 13:19:02

+0

创建单个实体对象的时间......这是几毫秒(50毫秒)。通过收集循环可能会更快或更长,具体取决于它的大小。与O(n)相比,性能明智的O(1)很少成为问题。 [大O符号](http://en.wikipedia.org/wiki/Big_O_notation) – Guish 2014-07-02 18:46:05

+0

不遵循你 - 处理和重新创建连接的性能。我在现有的项目上测试了它,并且它比'Rollback'过程快了一点,如果想要恢复整个数据库状态,这将是更好的选择。回滚可能樱桃采取寿。 – majkinetor 2014-07-03 12:02:44

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

0

上面的一些好主意,我选择实现ICloneable,然后是一个简单的扩展方法。

这里找到:How do I clone a generic list in C#?

为了用作:

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

这样我就能够克隆我的产品的实体名单,申请折扣的每个项目,而不必担心恢复任何原始实体的变化。无需与DBContext交谈并要求刷新或使用ChangeTracker。你可能会说我没有充分利用EF6,但这是一个非常好的和简单的实现,并避免了数据库命中。我不能说这是否有性能问题。

1

我们使用EF 4和Legacy Object上下文。上述解决方案都没有直接回答我的问题 - 尽管通过推动我朝着正确的方向发展,从长远来看它是应对这个问题的。

我们不能只处理和重建上下文,因为我们在内存中挂起的一些对象(该死的加载!!)仍然附加到上下文,但有孩子尚未成为可能,加载。对于这些情况,我们需要将所有内容都恢复到原始值,而不用敲打数据库并且不删除现有连接。

下面是我们解决这个同样的问题:

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

我希望这可以帮助别人。