繁体   English   中英

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

[英]Can I get a reference to a pending transaction from a SqlConnection object?

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

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对象中检索对待处理事务的引用(如果需要,我很乐意使用反射)?

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

    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

哇我一开始并不相信。 我很惊讶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。

对于任何对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)上编写一个包装器来暴露Transaction。 (对不起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上遇到此问题,您可以在System.Transactions使用Transaction.Current

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM