假设有人(除了我)编写以下代码并将其编译成程序集:
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对象中检索对待处理事务的引用(如果需要,我很乐意使用反射)?
答案 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;
}
注意:
答案 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.Current
在System.Transactions
。