撤消entity framework实体中的更改

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

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

现在我有两个button“接受”和“恢复”:如果单击“接受”,我调用Context.SaveChanges()和更改的对象被写回数据库。 如果单击“还原”,我希望所有对象都能获得其原始属性值。 那代码是什么?

谢谢

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

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

为脏项目查询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; } } } 
 dbContext.Entry(entity).Reload(); 

感谢MSDN :

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

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

  • networkingstream量
  • 数据库过载
  • 增加的应用程序响应时间

这对我工作:

 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是要回复的客户实体。“


我使用SQL Azure中的ObjectContext.Refresh进行了testing,“RefreshMode.StoreWins”针对每个实体的数据库触发查询,并导致性能泄漏。 基于微软文档():

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

StoreWins:在对象上下文中对对象所做的属性更改将被来自数据源的值replace。

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

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

问候,

恩里克Clausing

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

我发现这在我的上下文中工作正常:

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

这是Mrnka正在谈论的一个例子。 以下方法用原始值覆盖实体的当前值, 不会调出数据库。 我们通过使用DbEntityEntry的OriginalValues属性来做到这一点,并且使用reflection来以通用的方式设置值。 (这适用于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和Legacy Object上下文。 以上所有的解决scheme都没有直接回答这个问题 – 尽pipe从长远的angular度来看,这个答案是正确的。

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

以下是我们对同一问题的解决scheme:

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

我希望这可以帮助别人。

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

在这里find: 我如何克隆C#中的通用列表?

用作:

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

通过这种方式,我可以克隆我的产品实体列表,为每个项目应用折扣,而不必担心恢复原始实体的任何更改。 无需与DBContext交谈,并要求刷新或使用ChangeTracker。 你可能会说我没有充分利用EF6,但这是一个非常好的和简单的实现,并避免数据库命中。 我不能说这是否有性能打击。