【问题标题】:How to ignore a DbUpdateConcurrencyException when deleting an entity删除实体时如何忽略 DbUpdateConcurrencyException
【发布时间】:2013-10-18 04:21:31
【问题描述】:

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

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

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

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

例子

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

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

注意:Db.Configuration.ValidateOnSaveEnabled = false; 并没有像另一个线程建议的那样解决这个问题。

【问题讨论】:

    标签: entity-framework entity-framework-core


    【解决方案1】:

    怎么样?

    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

    【讨论】:

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

    您可以处理 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();
    }
    

    【讨论】:

    • 我看过refresh,但由于这将在多线程环境中以非常大的批次运行,我不想承担额外的读取数据的打击/成本第二次来自数据库。
    • 我真正需要的是 savechanges 忽略任何 DbUpdateConcurrencyException 错误并完成事务,而无需从数据库中重新读取实体。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      //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()ObjectContext.Refresh,因为我想忽略在另一个进程中删除的项目,而不需要任何额外的数据库开销。 我在 for 循环中添加了作为对无限循环的简单保护 - 这不是应该能够发生的事情,但如果可以避免的话,我是一个带大括号的人,而不是while(true) 的粉丝。 不需要像 isDonesaveFailed 这样的局部变量 - 如果我们成功保存,只需中断即可。 无需将 ex.Entries 强制转换为列表即可枚举它 - 仅仅因为您可以在一行上写一些东西并不能让它变得更好。

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

        • 谢谢。这不正是我对已接受答案的评论吗?
        • 很接近了。循环结构不同,我将分离每个失败的条目,而不是将其限制为已删除的条目(这可能导致无限循环)。另外我建议创建一个新的SaveChanges 函数。
        【解决方案6】:

        这是我的方法:

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

        【讨论】:

          【解决方案7】:

          这是另一种方法:

                  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 数据库中不存在的就是无操作。但是请注意,一个 添加的实体可能尚未创建其永久键值。

          【讨论】:

          • 如果修改了多个条目,var entity = ex.Entries.Single(); 将引发异常。见-docs.microsoft.com/en-us/dotnet/api/…
          • @herostwist ,在我们删除Principal 实体的情况下,这可能是真的,但如果我们的实体是Dependent,仅仅依靠Single() 方法就足够了。
          猜你喜欢
          • 2011-06-28
          • 1970-01-01
          • 2014-01-24
          • 1970-01-01
          • 2021-12-20
          • 1970-01-01
          • 1970-01-01
          • 2018-01-27
          • 2022-11-21
          相关资源
          最近更新 更多