我有以下代码:
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 次,但方式相同。
例如:
- 插入员工
- 插入员工证书
- 更新另一个表上的员工程度 [例如,此处可能导致失败 ]
如果点 [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 TRANSACTION
、COMMIT
和(如果需要(ROLLBACK
的Query
参数构建一个字符串是完全可以的。基本上,在 C# 代码中保留整个存储过程。这也有一个很好的好处,即更容易在过程中使用版本控制。
但它仍然感觉有点黑客。
减少这种影响的一种方法是将Execute()
方法标记为私有。然后,在类中为每个查询提供一个附加方法。通过这种方式,长 SQL 字符串是隔离的,当您使用数据库时,感觉更像是使用本地 API。对于更复杂的应用程序,这可能是一个完全独立的程序集,其中包含一些管理逻辑功能区域的类型,其中internal
Exectue()
等核心方法。无论如何,这都是一个好主意,无论您最终如何支持交易。
说到过程,存储过程也是处理这个问题的一种很好的方法。有一个存储过程来完成所有工作,并在准备就绪时调用它。
另一个选项是重载该方法以接受多个查询和参数集合:
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
}
}
}