Как я могу избежать исключения оптимистического параллелизма при удалении строк?

У меня есть метод, который получает идентификаторы некоторых строк для удаления. Я использую такой код:

public bool delete(IEnumerable<long> paramIeId)
{
    using(myContext)
    {
        foreach(long iterator in paramIeId)
        {
            nyDbContext.Remove(new MyType(){ID = iterator});
        }
    }
}

Он отлично работает, потому что удаляет строки, когда они существуют. Но если есть 1 или более несуществующих строк, я получаю исключение, и ни одна строка не удаляется, хотя некоторые из них существуют.

Если я выполняю этот запрос в T-SQL, у меня нет проблем, база данных удаляет существующие строки и игнорирует несуществующие строки, потому что в конце я хочу их удалить, поэтому, если другой процесс удалит их для меня, нет проблем.

Я мог бы обработать исключение оптимистичного параллелизма, обновляющее базу данных dbContextfrom, но я думаю, что это связано с выполнением дополнительных запросов, которых можно было бы избежать.

Есть ли способ, которым EF работает как T-SQL? Если я попытаюсь удалить несуществующую строку, проигнорирую ее и удалю остальные строки.

Спасибо.


person Álvaro García    schedule 19.11.2017    source источник
comment
Я не уверен, что это поведение можно отключить. EF видит, что ваша инструкция DELETE from затронула меньше строк, чем ожидалось, и выдает это исключение. В этом случае вам лучше просто выполнить необработанный SQL-запрос без EF.   -  person Evk    schedule 19.11.2017
comment
Это одно из нелогичных действий EF. Существует открытый запрос на улучшение Оптимистическая проверка параллелизма должна быть настраиваемой #6218, которая охватывает это, хотя, если вы спросите меня, поведение удаления по умолчанию не должно вызывать такое исключение.   -  person Ivan Stoev    schedule 19.11.2017
comment
Если paramIeId реализует IEnumerable‹T›, вы можете использовать foreach(длинный идентификатор в paramIeId.ToList()), чтобы предотвратить отложенное выполнение запроса, выполняемого LINQ для удаления элементов коллекции во время выполнения. Использование метода .ToList() заморозит список во время его повторения.   -  person Kunal Mukherjee    schedule 19.11.2017


Ответы (2)


По крайней мере, на данный момент исключение кажется неизбежным при использовании отсоединенных сущностей для выполнения удаления. Вам придется либо использовать try / catch и обработать исключение, либо запросить в БД совпадающие идентификаторы и удалить только совпадения1.

Пример с обработкой исключений

using (myContext)
{
    foreach (long iterator in paramIeId)
    {
        nyDbContext.Remove(new MyType() { ID = iterator });
    }

    try
    {
        nyDbContext.SaveChanges()
    }
    catch(DbUpdateConcurrencyException ex)
    {
        //if you want special handling for double delete
    }
}

Пример с запросом, затем удаление

Обратите внимание, что я запрашиваю весь список типов перед циклом, чтобы не делать отдельные запросы для каждого типа.

using (myContext)
{
    var existingMyTypes = nyDbContext.MyTypes.Where(x => paramIeId.Contains(x.ID));
    foreach (MyType existing in existingMyTypes)
    {
        nyDbContext.Remove(existing);
    }

    nyDbContext.SaveChanges();
}

1 ПРИМЕЧАНИЕ. Опция запроса, а затем удаления оставляет открытым возможное состояние гонки, которое может вызвать OptimisticConcurrencyException, к которому вы пытаетесь, а именно, если другой процесс/поток/программа удалит строки между чтением и чтением ваших собственных процессов. Удалить. Единственный способ полностью обработать эту возможность — обработать исключение в файле try / catch.

person just.another.programmer    schedule 26.11.2017
comment
Но в этом случае, как EF делает все или ничего, если есть только одна несуществующая запись, мне нужно обработать исключение, чтобы удалить записи, которые все еще существуют. - person Álvaro García; 08.01.2018
comment
@ ÁlvaroGarcía Ты прав. Не существует встроенного способа избежать повторного удаления при исключении параллелизма без выполнения всех операций удаления отдельно. Обратите внимание, что это не проблема при втором подходе (запрос полного списка существующих типов в память с последующим их удалением). - person just.another.programmer; 08.01.2018

Вам не нужно создавать новый объект, чтобы удалить его, просто позвольте EF сделать все за вас:

public bool delete(IEnumerable<long> paramIeId)
{
    using(var nyDbContext = new DbContext())
    {
        foreach(long id in paramIeId)
        {
            MyType myType = nyDbContext.MyTypes.FirstOrDefault(x => x.ID == id);
            if (myType != null)
            {
                nyDbContext.MyTypes.Remove(myType);
            }
        }
        nyDbContext.SaveChanges();
     }
}
person Isma    schedule 19.11.2017
comment
Этот ответ приведет к запросам к базе данных для каждого идентификатора в paramIeId, что может значительно повлиять на производительность. Код OP не делал никаких запросов перед удалением. - person just.another.programmer; 27.11.2017
comment
Вопрос был не в производительности, а в том, чтобы убедиться, что запись существует перед ее удалением, но вы правы, если ему нужно массово удалить много записей, это не лучшее решение, а только решение общего назначения stackoverflow .com/questions/2519866/ - person Isma; 27.11.2017
comment
ОП специально сказал, что он обеспокоен выполнением дополнительных запросов, если они не нужны. - person just.another.programmer; 27.11.2017
comment
Три отличия: 1) я выделил компромиссы использования запроса перед удалением, 2) я предложил подход, основанный на обработке исключений, который не имеет ненужных запросов, 3) в примере запроса я выполнил весь запрос before ввод foreach, чтобы это был один запрос, а не множество отдельных запросов. - person just.another.programmer; 27.11.2017
comment
Дополнительный запрос не устраняет исключение полностью, а только делает его гораздо менее вероятным. Смотрите мой ответ для более подробной информации. - person just.another.programmer; 27.11.2017