Предотвращение состояния гонки if-exists-update-else-insert в Entity Framework

Я читал другие вопросы о том, как реализовать семантику if-exists-insert-else-update в EF, но либо я не понимаю, как работают ответы, или они на самом деле не решают проблему. Обычное предлагаемое решение - заключить работу в область транзакции (например: Реализация if-not-exists-insert с использованием Entity Framework без условий гонки ):

using (var scope = new TransactionScope()) // default isolation level is serializable
using(var context = new MyEntities())
{
    var user = context.Users.SingleOrDefault(u => u.Id == userId); // *
    if (user != null)
    {
        // update the user
        user.property = newProperty;
        context.SaveChanges();
    }
    else
    {
        user = new User
        {
             // etc
        };
        context.Users.AddObject(user);
        context.SaveChanges();
    }
}

Но я не вижу, как это что-то решает для того, чтобы это работало, строка, отмеченная мной звездочкой выше, должна блокировать , если второй поток пытается получить доступ к тому же идентификатору пользователя, разблокировка только тогда, когда первый поток завершит свою работу. Однако использование транзакции вызовет это , а не , и мы получим исключение UpdateException из-за нарушения ключа, которое происходит, когда второй поток пытается создать того же пользователя во второй раз.

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

Похоже, что это обычная проблема для пользователей EF, поэтому я ищу чистое универсальное решение, которое можно использовать везде.

Я бы действительно хотел избежать использования хранимой процедуры для создания моего пользователя если возможно.

Есть идеи?

РЕДАКТИРОВАТЬ : Я попытался выполнить приведенный выше код одновременно в двух разных потоках, используя один и тот же идентификатор пользователя, и, несмотря на извлечение сериализуемых транзакций, они оба смогли войти в критическую секцию (*) одновременно. Это привело к возникновению исключения UpdateException, когда второй поток попытался вставить тот же идентификатор пользователя, который только что вставил первый. Это связано с тем, что, как указано ниже Ладиславом, сериализуемая транзакция принимает исключительные блокировки только после того, как она начала изменять данные, а не читать.

29
задан Community 23 May 2017 в 12:34
поделиться