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

时间:2011-03-29 00:43:07

标签: .net entity-framework rollback

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

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

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

由于

12 个答案:

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

我希望这有助于其他人。