我能从一个 SqlConnection 对象中获取一个挂起事务的引用吗?

14 浏览
0 Comments

我能从一个 SqlConnection 对象中获取一个挂起事务的引用吗?

假设有人(不是我)编写了以下代码并将其编译为汇编:\n

using (SqlConnection conn = new SqlConnection(connString)) 
{
    conn.Open();
    using (var transaction = conn.BeginTransaction())
    {
        /* 在数据库中更新一些内容 */
        /* 然后调用任何注册的 OnUpdate 处理程序 */
        InvokeOnUpdate(conn);
        transaction.Commit();
    }
}

\n对 InvokeOnUpdate(IDbConnection conn) 的调用会调用一个我可以实现和注册的事件处理程序。因此,在这个处理程序中,我将拥有一个对 IDbConnection 对象的引用,但我将没有对未决事务的引用。是否有任何方法可以获取事务的引用?在我的 OnUpdate 处理程序中,我想执行类似以下的操作:\n

private void MyOnUpdateHandler(IDbConnection conn) 
{
    var cmd = conn.CreateCommand();
    cmd.CommandText = someSQLString;
    cmd.CommandType = CommandType.Text;
    cmd.ExecuteNonQuery();
}

\n然而,对 cmd.ExecuteNonQuery() 的调用会引发一个 InvalidOperationException,指出:\n

\n\"当连接分配给命令处于挂起的本地事务中时,ExecuteNonQuery 需要命令具有事务。命令的 Transaction 属性尚未初始化。\"\n

\n我是否可以以任何方式将我的 SqlCommand cmd 登记到未决事务中?是否可以从 IDbConnection 对象中检索到未决事务的引用(如果需要,我可以使用反射来实现)?

0
0 Comments

问题的原因是在C#中,SqlConnection类没有直接提供获取挂起事务的方法。解决方法是创建一个装饰类ConnectionWithExtraInfo,该类实现了IDbConnection接口,并包含一个私有的IDbConnection对象和一个私有的IDbTransaction对象,通过该装饰类可以间接访问到连接对象的事务。

具体代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace DataAccessLayer
{
    /// 
    /// Decorator for the connection class, exposing additional info like it's transaction.
    /// 
    public class ConnectionWithExtraInfo : IDbConnection
    {
        private IDbConnection connection = null;
        private IDbTransaction transaction = null;
        public IDbConnection Connection
        {
            get { return connection; }
        }
        public IDbTransaction Transaction
        {
            get { return transaction; }
        }
        public ConnectionWithExtraInfo(IDbConnection connection)
        {
            this.connection = connection;
        }
        #region IDbConnection Members
        public IDbTransaction BeginTransaction(IsolationLevel il)
        {
            transaction = connection.BeginTransaction(il);
            return transaction;
        }
        public IDbTransaction BeginTransaction()
        {
            transaction = connection.BeginTransaction();
            return transaction;
        }
        public void ChangeDatabase(string databaseName)
        {
            connection.ChangeDatabase(databaseName);
        }
        public void Close()
        {
            connection.Close();
        }
        public string ConnectionString
        {
            get
            {
                return connection.ConnectionString;
            }
            set
            {
                connection.ConnectionString = value;
            }
        }
        public int ConnectionTimeout
        {
            get { return connection.ConnectionTimeout; }
        }
        public IDbCommand CreateCommand()
        {
            return connection.CreateCommand();
        }
        public string Database
        {
            get { return connection.Database; }
        }
        public void Open()
        {
            connection.Open();
        }
        public ConnectionState State
        {
            get { return connection.State; }
        }
        #endregion
        #region IDisposable Members
        public void Dispose()
        {
            connection.Dispose();
        }
        #endregion
    }
}

然后,有人指出这种方法的缺点是只能在连接没有活动事务的情况下使用。对于已经存在活动事务的情况,这种方法不适用。建议在ConnectionWithExtraInfo的构造函数中创建新的连接,而不是接收一个已存在的连接作为参数。最好的解决方法是直接从连接对象中获取事务,这样就不需要使用装饰类ConnectionWithExtraInfo了。

0
0 Comments

问题的出现的原因是SqlConnection对象没有直接提供对挂起事务的引用。在反射SqlConnection对象时,当前事务甚至没有存储在该对象中。

解决方法是使用TransactionScope将方法bar包装起来。在TransactionScope中创建新的事务,并在方法bar执行完毕后调用Complete()方法提交事务。需要注意的是,如果在TransactionScope中打开了多个连接,将会升级为分布式事务,如果系统没有配置分布式事务,将会出错。而且与本地事务相比,与DTC的协同需要更多的时间。

如果真的想要尝试使用反射,可以通过SqlConnection对象的SqlInternalConnection属性获取SqlInternalTransaction对象,然后通过Parent属性获取SqlTransaction对象。

0
0 Comments

问题的出现原因是:需要从一个SqlConnection对象中获取一个待处理事务的引用。

解决方法是:使用反射来实现这个功能。首先,通过反射获取SqlConnection对象的InnerConnection属性,再通过InnerConnection对象获取CurrentTransaction属性和Parent属性,最后将Parent属性转换为SqlTransaction类型并返回。

代码如下:

private static readonly PropertyInfo ConnectionInfo = typeof(SqlConnection).GetProperty("InnerConnection", BindingFlags.NonPublic | BindingFlags.Instance);
private static SqlTransaction GetTransaction(IDbConnection conn) {
    var internalConn = ConnectionInfo.GetValue(conn, null);
    var currentTransactionProperty = internalConn.GetType().GetProperty("CurrentTransaction", BindingFlags.NonPublic | BindingFlags.Instance);
    var currentTransaction = currentTransactionProperty.GetValue(internalConn, null);
    var realTransactionProperty = currentTransaction.GetType().GetProperty("Parent", BindingFlags.NonPublic | BindingFlags.Instance);
    var realTransaction = realTransactionProperty.GetValue(currentTransaction, null);
    return (SqlTransaction) realTransaction;
}

需要注意的是:

- 这些类型和属性都是内部的,且属性都是私有的,所以无法使用动态类型。

- 由于类型是内部的,无法像ConnectionInfo那样声明中间类型,而需要使用对象的GetType方法。

- 在继续操作之前,最好先检查currentTransaction的值。如果currentTransaction为null,则返回null。

还有一位用户提到了使用上述代码后遇到的问题,即在代码中没有任何异常的情况下,出现了插入、插入和随机回滚的情况。另一位用户则提出了是否能够在接口级别(IDbConnection)上实现这个功能的问题,并且还希望能够获取当前活动的IDbCommand。

文章完整内容如上。

0