如何回滚所有已经使用 Sqltransaction 执行的 SqlCommand?



我有以下代码:

public void Execute(string Query, params SqlParameter[] Parameters)
{
using (var Connection = new SqlConnection(Configuration.ConnectionString))
{
Connection.Open();
using (var Command = new SqlCommand(Query, Connection))
{
if (Parameters.Length > 0)
{
Command.Parameters.Clear();
Command.Parameters.AddRange(Parameters);
}
Command.ExecuteNonQuery();
}
}
}

对于不同的查询,可以调用该方法 2 或 3 次,但方式相同。

例如:

  1. 插入员工
  2. 插入员工证书
  3. 更新另一个表上的员工程度 [例如,此处可能导致失败 ]

如果点 [3] 失败,则所有已提交的命令不应执行,必须回滚。

我知道我可以把SqlTransaction放在上面并使用Commit()方法。但是如果失败了,第三点呢?我认为只有第 3 点会回滚,而其他第 1,2 点不会?如何解决这个问题,我应该怎么做?

我应该使用SqlCommand[]数组吗?我该怎么办?

我只找到类似的问题,但在代码项目中:

看这里

无需更改Execute方法即可执行此操作

var tranOpts = new TransactionOptions()
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TransactionManager.MaximumTimeout
};
using (var tran = new TransactionScope(TransactionScopeOption.Required, tranOpts)
{
Execute("INSERT ...");
Execute("INSERT ...");
Execute("UPDATE ...");
tran.Complete();
}

SqlClient 将缓存事务中登记的内部 SqlConnection,并在每次调用执行时重复使用它。 因此,您甚至最终会得到本地(非分布式(事务。

這在這裡的文档中都有解釋:System.Transactions Integration with SQL Server

有几种方法可以做到这一点。

可能涉及更改最少代码和涉及最低复杂性的方法是将多个 SQL 语句链接到单个查询中。为运行多个语句(包括BEGIN TRANSACTIONCOMMIT和(如果需要(ROLLBACKQuery参数构建一个字符串是完全可以的。基本上,在 C# 代码中保留整个存储过程。这也有一个很好的好处,即更容易在过程中使用版本控制。

但它仍然感觉有点黑客。

减少这种影响的一种方法是将Execute()方法标记为私有。然后,在类中为每个查询提供一个附加方法。通过这种方式,长 SQL 字符串是隔离的,当您使用数据库时,感觉更像是使用本地 API。对于更复杂的应用程序,这可能是一个完全独立的程序集,其中包含一些管理逻辑功能区域的类型,其中internalExectue()等核心方法。无论如何,这都是一个好主意,无论您最终如何支持交易。

说到过程,存储过程也是处理这个问题的一种很好的方法。有一个存储过程来完成所有工作,并在准备就绪时调用它。

另一个选项是重载该方法以接受多个查询和参数集合:

public void Execute(string TransactionName, string[] Queries, params SqlParameter[][] Parameters)
{
using (var Connection = new SqlConnection(Configuration.ConnectionString))
using (var Transaction = new SqlTransaction(TransactionName))
{
connection.Transaction = Transaction;
Connection.Open();
try 
{
for (int i = 0; i < Queries.Length; i++)
{
using (var Command = new SqlCommand(Queries[i], Connection))
{
command.Transaction = Transaction;
if (Parameters[i].Length > 0)
{
Command.Parameters.Clear();
Command.Parameters.AddRange(Parameters);
}                
Command.ExecuteNonQuery();
}
}
Transaction.Commit();
}
catch(Exception ex)
{
Transaction.Rollback();
throw; //I'm assuming you're handling exceptions at a higher level in the code
}
}
}

虽然我不确定params关键字如何与数组一起使用......我只是没有尝试过这个选项,但类似的东西会起作用。这里的弱点还在于,让后面的查询依赖于早期查询的结果并非易事,即使没有参数的查询仍然需要 Parameters 数组作为占位符。

最后一个选项是扩展保存Execute()方法的类型以支持事务。这里的诀窍是,static这种类型是常见的(也是可取的(,但支持事务需要重用公共连接和事务对象。鉴于事务的隐含长期运行性质,您必须一次支持多个事务,这意味着实例和实现IDisposable

using (var connection = new SqlConnection(Configuration.ConnectionString))
{
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
connection.Open();
transaction = connection.BeginTransaction("Transaction");
command.Connection = connection;
command.Transaction = transaction;
try
{
if (Parameters.Length > 0)
{
command.Parameters.Clear();
command.Parameters.AddRange(Parameters);
}
command.ExecuteNonQuery();
transaction.Commit();
}
catch (Exception e)
{
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
//trace
}
}
}

最新更新