2014-10-08 2 views
0

У меня есть код, который я использую для минимизации проблем параллелизма. Он структурирован примерно так:Обнаружение проблем параллелизма в Entity Framework с использованием транзакций

// HasConcurrencyIssues opens a new EntityContext and gets the actual current 
// details and then compares it to what was loaded for the form 
if (this.HasConcurrencyIssues()) { 

    // can't save, exit out of save code 
    return; 
} 

using (EntityContext context = new EntityContext()) { 

    // shared locks to avoid dirty reads - should I be using this?  
    IsolationLevel level = IsolationLevel.ReadCommitted; 

    using (DbContextTransaction trans == context.Database.BeginTransaction(level)) { 

     try { 

      // here is where I save stuff using the context by adding 
      // items to the DbSets 
      this.SaveStuff(context); 

      // execute the generated SQL commands before secondary check because 
      // this could take some time 
      context.SaveChanges(); 

      // check again in case something happened after executing SaveChanges() 
      // ** gets blocked here ** 
      if (this.HasConcurrencyIssues()) { 

       // can't save, rollback and exit out of save code 
       trans.Rollback(); 
       return; 
      } 

      trans.Commit(); 
     } catch (Exception ex) { 

      // handle error 
      trans.Rollback(); 
     } 
    } 
} 

Я использовал эту структуру, чтобы минимизировать проблемы параллелизма. Проблема, с которой я сталкиваюсь, заключается в том, что я не совсем понимаю, как работают ReadCommitted и параллелизм. Я заметил, что при второй проверке проблем параллелизма он блокируется, потому что он не может читать текущие записи (я предполагаю, что это связано с моим выбранным IsolationLevel). Я беспокоюсь, что если я удалю эту вторичную проверку, это может привести к фантомным данным, которые я не хочу.

Приложение масштабируется и должно обрабатывать ~ 200 пользователей, которые потенциально могут захотеть отредактировать один и тот же объект и сохранить его. Я в основном хочу жесткую остановку, которая проверяет, изменился ли объект и если он есть, предотвратить сохранение.

Как я могу структурировать свой код сохранения, чтобы минимизировать проблемы параллелизма?

Редактировать

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


Соответствующая архитектура системы:

  • Entity Framework 6
  • SQL Server 2012
+1

Кажется, что обнаружение и предотвращение проблем с параллелизмом обычно лучше всего оставить в базе данных и обработке транзакций. Можете ли вы объяснить, какие проблемы параллелизма вы пытаетесь избежать, и почему вы не можете просто использовать простой «TransactionScope» с уровнем изоляции по умолчанию? – StriplingWarrior

+0

@StriplingWarrior Предположим, что 'User A' и' User B' открывают форму, которая редактирует 'Object A'. Если 'User B' сохраняет и закрывает форму, форма пользователя 'A' теперь недействительна. Мне нужно иметь возможность предотвратить сохранение «User A», потому что они будут записывать записи, которые технически неверны. – test

+0

Это обычная проблема, с которой обычно сталкивается * оптимистичный параллелизм *. EF полностью поддерживает эту стратегию параллелизма. –

ответ

0

Я думаю, что это будет более простой подход:

using (var ts = new TransactionScope()) 
{ 
    if (this.HasConcurrencyIssues()) { 
     // can't save, exit out of save code 
     return; 
    } 

    using (EntityContext context = new EntityContext()) { 
     this.SaveStuff(context); 
     context.SaveChanges(); 
    } 

    ts.Complete(); 
} 

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

Аналогично, если другой поток уже прочитал информацию, чтобы определить, есть ли проблемы с параллелизмом, ваш вызов HasConcurrencyIssues() не вернется, пока они не закончат сохранение своих изменений.

Объем транзакции автоматически откатывается в конце блока using, если он еще не завершен, поэтому нет необходимости в дополнительной логике try/catch.

+0

Это использование распределенных транзакций, которые нецелесообразны или доступны во многих системах. – usr

+0

@usr: Есть ли основания думать, что это не сработает для ситуации OP? Конечно, есть более безопасные/лучшие подходы, но все, о которых я могу думать, довольно сложны. Если это сработает, я поеду с ним. – StriplingWarrior

0

Работа с двумя транзакциями по тем же данным требует проблем. Вы получите все виды блокировки, часто зависящие от плана запроса. Во время тестирования это может работать, а затем ломаться под нагрузкой. Вы создадите распределенные блокировки (включая несколько подключений). SQL Server не имеет возможности их разбить. Вы в основном блокируете некоторые данные и выполняете выполнение в течение 30 секундного таймаута. Убийца масштабируемости.

Используйте один контекст, соединение и транзакцию. Получите право на блокировку.

Лучше понять блокировку в SQL Server и применить правильный уровень изоляции и, возможно, пессимистические блокирующие подсказки.Или используйте решение, которое всегда работает: SERIALIZABLE. Этот уровень изоляции гарантирует результаты, если только одна транзакция была запущена одновременно. Обработайте взаимоблокировки, повторив транзакцию.

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

Исследуйте изоляцию для читателей SNAPSHOT. Он удаляет всю блокировку данных и обеспечивает отличную согласованность данных.

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

+0

По существу, я обновляю две таблицы, создавая их записи, а затем вставляю новые записи. Поэтому 'SaveChanges' будет называть кучу операторов UPDATE и INSERT. Помогает ли эта информация для разработки? Ты меня так беспокоился, что я не думаю, что я справляюсь с этим. – test

+0

Выполнение изменений атома очень просто, используя транзакцию. Выделение других транзакций из этих изменений сложнее. Какой конкретный сценарий вы беспокоитесь? Пример. Предположим, что два транса попытаются прочитать int, увеличьте его и запишите. Важно, чтобы прочитанное было частью транзакции (вы понимаете, почему?). Являются ли ваши чтения (которые выписаны для записи) частью транзакции? Это не похоже на код. – usr

0

Вам не нужно указывать уровень изоляции как ReadCommitted, если вы не измените его на своем SQL-сервере с помощью команды SET TRANSACTION ISOLATION LEVEL или где-либо еще в приложении. Поскольку ReadCommitted является настройкой по умолчанию на сервере sql.

Было бы полезно ознакомиться с here и here, чтобы понять возможные сценарии.

Смежные вопросы