Рекомендуемая практика для остановки транзакций, переходящих в распределенную при использовании transactioncope

Использование объекта 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;
    }
}

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

Насколько я могу судить (используя 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;
    }
}

По-прежнему ценил бы менее хакерское решение :)

10
задан Kram 15 November 2010 в 19:00
поделиться