2009-01-06 69 views
34

假如有人(除了我之外)写入下面的代码并将其编译成一个程序集:我可以从SqlConnection对象获得对待定事务的引用吗?

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)调用叫一声,我可以实现并注册一个事件处理程序。因此,在这个处理程序中,我将引用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对象中检索对挂起事务的引用(如果需要,我会很乐意使用反射)?

回答

7

哇,我一开始并不相信。令我惊讶的是,在使用本地SQL Server事务时,CreateCommand()不会为该命令提供命令,并且该事务未在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,这有它返回你需要的SqlTransaction父母的财产。

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处理。

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 
+0

我可能会误解,但在我看来,您的建议要求我可以使用我正在使用的第三方库实例化MyConnection而不是普通的SqlConnection。不幸的是,我没有源代码,它不支持任何形状或形式的依赖注入。 – Rune 2010-12-20 16:03:48

+0

不确定你的意思。在你的情况下,你的“MyConnection”类将采用SqlConnection,OleConnection或你有什么作为参数。不需要注射。看看“委托设计模式”如何工作,如下所示:http://en.wikipedia.org/wiki/Delegation_pattern – Denis 2010-12-20 17:03:52

+0

我明白你的意思了。你的情况很难,因为你让第三个创建你的连接,在我的情况下,我创建了很多连接,但它们在我的应用程序中是全局的,所以我经常失去跟踪谁在打开什么事务,并且希望保持与我的连接。在你的情况下,我猜想在你的第三方创建的连接被传递到MyConnection后,事务将被跟踪。在得到conn之前,你必须弄清楚第三方是否创建了一个交易,所以你别无选择,只能用反射来找到最初的交易。 – Denis 2010-12-20 21:08:25

4

对于任何人谁是感兴趣的装饰类,丹尼斯在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 
    } 
} 
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一样声明中间类型。得上的对象
-2

用gettype在情况下任何人都面临这个问题上.NET 4.5,你可以在System.Transactions使用Transaction.Current

相关问题