![](/img/trans.png)
[英]How can I “detach” a SqlDataReader from its SqlConnection object?
[英]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;
}
笔记:
哇我一开始并不相信。 我很惊讶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.