假设有人(除了我)编写了以下代码并将其编译成程序集:

using (SqlConnection conn = new SqlConnection(connString)) 
{
    conn.Open();
    using (var transaction = conn.BeginTransaction())
    {
        /* Update something in the database */
        /* Then call any registered OnUpdate handlers */
        InvokeOnUpdate(conn);

        transaction.Commit();
    }
}

对 InvokeOnUpdate(IDbConnection conn) 的调用调用了我可以实现和注册的事件处理程序。因此,在此处理程序中,我将拥有对 IDbConnection 对象的引用,但不会拥有对待处理事务的引用。有什么办法可以让我掌握交易情况吗?在我的 OnUpdate 处理程序中,我想执行类似于以下内容的操作:

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

    cmd.ExecuteNonQuery();
}

但是,对 cmd.ExecuteNonQuery() 的调用会引发 InvalidOperationException 抱怨

“ ExecuteNonquery要求命令在分配给命令的连接处于待处理的本地事务中时必须进行事务。命令的交易属性尚未初始化”。

我可以以任何方式将我的 SqlCommand cmd 与挂起的事务一起使用吗?我可以从 IDbConnection 对象检索对待处理事务的引用(如果需要,我很乐意使用反射)?

有帮助吗?

解决方案

哇我起初并不相信这一点。我很惊讶 CreateCommand()在使用本地SQL Server事务时没有给出它的事务命令,并且事务没有在 SqlConnection 对象上公开。实际上,当反思 SqlConnection 时,当前事务甚至不存储在该对象中。在下面的编辑中,我给了你一些提示,通过它们的一些内部类来追踪对象。

我知道你无法修改方法但是你可以在方法栏周围使用TransactionScope吗?所以如果你有:

public static void CallingFooBar()
{
   using (var ts=new TransactionScope())
   {
      var foo=new Foo();
      foo.Bar();
      ts.Complete();
   }
}

这将有效,我使用类似的代码测试你的,并且一旦我添加包装器,一切正常,如果你可以做到这一点。正如所指出的那样,注意在 TransactionScope 中是否打开了多个连接,您将升级到分布式事务,除非为您配置系统,否则您将收到错误。

加入DTC也比本地交易慢几倍。

修改

如果你真的想尝试使用反射,SqlConnection有一个SqlInternalConnection,它依次有一个AvailableInternalTransaction属性,它返回一个SqlInternalTransaction,它有一个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

对于任何对Denis在VB.NET中制作的装饰器类的C#版本感兴趣的人,这里是:

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

namespace DataAccessLayer
{
    /// <summary>
    /// Decorator for the connection class, exposing additional info like it's transaction.
    /// </summary>
    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
    }
}

只能使用其构造函数之一为命令对象分配事务对象。您可以使用.NET 2.0方法并使用在 System.Transactions 命名空间(具有专用程序集)中定义的TransactionScope对象。

   using System.Transactions;

    class Foo
    {   
        void Bar()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                // Data access
                // ...
                scope.Complete()
            }
        }
    }

System.Transactions方法与SQL Server 2005一起使用轻量级事务协调器(LTM)。注意不要在事务范围中使用多个连接对象,否则事务将被视为分布式事务。然后,这个更加资源密集的交易版本将由DTC处理。

我是一个简单的支持者,那么如何在IDBConnection(DELEGATE PATTERN)上编写一个包含暴露事务的包装器。 (对不起VB.NET代码,我现在正在VB.NET中写这个)这样的事情:

  Public class MyConnection
      Implements IDbConnection

      Private itsConnection as IDbConnection
      Private itsTransaction as IDbTransaction

      Public Sub New(ByVal conn as IDbConnection)
         itsConnection = conn
      End Sub

      //...  'All the implementations would look like
      Public Sub Dispose() Implements IDbConnection.Dispose
         itsConnection.Dispose()
      End Sub
      //...

      //     'Except BeginTransaction which would look like
       Public Overridable Function BeginTransaction() As IDbTransaction Implements IDbConnection.BeginTransaction
         itsTransaction = itsConnection.BeginTransaction()
         Return itsTransaction
       End Function  


      // 'Now you can create a property and use it everywhere without any hacks
       Public ReadOnly Property Transaction
          Get
              return itsTransaction
          End Get
       End Property

    End Class

所以你将它实例化为:

Dim myConn as new MyConnection(new SqlConnection(...))

然后您可以使用以下任意时间获取交易:

 myConn.Transaction

如果有人在.Net 4.5上遇到此问题,您可以使用 Transaction.Current System.Transactions

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top