我可以从SqlConnection对象获取对待处理事务的引用吗?

时间:2009-01-06 15:44:16

标签: c# .net sql-server transactions ado.net

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

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需要命令   当有一个交易时   分配给命令的连接是   在待处理的本地事务中。该   命令的Transaction属性   尚未初始化“。

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

6 个答案:

答案 0 :(得分:16)

如果有人对反射代码感兴趣来完成这个,那么它就是:

    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

答案 1 :(得分:9)

哇我起初并不相信这一点。我很惊讶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。

答案 2 :(得分:4)

对于任何对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
    }
}

答案 3 :(得分:3)

只能使用其构造函数之一为命令对象分配事务对象。您可以使用.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处理。

答案 4 :(得分:0)

我是一个简单的支持者,那么如何在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

答案 5 :(得分:-2)

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