Использование объекта TransactionScope для установки неявной транзакции, которую не нужно передавать через вызовы функций, прекрасно! Однако, если соединение открыто, а другое уже открыто, координатор транзакций молча эскалирует транзакцию, которая должна быть распределена (требуется, чтобы служба MSDTC была запущена, и занимала гораздо больше ресурсов и времени).
Итак, это нормально:
using (var ts = new TransactionScope())
{
using (var c = DatabaseManager.GetOpenConnection())
{
// Do Work
}
using (var c = DatabaseManager.GetOpenConnection())
{
// Do more work in same transaction using different connection
}
ts.Complete();
}
Но это усиливает транзакцию:
using (var ts = new TransactionScope())
{
using (var c = DatabaseManager.GetOpenConnection())
{
// Do Work
using (var nestedConnection = DatabaseManager.GetOpenConnection())
{
// Do more work in same transaction using different nested connection - escalated transaction to distributed
}
}
ts.Complete();
}
Есть ли рекомендуемая практика, позволяющая избежать эскалации транзакций таким образом, при этом все еще используя вложенные соединения?
Лучшее, что я могу придумать на данный момент, - это иметь подключение к ThreadStatic и повторно использовать его, если установлен Transaction.Current, например:
public static class DatabaseManager
{
private const string _connectionString = "data source=.\\sql2008; initial catalog=test; integrated security=true";
[ThreadStatic]
private static SqlConnection _transactionConnection;
[ThreadStatic] private static int _connectionNesting;
private static SqlConnection GetTransactionConnection()
{
if (_transactionConnection == null)
{
Transaction.Current.TransactionCompleted += ((s, e) =>
{
_connectionNesting = 0;
if (_transactionConnection != null)
{
_transactionConnection.Dispose();
_transactionConnection = null;
}
});
_transactionConnection = new SqlConnection(_connectionString);
_transactionConnection.Disposed += ((s, e) =>
{
if (Transaction.Current != null)
{
_connectionNesting--;
if (_connectionNesting > 0)
{
// Since connection is nested and same as parent, need to keep it open as parent is not expecting it to be closed!
_transactionConnection.ConnectionString = _connectionString;
_transactionConnection.Open();
}
else
{
// Can forget transaction connection and spin up a new one next time one's asked for inside this transaction
_transactionConnection = null;
}
}
});
}
return _transactionConnection;
}
public static SqlConnection GetOpenConnection()
{
SqlConnection connection;
if (Transaction.Current != null)
{
connection = GetTransactionConnection();
_connectionNesting++;
}
else
{
connection = new SqlConnection(_connectionString);
}
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
return connection;
}
}
Редактировать: Итак, если ответ заключается в том, чтобы повторно использовать то же соединение, когда оно вложено в область транзакций, как это делается в коде выше, я задаюсь вопросом о последствиях удаления этого соединения в середине транзакции.
Насколько я вижу (используя Reflector для проверки кода), настройки соединения (строка соединения и т. Д.) Сбрасываются, а соединение закрывается. Таким образом (теоретически), переустановка строки соединения и открытие соединения при последующих вызовах должно «повторно использовать» соединение и предотвратить эскалацию (и мое первоначальное тестирование с этим согласно).
Это кажется немного хакерским, хотя ... и я уверен, что где-то должна быть лучшая практика, которая утверждает, что не следует продолжать использовать объект после того, как он был уничтожен!
Тем не менее, поскольку я не могу создать подкласс запечатанного SqlConnection и хочу поддерживать методы, не зависящие от транзакций, не зависящие от транзакций, я изо всех сил (но был бы рад) найти лучший способ.
Кроме того, я понял, что мог бы принудительно устанавливать не вложенные соединения, генерируя исключение, если код приложения пытается открыть вложенное соединение (что в большинстве случаев не требуется в нашей кодовой базе)
public static class DatabaseManager
{
private const string _connectionString = "data source=.\\sql2008; initial catalog=test; integrated security=true; enlist=true;Application Name='jimmy'";
[ThreadStatic]
private static bool _transactionHooked;
[ThreadStatic]
private static bool _openConnection;
public static SqlConnection GetOpenConnection()
{
var connection = new SqlConnection(_connectionString);
if (Transaction.Current != null)
{
if (_openConnection)
{
throw new ApplicationException("Nested connections in transaction not allowed");
}
_openConnection = true;
connection.Disposed += ((s, e) => _openConnection = false);
if (!_transactionHooked)
{
Transaction.Current.TransactionCompleted += ((s, e) =>
{
_openConnection = false;
_transactionHooked = false;
});
_transactionHooked = true;
}
}
connection.Open();
return connection;
}
}
Все равно оценил бы менее хакерское решение:)