问题 删除实体时如何忽略DbUpdateConcurrencyException


我有一个应用程序,可以将大量数据读入内存并批量处理。

我想要的是实体框架要忽略 DbUpdateConcurrencyException 删除已删除的实体时。

原因是,当一个实体被处理并标记为删除时,它可能已经从数据库中删除。

不经意地删除已经删除的行不是问题,不应该导致错误,我只需要一种方法告诉实体框架:)

Db.Entry(itemToRemove).State = EntityState.Deleted;
Db.SaveChanges();

如果导致错误 itemToRemove 已被删除。

注意: Db.Configuration.ValidateOnSaveEnabled = false; 不会像另一个线程建议那样修复此问题。


3007
2017-10-10 12:02


起源



答案:


怎么样?

Db.Entry(itemToRemove).State = EntityState.Deleted;

bool saveFailed;
do
{
    saveFailed = false;
    try
    {
       Db.SaveChanges();
    }
    catch(DbUpdateConcurrencyException ex)
    {
       saveFailed = true;
       var entry = ex.Entries.Single();
       //The MSDN examples use Single so I think there will be only one
       //but if you prefer - do it for all entries
       //foreach(var entry in ex.Entries)
       //{
       if(entry.State == EntityState.Deleted)
          //When EF deletes an item its state is set to Detached
          //http://msdn.microsoft.com/en-us/data/jj592676.aspx
          entry.State = EntityState.Detached;
       else
          entry.OriginalValues.SetValues(entry.GetDatabaseValues());
          //throw; //You may prefer not to resolve when updating
       //}
    }
} while (saveFailed);

更多信息: 解决乐观并发异常


12
2017-10-10 12:07



看起来很有趣如果在调用savechanges时有很多要删除的项目会发生什么。我需要分离之前已删除的内容,然后继续保存/删除其他内容。 - herostwist
SaveChanges在事务中完成,因此在修复导致并发问题的最后一项之前,实际上不会删除任何内容 - Colin
啊哈,我明白了。在那种情况下我不能这样做... catch(DbUpdateConcurrencyException ex){ex.Entries.Where(e => e.State == EntityState.Deleted).ToList()。ForEach(e => e.State = EntityState .Detached); Db.SaveChanges(); } - herostwist
鉴于MSDN上的示例调用 ex.Entries.Single 那我假设 ex.Entries 只会包含一条记录,所以你的代码会做同样的事情...但也许你对这个假设不满意......我没有得到的是为什么你认为事务回滚阻止你这样写? - Colin
根据 msdn.microsoft.com/en-us/library/...  ex.Entries 包含无法保存的所有实体。我对while循环唯一的问题是,如果有一个实体因为除了以外的原因而无法保存,它将永远旋转 entry.State == EntityState.Deleted。 - herostwist


答案:


怎么样?

Db.Entry(itemToRemove).State = EntityState.Deleted;

bool saveFailed;
do
{
    saveFailed = false;
    try
    {
       Db.SaveChanges();
    }
    catch(DbUpdateConcurrencyException ex)
    {
       saveFailed = true;
       var entry = ex.Entries.Single();
       //The MSDN examples use Single so I think there will be only one
       //but if you prefer - do it for all entries
       //foreach(var entry in ex.Entries)
       //{
       if(entry.State == EntityState.Deleted)
          //When EF deletes an item its state is set to Detached
          //http://msdn.microsoft.com/en-us/data/jj592676.aspx
          entry.State = EntityState.Detached;
       else
          entry.OriginalValues.SetValues(entry.GetDatabaseValues());
          //throw; //You may prefer not to resolve when updating
       //}
    }
} while (saveFailed);

更多信息: 解决乐观并发异常


12
2017-10-10 12:07



看起来很有趣如果在调用savechanges时有很多要删除的项目会发生什么。我需要分离之前已删除的内容,然后继续保存/删除其他内容。 - herostwist
SaveChanges在事务中完成,因此在修复导致并发问题的最后一项之前,实际上不会删除任何内容 - Colin
啊哈,我明白了。在那种情况下我不能这样做... catch(DbUpdateConcurrencyException ex){ex.Entries.Where(e => e.State == EntityState.Deleted).ToList()。ForEach(e => e.State = EntityState .Detached); Db.SaveChanges(); } - herostwist
鉴于MSDN上的示例调用 ex.Entries.Single 那我假设 ex.Entries 只会包含一条记录,所以你的代码会做同样的事情...但也许你对这个假设不满意......我没有得到的是为什么你认为事务回滚阻止你这样写? - Colin
根据 msdn.microsoft.com/en-us/library/...  ex.Entries 包含无法保存的所有实体。我对while循环唯一的问题是,如果有一个实体因为除了以外的原因而无法保存,它将永远旋转 entry.State == EntityState.Deleted。 - herostwist


你可以处理 DbUpdateConcurrencyException 然后打电话 Refresh(RefreshMode,IEnumerable) 使用RefreshMode.StoreWins和已删除的实体作为参数。

try{
  Db.Entry(itemToRemove).State = EntityState.Deleted;
  Db.SaveChanges();
}
catch(DbUpdateConcurrencyException)
{
  IObjectContextAdapter adapter = Db;

  adapter.ObjectContext.Refresh(RefreshMode.StoreWins, context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Deleted));
  Db.SaveChanges();
}

3
2017-10-10 12:13



我看过了 refresh 但由于这将在多线程环境中以非常大的批量运行,我不想从db中第二次读取数据的附加命中/成本。 - herostwist
我真正需要的是保存更改忽略任何 DbUpdateConcurrencyException  错误并完成事务而无需从数据库中重新读取实体。 - herostwist


基于来自的代码 https://msdn.microsoft.com/en-US/data/jj592904 但是我添加了一个infite循环计数器(以防万一,你永远不知道,对吧?)并循环遍历异常列表中的所有条目。

var maxTriesCounter = 20;
bool saveFailed;
do
{
    saveFailed = false;
    maxTriesCounter--;
    try
    {
        context.SaveChanges();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        saveFailed = true;
        foreach (var entry in ex.Entries)
        {
            entry.Reload();
        }
    }
} while (saveFailed && maxTriesCounter > 0);

1
2018-01-04 09:38





这是我使用的。保存后分离所有问题记录。

Db.Entry(itemToRemove).State = EntityState.Deleted;

while(true)
    try {
        Db.SaveChanges();
        break;
    } catch (DbUpdateConcurrencyException ex) {
        ex.Entries.ToList().ForEach(x=>x.State=EntityState.Detached);
    }

或者,您可以将自定义SaveChanges函数添加到DbContext类,并在需要忽略这些错误时使用它。

    public int SaveChanges_IgnoreConcurrencyExceptions  () {
        while(true)
            try {
                return this.SaveChanges();
            } catch (DbUpdateConcurrencyException ex) {
                ex.Entries.ToList().ForEach(x => x.State=EntityState.Detached);
            }
    }

0
2018-04-06 16:37



谢谢。这不完全是我对已接受答案的评论吗? - herostwist
关了。循环结构是不同的,我正在分离每个失败的条目,而不是将其限制为已删除的条目(这可能导致无限循环)。我也建议做一个新的 SaveChanges 功能。 - Carter Medlin