10

使用 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;
    }
}

编辑:所以,如果答案是当它嵌套在事务范围内时重用相同的连接,就像上面的代码一样,我想知道在事务中处理这个连接的含义。

据我所知(使用反射器检查代码),连接的设置(连接字符串等)被重置并且连接被关闭。因此(理论上),重新设置连接字符串并在后续调用中打开连接应该“重用”连接并防止升级(我的初始测试同意这一点)。

不过,它看起来确实有点老套……而且我敢肯定,在某处必须有一个最佳实践,即在对象被处理后不应继续使用它!

但是,由于我无法继承密封的 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;
    }
}

仍然会重视一个不那么老套的解决方案:)

4

1 回答 1

3

事务升级的主要原因之一是当您在事务中涉及多个(不同)连接时。这几乎总是升级为分布式事务。这确实是一种痛苦。

这就是为什么我们要确保我们所有的交易都使用一个单一的连接对象。有几种方法可以做到这一点。在大多数情况下,我们使用线程静态对象来存储连接对象,而我们做数据库持久性工作的类使用线程静态连接对象(当然是共享的)。这可以防止使用多个连接对象并消除事务升级。您也可以通过简单地将连接对象从一个方法传递到另一个方法来实现这一点,但这并不那么干净,IMO。

于 2010-11-13T13:27:36.517 回答