撤消实体框架实体中的更改

Posted

技术标签:

【中文标题】撤消实体框架实体中的更改【英文标题】:Undo changes in entity framework entities 【发布时间】:2011-07-24 21:54:21 【问题描述】:

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

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

现在我有两个按钮“Accept”和“Revert”:如果单击“Accept”,我调用Context.SaveChanges() 并将更改的对象写回数据库。如果单击“还原”,我希望所有对象都获得其原始属性值。代码是什么?

谢谢

【问题讨论】:

【参考方案1】:

我们使用的是带有 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);
            
        
    

我希望这对其他人有所帮助。

【讨论】:

【参考方案2】:

在 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;
        
    
 

【讨论】:

谢谢 - 这对我很有帮助! 您也应该将原始值设置为已删除的条目。您可能先更改了一个项目,然后再将其删除。 State 设置为 EntityState.Unchanged 将使用 Original Values 覆盖所有值,因此无需调用 SetValues 方法。 这个答案的更简洁的版本:***.com/a/22098063/2498426 伙伴,这太棒了!我所做的唯一修改是使用 Entries() 的通用版本,以便它适用于我的存储库。这给了我更多的控制权,我可以回滚每个实体类型。谢谢!【参考方案3】:
// 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

【讨论】:

【参考方案4】:

上面的一些不错的想法,我选择实现ICloneable然后一个简单的扩展方法。

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

用作:

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

通过这种方式,我能够克隆我的产品实体列表,为每件商品应用折扣,而不必担心还原原始实体上的任何更改。无需与 DBContext 交谈并要求刷新或使用 ChangeTracker。您可能会说我没有充分利用 EF6,但这是一个非常好的和简单的实现,并且避免了 DB 命中。我不能说这是否会影响性能。

【讨论】:

【参考方案5】:

这是 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】:

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

public void Rollback()

    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);

【讨论】:

创建单个实体对象的时间...这是几毫秒(50 毫秒)。根据集合的大小,循环遍历集合可能更快或更长时间。与 O(n) 相比,性能方面的 O(1) 很少成为问题。 Big O notation 不跟随你 - 处理和重建连接的性能。我在现有项目上对其进行了测试,它完成的速度比Rollback 过程要快一些,如果想要恢复整个数据库状态,它会是更好的选择。回滚可以选择樱桃。 'n' 表示对象的数量。重新创建连接大约需要 50 毫秒... O(1) 意味着它始终是同一时间50ms+0*n= 50ms。 O(n) 表示性能受对象数量的影响...性能可能是2ms+0.5ms*n...所以低于 96 个对象会更快,但时间会随着数据量线性增加。 如果您不打算挑选(不)回滚的内容,只要您不担心带宽,这就是您要走的路。【参考方案7】:
dbContext.Entry(entity).Reload();

根据MSDN:

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

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

网络流量 数据库过载 应用程序响应时间增加

【讨论】:

【参考方案8】:

我发现这在我的上下文中运行良好:

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

【讨论】:

我相信这将阻止实体的更改在调用DbContext.SaveChanges() 时持续存在,但它不会将实体值返回到原始值。如果实体状态在以后的更改中被修改,那么之前的所有修改可能会在保存时保持不变? 检查此链接 code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 它说将实体设置为 Unchaged 状态会“在幕后”恢复原始值。【参考方案9】:

“这对我有用:

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

其中item 是要还原的客户实体。"


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

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

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

ClientWins 也不是一个好主意,因为触发 .SaveChanges 会将“丢弃”的更改提交到数据源。

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

问候,

恩里克·克劳斯

【讨论】:

【参考方案10】:

这对我有用:

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

其中item 是要还原的客户实体。

【讨论】:

【参考方案11】:

就我而言,更好的方法是在每个要撤消更改的实体上设置EntityState.Unchanged。这样可以确保在 FK 上还原更改并且语法更清晰。

【讨论】:

注意:如果实体再次更改,更改将返回。【参考方案12】:

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

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

【讨论】:

@LadislavMrnka 当然Context.Refresh() 是您声称没有还原操作的反例?使用Refresh() 似乎比处理上下文并丢失所有跟踪更改更好的方法(即更容易针对特定实体)。 @robjb:不。刷新只能刷新您手动定义的单个实体或实体集合,但刷新功能仅影响简单属性(不影响关系)。它也不能解决添加或删除实体的问题。

以上是关于撤消实体框架实体中的更改的主要内容,如果未能解决你的问题,请参考以下文章

实体框架 - 重新开始 - 撤消/回滚所有迁移

如何在实体框架中更改数据库名称

如何从实体框架 6 中的 Auditlog 实体获取 id

在实体框架中更改命名空间

实体框架不保存更改

实体框架更改 Id 类型