C# DbContext 丢弃更改而不处理
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16437083/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
DbContext discard changes without disposing
提问by Raheel Khan
I have a desktop client application that uses modal windows to set properties for hierarchical objects. Since this is a client application and access to the DbContext is not threaded, I use a long-running context on the main Form that gets passed around to modal children.
我有一个桌面客户端应用程序,它使用模式窗口来设置分层对象的属性。由于这是一个客户端应用程序并且对 DbContext 的访问不是线程化的,因此我在主窗体上使用了一个长时间运行的上下文,该上下文被传递给模态子级。
These modal windows use the PropertyGrid to display entity properties and also have cancel buttons. If any data is modified and the cancel button is pressed, the changes are reflected in the parent form (where I cannot dispose the DbContext object).
这些模式窗口使用 PropertyGrid 来显示实体属性,并且还有取消按钮。如果修改了任何数据并按下了取消按钮,则更改会反映在父窗体中(我无法在其中处理DbContext object)。
Is there a way to discard any changes made if the DbContext.SaveChanges()method has NOT been called?
如果DbContext.SaveChanges()没有调用该方法,有没有办法放弃所做的任何更改?
UPDATE:Entity Framework Version 4.4.
更新:实体框架版本 4.4。
采纳答案by Martin
How about wrapping it in a transaction?
把它包装在一个事务中怎么样?
using(var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })){
// Do something
context.SaveChanges();
// Do something else
context.SaveChanges();
scope.Complete();
}
回答by jure
You colud try to do it manually, something like this.. not sure this works for your scenario but you can give it a try:
您可以尝试手动执行此操作,就像这样.. 不确定这是否适用于您的场景,但您可以尝试一下:
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;
}
}
回答by jure
In the simple case of cancelling the changes made to properties of a single entity you can set the current values to the original values.
在取消对单个实体的属性所做的更改的简单情况下,您可以将当前值设置为原始值。
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;
or alternatively reload (but results in db hit)
或者重新加载(但导致数据库命中)
context.Entry(myEntity).Reload();
context.Entry(myEntity).Reload();
回答by Rayan Elmakki
You can apply this:
你可以应用这个:
context.Entry(TEntity).Reload();
I try it and its work well for me.
我尝试了它,它对我来说效果很好。
Note: This method (Reload) 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.
注意:此方法 ( Reload) 从数据库重新加载实体,用数据库中的值覆盖任何属性值。调用此方法后,实体将处于 Unchanged 状态。
回答by Sergey Shuvalov
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;
}
}
}
Update:
更新:
Some users suggest to add .ToList()to avoid 'collection was modified' exception.
But I believe there is a reason for this exception.
一些用户建议添加.ToList()以避免“集合被修改”异常。但我相信这个例外是有原因的。
How do you get this exception? Probably, you are using context in non threadsafe manner.
你如何得到这个异常?可能,您正在以非线程安全的方式使用上下文。
回答by Jerther
This is based on Surgey Shuvalov's answer. It adds support for navigation property changes.
这是基于 Surgey Shuvalov 的回答。它增加了对导航属性更改的支持。
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);
}
回答by Evgeny
I came across nasty surprise - call to ChangeTracker.Entries()crashes if you need to rollback changes due to exception in DbContext e.g.
我遇到了令人讨厌的惊喜 -如果由于 DbContext 中的异常而需要回滚更改,则调用ChangeTracker.Entries() 会崩溃,例如
System.InvalidOperationException:
'The property 'Id' on entity type 'TestEntity' is part of a key and so cannot be modified or marked as modified.
To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.'
so I came up with hacked version of manual rollback
所以我想出了手动回滚的黑客版本
public async Task RollbackChanges()
{
var oldBehavtheitroad = ChangeTracker.QueryTrackingBehavior;
var oldAutoDetect = ChangeTracker.AutoDetectChangesEnabled;
// this is the key - disable change tracking logic so EF does not check that there were exception in on of tracked entities
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
ChangeTracker.AutoDetectChangesEnabled = false;
var entries = ChangeTracker.Entries().ToList();
foreach (var entry in entries)
{
switch (entry.State)
{
case EntityState.Modified:
await entry.ReloadAsync();
break;
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;
}
}
ChangeTracker.QueryTrackingBehavior = oldBehavtheitroad;
ChangeTracker.AutoDetectChangesEnabled = oldAutoDetect;
}

