Несколько консольных приложений обновляют одну и ту же таблицу sql с использованием объектного контекста EF

У меня есть несколько консольных приложений, но они будут читать и вставлять обновления одной и той же таблицы sql. Я использовал транзакцию, но получаю эту ошибку «Транзакция (идентификатор процесса) была заблокирована на ресурсах блокировки с другим процессом и была выбрана в качестве жертвы взаимоблокировки. Повторно запустите транзакцию».

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

Все ли хорошо в моем коде?

bool isMaster = false;
tourneyInstanceTrackerId = 0;
using (JG_RummyEntities dbContext = new JG_RummyEntities())
{
try
{
using (TransactionScope transaction = new    TransactionScope(TransactionScopeOption.Required,new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
                {
bool isAnyMaster = dbContext.TourneyInstanceTrackers.Any(t => t.IsMaster & t.TournamentId == tournamentId);
TourneyInstanceTracker tourneyInstanceTracker = new TourneyInstanceTracker
                    {
                        TournamentId = tournamentId,
                        IsMaster = !isAnyMaster,
                        CreateDate = DateTime.Now
                    };
                       dbContext.AddToTourneyInstanceTrackers(tourneyInstanceTracker);
dbContext.SaveChanges();
                    var result = dbContext.TourneyInstanceTrackers.Where(t => t.TournamentId == tournamentId)
                            .OrderByDescending(t => t.CreateDate)
                            .Select(t => new { t.IsMaster, t.Id })
                            .FirstOrDefault();
                    if (result != null)
                    {
                        isMaster = result.IsMaster;
                        tourneyInstanceTrackerId = result.Id;
                    }
                    transaction.Complete();
                }
            }
            catch (Exception ex)
            {
                Logger.Log("Got exception in SetTournamentInTourneyInstanceTracker : " + ex.Message + ", " + ex.StackTrace + ", "+ tournamentId);
            }
        }

person user1955255    schedule 04.11.2015    source источник


Ответы (1)


IsolationLevel.RepeatableRead действует как ReaderWriterLockSlim, многие потоки могут читать запись одновременно, но как только один из них захочет записать, вы должны дождаться завершения всех операций чтения, а затем установить эксклюзивную блокировку объекта, пока вы не закончите запись.

Итак, представьте, что у вас есть программа, она читает объект «А», немного ждет, а затем записывает объект «А». Теперь давайте посмотрим, что происходит, когда запускаются две копии программы.

Program 1     Program 2
---------     ---------
Begin Trans   Not Started
Read A        Begin Trans
Wait          Read A
Try Write A   Wait
Try Write A   Try Write A
Try Write A   Try Write A
Try Write A   Try Write A

Вы можете видеть, что Программа 1 ожидает, пока Программа 2 освободит свою блокировку чтения, однако Программа 2 не может снять свою блокировку чтения, пока Программа 1 не освободит свою блокировку чтения. Такая ситуация, когда двое ожидают завершения работы друг друга, называется тупиковой ситуацией.

Есть два способа обойти это, один вы можете использовать более ограничительный IsolationLevel.Serializable, который блокирует других читателей, пока транзакция не завершится.

Program 1     Program 2
---------     ---------
Begin Trans   Not Started
Read A        Begin Trans
Wait          Try Read A
Write A       Try Read A
End Trans     Read A
              Wait
              Write A
              End Trans

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

person Scott Chamberlain    schedule 04.11.2015
comment
Спасибо за ваш ответ. Я изменил IsolationLevel на Serializable, но это тоже не помогает. Это ошибка, которую я получаю. Произошла ошибка при чтении из устройства чтения данных поставщика хранилища. Подробности см. во внутреннем исключении в System.Data.Common.Internal.Materialization.Shaper`1.StoreRead(). Это действительно та же ошибка, которую я проверил в Интернете. Просто к вашему сведению, в моей таблице SQL всего 4 столбца, один из которых является идентификатором, который является первичным ключом с IDENTITY (1,1), а остальные три имеют тип bigint. - person user1955255; 05.11.2015
comment
Вам нужно посмотреть на внутреннее исключение, чтобы найти причину ошибки. - person Scott Chamberlain; 05.11.2015
comment
Как я уже говорил, это была та же самая ошибка.System.Data.SqlClient.SqlException (0x80131904). Транзакция (идентификатор процесса 293) заблокирована на ресурсах блокировки другим процессом и выбрана в качестве жертвы взаимоблокировки. Повторите транзакцию. - person user1955255; 05.11.2015
comment
Использует ли другой код, помимо кода, который вы указали в своем ответе, dbContext.TourneyInstanceTrackers? - person Scott Chamberlain; 05.11.2015