У меня есть блок кода, который работает в TransactionScope и в рамках этого блока кода, я выполняю несколько вызовов к DB. Выбирает, Обновления, Создает и Удаляет, целая палитра. Когда я выполняю мой удалять, я выполняю его с помощью дополнительного метода SqlCommand, который автоматически повторно отправит запрос, если это зайдет в тупик, поскольку этот запрос мог бы потенциально поразить мертвую блокировку.
Я полагаю, что проблема происходит, когда мертвая блокировка поражена, и функция пытается повторно отправить запрос. Это - ошибка, которую я получаю:
Транзакция, связанная с текущим соединением, завершилась, но не была расположена. Транзакция должна быть расположена, прежде чем соединение может использоваться для выполнения SQL-операторов.
Это - простой код, который выполняет запрос (весь код ниже выполняется в рамках использования TransactionScope):
using (sqlCommand.Connection = new SqlConnection(ConnectionStrings.App))
{
sqlCommand.Connection.Open();
sqlCommand.ExecuteNonQueryWithDeadlockHandling();
}
Вот дополнительный метод, который повторно отправляет заведенный в тупик запрос:
public static class SqlCommandExtender
{
private const int DEADLOCK_ERROR = 1205;
private const int MAXIMUM_DEADLOCK_RETRIES = 5;
private const int SLEEP_INCREMENT = 100;
public static void ExecuteNonQueryWithDeadlockHandling(this SqlCommand sqlCommand)
{
int count = 0;
SqlException deadlockException = null;
do
{
if (count > 0) Thread.Sleep(count * SLEEP_INCREMENT);
deadlockException = ExecuteNonQuery(sqlCommand);
count++;
}
while (deadlockException != null && count < MAXIMUM_DEADLOCK_RETRIES);
if (deadlockException != null) throw deadlockException;
}
private static SqlException ExecuteNonQuery(SqlCommand sqlCommand)
{
try
{
sqlCommand.ExecuteNonQuery();
}
catch (SqlException exception)
{
if (exception.Number == DEADLOCK_ERROR) return exception;
throw;
}
return null;
}
}
Ошибка происходит на строке:
sqlCommand.ExecuteNonQuery();
Не забывайте подавлять операторы select в TransactionScope. В SQL Server 2005 и выше, даже когда вы используете with(nolock), блокировки все равно создаются на тех таблицах, которые затрагивает select. Посмотрите здесь, здесь показано как настроить и использовать TransactionScope.
using(TransactionScope ts = new TransactionScope
{
// db calls here are in the transaction
using(TransactionScope tsSuppressed = new TransactionScope (TransactionScopeOption.Suppress))
{
// all db calls here are now not in the transaction
}
}
Если исключение происходит внутри TransactionScope
, выполняется откат. Это означает, что TransactionScope
завершен. Теперь вы должны вызвать для него dispose ()
и начать новую транзакцию. Честно говоря, я не уверен, можно ли повторно использовать старую TransactionScope
или нет, я никогда не пробовал, но полагаю, что нет.