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


116

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

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

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

谢谢

Answers:


69

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

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


4
@LadislavMrnka当然Context.Refresh()是您声称没有还原操作的反例吗?Refresh()与处理上下文并丢失所有跟踪的更改相比,使用似乎是一种更好的方法(即,更容易针对特定实体)。
罗布

14
@robjb:否。刷新只能刷新您手动定义的单个实体或实体集合,但是刷新功能仅影响简单的属性(不影响关系)。它还不能解决添加或删除实体的问题。
Ladislav Mrnka '02

153

查询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;
        }
    }
 }

3
谢谢-这真的帮助了我!
马特

5
您可能还应该将原始值设置为已删除的条目。您可能先更改了一项,然后将其删除了。
Bas de Raad 2014年

22
设置StateEntityState.Unchanged也会覆盖所有值,Original Values因此无需调用SetValues方法。
Abolfazl Hosnoddin 2014年

10
该答案的更清洁版本:stackoverflow.com/a/22098063/2498426
Jerther

1
伴侣,太棒了!我所做的唯一修改是使用Entries <T>()的通用版本,以便它可用于我的存储库。这给了我更多的控制权,并且我可以回滚每种实体类型。谢谢!
Daniel Mackay

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

进入MSDN

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

请注意,将请求还原到数据库有一些缺点:

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

17

这对我有用:

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

item要还原的客户实体在哪里。


12

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

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

创建单个实体对象的时间...几毫秒(50毫秒)。根据集合的大小,循环浏览可能更快或更长时间。与O(n)相比,性能方面的O(1)很少出现问题。大O符号
-Guish

不关注您-处理和重新创建连接的性能。我在现有项目上进行了测试,它的完成速度比上述Rollback过程要快一些,如果要还原整个数据库状态,这将是更好的选择。回滚可能会带来麻烦。
majkinetor 2014年

“ n”表示对象数。重新建立连接大约需要50毫秒... O(1)表示总是相同的时间50ms+0*n= 50ms。O(n)表示性能受对象数量的影响……性能可能2ms+0.5ms*n……因此,在96个以下的对象中,它会更快,但时间会随着数据量的增加而线性增加。
Guish 2014年

如果您不打算选择什么(不)回滚,那么只要您不担心带宽,这就是要走的路。
安东尼·尼科尔斯

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

它为我工作。但是,您必须从上下文重新加载数据以带来旧数据。来源在这里


3

“这对我有用:

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

item要还原的客户实体在哪里。”


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

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

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

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

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

问候,

亨里克·克劳斯


2

对于我来说,更好的方法是EntityState.Unchanged在要撤消更改的每个实体上进行设置。这样可以确保更改可以在FK上还原,并且语法更加清晰。


4
注意:如果再次更改实体,更改将恢复。
Nick Whaley 2012年

2

我发现这在我的情况下工作正常:

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


1
我相信这将防止对实体的更改在调用时持久存在DbContext.SaveChanges(),但不会将实体值恢复为原始值。并且,如果实体状态因后来的更改而被修改,那么保存时是否可能保留所有先前的修改?
卡尔·G

1
检查此链接code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4它说将实体设置为Unchaged状态可恢复“隐藏”状态下的原始值。
Hannish

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

1

我们将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);
            }
        }
    }

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


0

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

在这里找到:如何在C#中克隆通用列表?

用作:

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

这样,我可以克隆我的产品实体列表,对每个商品应用折扣,而不必担心还原原始实体上的任何更改。无需与DBContext交谈并要求刷新或使用ChangeTracker。您可能会说我没有充分利用EF6,但这是一个非常好的简单的实现,并且避免了数据库崩溃。我不能说这是否对性能有影响。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.