删除实体时如何忽略DbUpdateConcurrencyException

时间:2013-10-10 12:02:02

标签: entity-framework

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

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

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

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

实施例

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

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

注意:Db.Configuration.ValidateOnSaveEnabled = false;并不像另一个线程所暗示的那样解决这个问题。

7 个答案:

答案 0 :(得分:15)

怎么样?

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

更多信息: Resolving optimistic concurrency exceptions

答案 1 :(得分:3)

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

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

答案 2 :(得分:1)

基于来自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);

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

这是我的方法:

    public async Task DeleteItem(int id)
    {
        bool isDone = false;
        while (!isDone)
        {
            var item= await dbContext.Items.AsNoTracking().SingleOrDefaultAsync(x=> x.id== id);
            if (item== null)
                return;

            dbContext.Items.Delete(item);
            try
            {
                await dbContext.CommitAsync();
                return;
            }
            catch (DbUpdateConcurrencyException ex)
            {
            }
        }

    }

答案 5 :(得分:0)

这是另一种方法:

        context.Delete(item);
        bool saveFailed;
        do
        {
            saveFailed = false;

            try
            {
                await context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                saveFailed = true;
                var entity = ex.Entries.Single();
                await entity.Single().ReloadAsync();

                if (entity.State == EntityState.Unchanged)// entity is already updated
                    context.Delete(item);;
                else if (entity.State == EntityState.Detached) // entity is already deleted
                    saveFailed =false;
            }
        } while (saveFailed);

ReloadAsync() 方法从 Microsoft docs 开始:

<块引用>

从数据库重新加载实体并覆盖任何属性值 使用数据库中的值。

调用此方法后实体将处于Unchanged状态, 除非实体不存在于数据库中,在这种情况下 实体将被分离。最后,在添加的实体上调用 Reload 数据库中不存在的就是空操作。但是请注意,一个 添加的实体可能尚未创建其永久键值。

答案 6 :(得分:0)

我很久以前发布了这个问题,但最近引起了一些关注,所以我想我会添加我实际使用的解决方案。

${WORKDIR}

我考虑过的事情 - 我不想使用 //retry up to 5 times for (var retries = 0; retries < 5; retries++) { try { Db.SaveChanges(); break; } catch (DbUpdateConcurrencyException ex) { foreach (var entity in ex.Entries) { entity.State = EntityState.Detached; } } } ReloadAsync(),因为我想忽略在另一个进程中删除的项目,而不需要任何额外的数据库开销。 我在 for 循环中添加了一个简单的防止无限循环的保护措施 - 这不是应该能够发生的事情,但如果可以避免的话,我是一个腰带和大括号接近的人而不是 ObjectContext.Refresh 的粉丝。 不需要像 while(true)isDone 这样的局部变量 - 如果我们成功保存,只需中断。 无需将 saveFailed 强制转换为列表来枚举它 - 仅仅因为您可以在一行上写一些东西并不会使它变得更好。