this might be a trivial question but: Since ADO.NET entity framework automatically tracks changes (in generated entities) and therefore keeps the original values, how can I rollback changes made to the entity objects?
这可能是一个微不足道的问题,但是:自从ADO之后。NET实体框架自动跟踪更改(在生成的实体中),因此保留原始值,我如何回滚对实体对象所做的更改?
I have a form which allows the user to edit a set of "Customer" entities in a grid view.
我有一个表单,允许用户在网格视图中编辑一组“客户”实体。
Now I have two buttons "Accept" and "Revert": if "Accept" is clicked, I call Context.SaveChanges()
and the changed objects are written back to the database. If "Revert" is clicked, I would like for all objects to get their original property values. What would be the code for that?
现在我有两个按钮“接受”和“恢复”:如果单击“接受”,我将调用Context.SaveChanges()并将修改后的对象写入数据库。如果单击“恢复”,我希望所有对象都获得它们的原始属性值。它的代码是什么?
Thanks
谢谢
12 个解决方案
#1
59
There is no revert or cancel changes operation in EF. Each entity has ObjectStateEntry
in ObjectStateManager
. State entry contains original and actual values so you can use original values to overwrite current values but you must do it manually for each entity. It will not reveret changes in navigation properties / relations.
EF中没有还原或取消更改操作。每个实体在ObjectStateManager中都有ObjectStateEntry。状态项包含原始值和实际值,因此您可以使用原始值来覆盖当前值,但您必须为每个实体手动执行该值。它不会改变导航属性/关系的变化。
The common way to "revert changes" is disposing context and reload entities. If you want to avoid reloading you must create clones of entities and modify those clones in new object context. If user cancel changes you will still have original entities.
“恢复更改”的常见方法是处理上下文并重新加载实体。如果希望避免重载,必须创建实体的克隆并在新的对象上下文中修改这些克隆。如果用户取消更改,您仍将拥有原始实体。
#2
117
Query ChangeTracker of DbContext for dirty items. Set deleted items state to unchanged and added items to detached. For modified items, use original values and set current values of the entry. Finally set state of modified entry to unchanged:
针对脏项的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
25
dbContext.Entry(entity).Reload();
Accroding to MSDN:
根据MSDN:
Reloads the entity from the database overwriting any property values with values from the database. The entity will be in the Unchanged state after calling this method.
从数据库中重新加载实体,使用数据库中的值覆盖任何属性值。调用此方法后,实体将处于未更改状态。
Note that reverting through the request to database has some drawbacks:
请注意,将请求恢复到数据库有一些缺点:
- network traffic
- 网络流量
- DB overload
- DB过载
- the increased application response time
- 增加了应用程序响应时间
#4
17
This worked for me:
这工作对我来说:
dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);
Where item
is the customer entity to be reverted.
其中项是要返回的客户实体。
#5
11
Easy way without tracking any changes. It should be faster than looking at every entities.
不跟踪任何更改的简单方法。它应该比查看每个实体要快。
public void Rollback()
{
dataContext.Dispose();
dataContext= new MyEntities(yourConnection);
}
#6
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;
}
}
It worked for me. However you must to reload your data from the context to bring the old data. Source here
它为我工作。但是,您必须从上下文重新加载数据,以带来旧数据。源在这里
#7
3
"This worked for me:
“这工作对我来说:
dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);
Where item
is the customer entity to be reverted."
其中项目是要返回的客户实体。
I have made tests with ObjectContext.Refresh in SQL Azure, and the "RefreshMode.StoreWins" fires a query against database for each entity and causes a performance leak. Based on microsoft documentation ():
我已经用ObjectContext进行了测试。在SQL Azure和“RefreshMode”中刷新。StoreWins“针对每个实体向数据库发出查询,并导致性能泄漏。基于microsoft文档():
ClientWins : Property changes made to objects in the object context are not replaced with values from the data source. On the next call to SaveChanges, these changes are sent to the data source.
ClientWins:对对象上下文中的对象所做的属性更改不会被数据源中的值替换。在下一次调用SaveChanges时,这些更改被发送到数据源。
StoreWins : Property changes made to objects in the object context are replaced with values from the data source.
StoreWins:对对象上下文中的对象进行的属性更改会被来自数据源的值替换。
ClientWins isn't a good ideia neither, because firing .SaveChanges will commit "discarded" changes to the datasource.
ClientWins也不是一个好主意,因为触发. savechanges将向数据源提交“丢弃”更改。
I dont' know what's the best way yet, because disposing the context and creating a new one is caused a exception with message: "The underlying provider failed on open" when I try to run any query on a new context created.
我还不知道最好的方法是什么,因为当我试图在创建的新上下文上运行任何查询时,处理上下文并创建一个新的上下文就会导致一个异常:“底层提供者在打开时失败”。
regards,
问候,
Henrique Clausing
恩里克条款
#8
2
As for me, better method to do it is to set EntityState.Unchanged
on every entity you want to undo changes on. This assures changes are reverted on FK and has a bit more clear syntax.
对于我来说,更好的方法是设置EntityState。在每个要撤消更改的实体上保持不变。这确保了FK上的更改得到恢复,并具有更清晰的语法。
#9
2
I found this to be working fine in my context:
我发现这在我的背景下很有效:
Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);
Context.ObjectStateManager。ChangeObjectState(客户、EntityState.Unchanged);
#10
2
This is an example of what Mrnka is talking about. The following method overwrites an entity's current values with the original values and doesn't call out the database. We do this by making use of the OriginalValues property of DbEntityEntry, and make use of reflection to set values in a generic way. (This works as of EntityFramework 5.0)
这是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]);
}
}
}
#11
1
We are using EF 4, with the Legacy Object context. None of the above solutions directly answered this for me -- although it DID answer it in the long run by pushing me in the right direction.
我们使用的是EF 4,使用的是遗留对象上下文。以上的解决方案都没有直接回答我这个问题——尽管从长远来看,它的确通过推动我朝正确的方向前进来回答这个问题。
We can't just dispose and rebuild the context because some of the objects we have hanging around in memory (damn that lazy loading!!) are still attached to the context but have children that are yet-to-be-loaded. For these cases we need to bump everything back to original values without hammering the database and without dropping the existing connection.
我们不能仅仅处理和重新构建上下文,因为我们在内存中挂起的一些对象(该死的惰性装载!!)仍然附加到上下文,但是有尚未加载的子对象。对于这些情况,我们需要在不影响数据库和不删除现有连接的情况下,将所有内容恢复到原始值。
Below is our solution to this same issue:
下面是我们对这个问题的解决方案:
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);
}
}
}
I hope this helps others.
我希望这能帮助别人。
#12
0
Some good ideas above, I chose to implement ICloneable and then a simple extension method.
上面有一些好的想法,我选择了实现iclonable,然后是一个简单的扩展方法。
Found here: How do I clone a generic list in C#?
这里发现:如何在c#中克隆一个泛型列表?
To be used as:
用作:
ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc);
This way I was able to clone my product entities list, apply a discount to each item and not have to worry about reverting any changes on the original entity. No need to talk with the DBContext and ask for a refresh or work with the ChangeTracker. You might say I am not making full use of EF6 but this is a very nice and simple implementation and avoids a DB hit. I cannot say whether or not this has a performance hit.
这样,我就可以克隆我的产品实体列表,对每个项目都给予折扣,并且不必担心恢复对原始实体的任何更改。无需与DBContext对话并请求刷新或使用ChangeTracker。您可能会说我没有充分利用EF6,但是这是一个非常好的、简单的实现,避免了DB的影响。我不能说这是否对性能有影响。
#1
59
There is no revert or cancel changes operation in EF. Each entity has ObjectStateEntry
in ObjectStateManager
. State entry contains original and actual values so you can use original values to overwrite current values but you must do it manually for each entity. It will not reveret changes in navigation properties / relations.
EF中没有还原或取消更改操作。每个实体在ObjectStateManager中都有ObjectStateEntry。状态项包含原始值和实际值,因此您可以使用原始值来覆盖当前值,但您必须为每个实体手动执行该值。它不会改变导航属性/关系的变化。
The common way to "revert changes" is disposing context and reload entities. If you want to avoid reloading you must create clones of entities and modify those clones in new object context. If user cancel changes you will still have original entities.
“恢复更改”的常见方法是处理上下文并重新加载实体。如果希望避免重载,必须创建实体的克隆并在新的对象上下文中修改这些克隆。如果用户取消更改,您仍将拥有原始实体。
#2
117
Query ChangeTracker of DbContext for dirty items. Set deleted items state to unchanged and added items to detached. For modified items, use original values and set current values of the entry. Finally set state of modified entry to unchanged:
针对脏项的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
25
dbContext.Entry(entity).Reload();
Accroding to MSDN:
根据MSDN:
Reloads the entity from the database overwriting any property values with values from the database. The entity will be in the Unchanged state after calling this method.
从数据库中重新加载实体,使用数据库中的值覆盖任何属性值。调用此方法后,实体将处于未更改状态。
Note that reverting through the request to database has some drawbacks:
请注意,将请求恢复到数据库有一些缺点:
- network traffic
- 网络流量
- DB overload
- DB过载
- the increased application response time
- 增加了应用程序响应时间
#4
17
This worked for me:
这工作对我来说:
dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);
Where item
is the customer entity to be reverted.
其中项是要返回的客户实体。
#5
11
Easy way without tracking any changes. It should be faster than looking at every entities.
不跟踪任何更改的简单方法。它应该比查看每个实体要快。
public void Rollback()
{
dataContext.Dispose();
dataContext= new MyEntities(yourConnection);
}
#6
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;
}
}
It worked for me. However you must to reload your data from the context to bring the old data. Source here
它为我工作。但是,您必须从上下文重新加载数据,以带来旧数据。源在这里
#7
3
"This worked for me:
“这工作对我来说:
dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);
Where item
is the customer entity to be reverted."
其中项目是要返回的客户实体。
I have made tests with ObjectContext.Refresh in SQL Azure, and the "RefreshMode.StoreWins" fires a query against database for each entity and causes a performance leak. Based on microsoft documentation ():
我已经用ObjectContext进行了测试。在SQL Azure和“RefreshMode”中刷新。StoreWins“针对每个实体向数据库发出查询,并导致性能泄漏。基于microsoft文档():
ClientWins : Property changes made to objects in the object context are not replaced with values from the data source. On the next call to SaveChanges, these changes are sent to the data source.
ClientWins:对对象上下文中的对象所做的属性更改不会被数据源中的值替换。在下一次调用SaveChanges时,这些更改被发送到数据源。
StoreWins : Property changes made to objects in the object context are replaced with values from the data source.
StoreWins:对对象上下文中的对象进行的属性更改会被来自数据源的值替换。
ClientWins isn't a good ideia neither, because firing .SaveChanges will commit "discarded" changes to the datasource.
ClientWins也不是一个好主意,因为触发. savechanges将向数据源提交“丢弃”更改。
I dont' know what's the best way yet, because disposing the context and creating a new one is caused a exception with message: "The underlying provider failed on open" when I try to run any query on a new context created.
我还不知道最好的方法是什么,因为当我试图在创建的新上下文上运行任何查询时,处理上下文并创建一个新的上下文就会导致一个异常:“底层提供者在打开时失败”。
regards,
问候,
Henrique Clausing
恩里克条款
#8
2
As for me, better method to do it is to set EntityState.Unchanged
on every entity you want to undo changes on. This assures changes are reverted on FK and has a bit more clear syntax.
对于我来说,更好的方法是设置EntityState。在每个要撤消更改的实体上保持不变。这确保了FK上的更改得到恢复,并具有更清晰的语法。
#9
2
I found this to be working fine in my context:
我发现这在我的背景下很有效:
Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);
Context.ObjectStateManager。ChangeObjectState(客户、EntityState.Unchanged);
#10
2
This is an example of what Mrnka is talking about. The following method overwrites an entity's current values with the original values and doesn't call out the database. We do this by making use of the OriginalValues property of DbEntityEntry, and make use of reflection to set values in a generic way. (This works as of EntityFramework 5.0)
这是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]);
}
}
}
#11
1
We are using EF 4, with the Legacy Object context. None of the above solutions directly answered this for me -- although it DID answer it in the long run by pushing me in the right direction.
我们使用的是EF 4,使用的是遗留对象上下文。以上的解决方案都没有直接回答我这个问题——尽管从长远来看,它的确通过推动我朝正确的方向前进来回答这个问题。
We can't just dispose and rebuild the context because some of the objects we have hanging around in memory (damn that lazy loading!!) are still attached to the context but have children that are yet-to-be-loaded. For these cases we need to bump everything back to original values without hammering the database and without dropping the existing connection.
我们不能仅仅处理和重新构建上下文,因为我们在内存中挂起的一些对象(该死的惰性装载!!)仍然附加到上下文,但是有尚未加载的子对象。对于这些情况,我们需要在不影响数据库和不删除现有连接的情况下,将所有内容恢复到原始值。
Below is our solution to this same issue:
下面是我们对这个问题的解决方案:
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);
}
}
}
I hope this helps others.
我希望这能帮助别人。
#12
0
Some good ideas above, I chose to implement ICloneable and then a simple extension method.
上面有一些好的想法,我选择了实现iclonable,然后是一个简单的扩展方法。
Found here: How do I clone a generic list in C#?
这里发现:如何在c#中克隆一个泛型列表?
To be used as:
用作:
ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc);
This way I was able to clone my product entities list, apply a discount to each item and not have to worry about reverting any changes on the original entity. No need to talk with the DBContext and ask for a refresh or work with the ChangeTracker. You might say I am not making full use of EF6 but this is a very nice and simple implementation and avoids a DB hit. I cannot say whether or not this has a performance hit.
这样,我就可以克隆我的产品实体列表,对每个项目都给予折扣,并且不必担心恢复对原始实体的任何更改。无需与DBContext对话并请求刷新或使用ChangeTracker。您可能会说我没有充分利用EF6,但是这是一个非常好的、简单的实现,避免了DB的影响。我不能说这是否对性能有影响。