Как игнорировать исключение DbUpdateConcurrencyException при удалении объекта

У меня есть приложение, которое считывает много данных в память и обрабатывает их в пакетах.

Я хочу, чтобы инфраструктура entity игнорировала DbUpdateConcurrencyException при удалении сущности, которая уже была удалена.

Причина в том, что к тому моменту, когда объект был обработан и помечен для удаления, он, возможно, уже был удален из БД.

Неверное удаление строки, которая уже была удалена, не является проблемой и не должна вызывать ошибку, мне просто нужен способ определить структуру сущности, которая:

Пример

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

Вызывает ошибку, если itemToRemove уже удален.

Примечание: Db.Configuration.ValidateOnSaveEnabled = false; не исправляет это, как предлагается другой поток.

Ответ 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);

Подробнее здесь: Устранение оптимистических concurrency исключений

Ответ 2

Вы можете обрабатывать 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

На основе кода 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

Вот что я использую. Отсоедините все записи проблем после сохранения.

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