DbContext丢弃更改而不进行处理

Rah*_*han 52 .net c# entity-framework undo dbcontext

我有一个桌面客户端应用程序,它使用模态窗口来设置分层对象的属性.由于这是一个客户端应用程序,并且没有线程化对DbContext的访问,我在主Form上使用长时间运行的上下文传递给模态子项.

这些模态窗口使用PropertyGrid显示实体属性,还具有取消按钮.如果修改了任何数据并按下了取消按钮,则更改将反映在父表单中(我无法处理DbContext object).

如果DbContext.SaveChanges()没有调用该方法,有没有办法丢弃所做的任何更改?

更新:实体框架版本4.4.

Ser*_*lov 127

public void RejectChanges()
    {
        foreach (var entry in ChangeTracker.Entries())
        {
            switch (entry.State)
            {
                case EntityState.Modified:
                case EntityState.Deleted:
                    entry.State = EntityState.Modified; //Revert changes made to deleted entity.
                    entry.State = EntityState.Unchanged;
                    break;
                case EntityState.Added:
                    entry.State = EntityState.Detached;
                    break;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

更新:

有些用户建议添加.ToList()以避免"收集被修改"异常.但我相信这个例外是有原因的.

你怎么得到这个例外?您可能正在以非线程安全的方式使用上下文.

  • 在**Entity.Modified**案例中,您不需要将**CurrentValues**设置为**OriginalValues**.将状态更改为**Unchanged**将为您执行^.^! (7认同)
  • 对我来说,这是抛出“集合被修改..”的异常。将ChangeTracker.Entries()更改为ChangeTracker.Entries()。ToList()以避免发生异常。 (2认同)

小智 18

在取消对单个实体的属性所做的更改的简单情况下,您可以将当前值设置为原始值.

context.Entry(myEntity).CurrentValues.SetValues(context.Entry(myEntity).OriginalValues);
//you may also need to set back to unmodified -
//I'm unsure if EF will do this automatically
context.Entry(myEntity).State = EntityState.UnModified;
Run Code Online (Sandbox Code Playgroud)

或者重新加载(但导致db命中)

context.Entry(myEntity).Reload();

  • 您不需要将**CurrentValues**设置为**OriginalValues**.将实体**状态**改为**未改变**将为你做^.^! (2认同)

Mar*_*tin 11

如何在交易中包装它?

    using(var scope = new TransactionScope(TransactionScopeOption.Required,
        new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })){

        // Do something 
        context.SaveChanges();
        // Do something else
        context.SaveChanges();

        scope.Complete();
}
Run Code Online (Sandbox Code Playgroud)

  • 花了一段时间,但这是我们最终使用的。不过,@Gert Arnold 对该问题的评论应被视为最佳实践。 (2认同)

jur*_*ure 6

您可以尝试手动执行此操作,如下所示..不确定这是否适合您的场景,但您可以尝试一下:

public void UndoAll(DbContext context)
    {
        //detect all changes (probably not required if AutoDetectChanges is set to true)
        context.ChangeTracker.DetectChanges();

        //get all entries that are changed
        var entries = context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged).ToList();

        //somehow try to discard changes on every entry
        foreach (var dbEntityEntry in entries)
        {
            var entity = dbEntityEntry.Entity;

            if (entity == null) continue;

            if (dbEntityEntry.State == EntityState.Added)
            {
                //if entity is in Added state, remove it. (there will be problems with Set methods if entity is of proxy type, in that case you need entity base type
                var set = context.Set(entity.GeType());
                set.Remove(entity);
            }
            else if (dbEntityEntry.State == EntityState.Modified)
            {
                //entity is modified... you can set it to Unchanged or Reload it form Db??
                dbEntityEntry.Reload();
            }
            else if (dbEntityEntry.State == EntityState.Deleted)
                //entity is deleted... not sure what would be the right thing to do with it... set it to Modifed or Unchanged
                dbEntityEntry.State = EntityState.Modified;                
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 这是非常好的。我唯一改变的是,如果实体被删除,将实体状态设置为修改不会删除它。而是 dbEntityEntry.Reload(); 将提供所需的效果(就像修改实体的情况一样)。 (2认同)

小智 5

你可以应用这个:

context.Entry(TEntity).Reload();
Run Code Online (Sandbox Code Playgroud)

我尝试了它,它对我来说效果很好。

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

  • 它也不会通过外键刷新其他连接的实体。 (2认同)

Jer*_*her 5

这是基于舒瓦洛夫手术的答案。它增加了对导航属性更改的支持。

public void RejectChanges()
{
    RejectScalarChanges();
    RejectNavigationChanges();
}

private void RejectScalarChanges()
{
    foreach (var entry in ChangeTracker.Entries())
    {
        switch (entry.State)
        {
            case EntityState.Modified:
            case EntityState.Deleted:
                entry.State = EntityState.Modified; //Revert changes made to deleted entity.
                entry.State = EntityState.Unchanged;
                break;
            case EntityState.Added:
                entry.State = EntityState.Detached;
                break;
        }
    }
}

private void RejectNavigationChanges()
{
    var objectContext = ((IObjectContextAdapter)this).ObjectContext;
    var deletedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Where(e => e.IsRelationship && !this.RelationshipContainsKeyEntry(e));
    var addedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(e => e.IsRelationship);

    foreach (var relationship in addedRelationships)
        relationship.Delete();

    foreach (var relationship in deletedRelationships)
        relationship.ChangeState(EntityState.Unchanged);
}

private bool RelationshipContainsKeyEntry(System.Data.Entity.Core.Objects.ObjectStateEntry stateEntry)
{
    //prevent exception: "Cannot change state of a relationship if one of the ends of the relationship is a KeyEntry"
    //I haven't been able to find the conditions under which this happens, but it sometimes does.
    var objectContext = ((IObjectContextAdapter)this).ObjectContext;
    var keys = new[] { stateEntry.OriginalValues[0], stateEntry.OriginalValues[1] };
    return keys.Any(key => objectContext.ObjectStateManager.GetObjectStateEntry(key).Entity == null);
}
Run Code Online (Sandbox Code Playgroud)