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

这可能是一个小问题,但是: 既然 Entity Framework 自动跟踪变更(在生成的实体中) ,因此保留了原始值,那么我如何回滚对实体对象所做的变更呢?

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

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

谢谢

108976 次浏览

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

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

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

这对我很有效:

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

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

“这对我很有效:

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

其中 item是要恢复的客户实体。”


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

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

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

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

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

问候,

亨利克 · 克劳丁

我发现这种方法在我的环境中很管用:

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

dbContext.Entry(entity).Reload();

根据 MSDN:

从数据库重新加载实体,用数据库中的值覆盖任何属性值。实体将保持不变 调用此方法后进行状态设置

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

  • 网络流量网络流量
  • 尸体超载
  • 增加的应用程序响应时间

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

不需要跟踪任何变化的简单方法。它应该比查看每个实体更快。

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

这就是 Mrnka 所说的一个例子。下面的方法用原始值覆盖实体的当前值,没有调出数据库。为此,我们使用 DbEntityEntry 的 OriginalValue 属性,并使用反射以通用方式设置值。(这在 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]);
}
}
}
// 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;
}
}

这招对我很管用。但是,您必须从上下文重新加载数据才能使用旧数据。来源 给你

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

在这里发现: 如何在 C # 中克隆一个通用列表?

用作:

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

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

我们正在使用 EF4和遗留对象上下文。以上的解决方案都没有直接回答我的这个问题——尽管从长远来看,它确实通过把我推向正确的方向来回答这个问题。

我们不能仅仅因为我们在内存中挂起的一些对象而释放和重新构建上下文(该死的延迟加载! !)仍然依附于上下文,但有孩子尚未被加载。对于这些情况,我们需要将所有内容恢复到原始值,而不会重击数据库,也不会丢弃现有的连接。

下面是我们对这个问题的解决方案:

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

我希望这能帮到其他人。

虽然这个问题早于实体框架核心,但 EF 核心开发人员已经为这个问题提供了一个简单的解决方案。

在 EF Core 5.0中,ChangeTracker现在提供了一种清除跟踪实体的方法,这种方法比分离所有已更改的实体更有效。

context.ChangeTracker.Clear()

停止跟踪所有当前追踪的实体。

DbContext 的设计目标是在较短的生命周期内为每个工作单元创建一个新实例。这种方式意味着当上下文在每个工作单元结束时被处置时,所有被跟踪的实体都被丢弃。但是,在创建新上下文实例不实际的情况下,使用此方法清除所有跟踪实体可能很有用。

这种方法应该总是优先于分离每个跟踪实体。分离实体是一个缓慢的过程,可能会有副作用。这种方法在清除上下文中的所有跟踪实体时效率更高。

请注意,此方法不生成 StateChanged 事件,因为实体没有单独分离。

Microsoft 文档