我可以从 SqlConnection 对象中获取对挂起事务的引用吗?
Posted
技术标签:
【中文标题】我可以从 SqlConnection 对象中获取对挂起事务的引用吗?【英文标题】:Can I get a reference to a pending transaction from a SqlConnection object? 【发布时间】:2010-09-29 20:13:50 【问题描述】:假设某人(除了我)编写以下代码并将其编译成程序集:
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 需要命令 进行交易时 分配给命令的连接是 在待处理的本地事务中。这 命令的事务属性 尚未初始化”。
我可以以任何方式将我的 SqlCommand cmd 加入待处理的事务中吗?我可以从 IDbConnection 对象中检索对待处理事务的引用吗(如果需要,我很乐意使用反射)?
【问题讨论】:
【参考方案1】:如果有人对实现此目的的反射代码感兴趣,请点击此处:
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【讨论】:
在继续之前检查 currentTransaction 可能是个好主意。 if(currentTransaction == null ) 返回 null; -1 使用这段代码我最终遇到了这样的情况:插入,插入,无声随机回滚,在我的代码中没有任何异常,在事务之外插入更多 我已经尝试了上面的代码,它只有在你有一个实际的 SqlConnection 时才有效,它在接口级别 (IDbConnection) 不起作用,并且不适用于 OleDbConnection、OracleConnection 或任何其他实施。你们认为我们可以在接口级别(IDbConnection)完成这项工作吗?如果没有必要也获取当前活动的 IDbCommand 也会很有帮助。【参考方案2】:哇,我一开始并不相信。我很惊讶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。
【讨论】:
【参考方案3】:对于任何对 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
【讨论】:
我不会用这个。您可以在连接上已经有一个活动事务之后实例化其中一个,在这种情况下,这将不起作用。 你是对的,这只有在你传递一个没有活动事务的连接时才有效......对于你的情况,更好的解决方案是在 ConnectionWithExtraInfo 构造函数中创建新连接,而不是接收已经创建的连接作为参数。最好的办法是从连接对象直接访问事务,并且根本不需要这个装饰器类......【参考方案4】:只能使用其构造函数之一为命令对象分配事务对象。您可以采用 .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 将处理这个更占用资源的事务版本。
【讨论】:
【参考方案5】:我是 simple 的大力支持者,那么如何在 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
【讨论】:
我可能弄错了,但在我看来,您的建议要求我可以让我正在使用的第三方库实例化 MyConnection 而不是普通的 SqlConnection。不幸的是,我没有源代码,它不支持任何形式的依赖注入。 不确定你的意思。在您的情况下,您的“MyConnection”类将采用 SqlConnection、OleConnection 或您作为参数。无需注射。看看“委托设计模式”是如何工作的:en.wikipedia.org/wiki/Delegation_pattern 我明白你的意思。您的情况很困难,因为您要让第三人创建您的连接,在我的情况下,我正在创建许多连接,但它们在我的应用程序中是全局的,因此我不断忘记谁在打开哪些交易,我想保持交易我的连接。在您的情况下,我猜在您的第三方创建的连接被传递到 MyConnection 之后,交易将被跟踪。您必须在获得 conn 之前确定第 3 方是否创建了交易,因此您别无选择,只能使用反射来查找初始交易。 只要你能及早获得连接并将包装器放在它周围(如果属性不是只读的),你就应该是黄金。令人惊讶的是,第三方在创建连接时或在创建连接时或在事务开始/结束时不给您任何事件的访问权限。也许您可以创建连接并将其传递给第 3 方应用程序以替换它自己的?也许您可以使用反射将第 3 方的连接属性或内部变量设置为 MyConnection? 刚刚有了另一个想法,假设您的第三方应用程序在某些配置文件中有要使用的 sql 驱动程序,也许您可以将其指向使用 MyConnection(添加不带参数的构造函数来实例化 SQLConnection MyConnection 内),这应该可以由第三方应用程序加载 - 从而允许您注入 MyConnection 而不是 SQLConnection【参考方案6】:如果有人在 .Net 4.5 上遇到此问题,您可以使用 Transaction.Current
在System.Transactions
。
【讨论】:
检索当前的TransactionScope,而不是SqlTransaction。类似但不同的技术。 你在哪里看到他需要 SqlTransaction 而不是 TransactionScope 的问题? 他问题的前 5 行。 “假设某人(除了我)编写以下代码并将其编译为程序集: using (SqlConnection conn = new SqlConnection(connString)) conn.Open(); using (var transaction = conn.BeginTransaction() )"以上是关于我可以从 SqlConnection 对象中获取对挂起事务的引用吗?的主要内容,如果未能解决你的问题,请参考以下文章
csharp 从DbContext获取SqlConnection