17

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

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

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

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

例子

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

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

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

4

7 回答 7

20

怎么样?

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

更多内容: 解决乐观并发异常

于 2013-10-10T12:07:45.517 回答
3

您可以处理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();
}
于 2013-10-10T12:13:05.943 回答
2

基于来自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);
于 2016-01-04T09:38:32.810 回答
1

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

//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)它是否可以避免的粉丝。不需要像isDoneor这样的局部变量saveFailed——如果我们成功保存就简单地中断。无需ex.Entries为了枚举它而强制转换为列表 - 仅仅因为您可以在一行上写一些东西并不能使它变得更好。

于 2021-07-02T13:58:56.897 回答
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);
            }
    }
于 2017-04-06T16:37:36.437 回答
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)
            {
            }
        }

    }
于 2021-06-30T09:24:43.363 回答
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()微软文档的方法:

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

实体在调用该方法后会处于Unchanged状态,除非该实体在数据库中不存在,此时实体将被Detached。最后,对数据库中不存在的已添加实体调用 Reload 是无操作的。但是请注意,Added 实体可能尚未创建其永久键值。

于 2021-06-30T10:02:02.727 回答