如何回滚所有已使用 SqlTransaction 执行的 SqlCommand?
how to rollback all SqlCommands which already executed with SqlTransaction?
我有以下代码:
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 次,但方式相同。
例如:
- 插入员工
- 插入员工证书
- 在另一个 table 上更新员工的学历 table [ 此处可能会导致失败。例如]
如果第 [3] 点失败,所有已提交的命令都不应执行,必须回滚。
我知道我可以把 SqlTransaction
放在上面并使用 Commit()
方法。但是如果失败了第三点呢?我认为第 3 点只会回滚而其他第 1,2 点不会?如何解决这个问题,我应该采取什么方法??
我应该使用 SqlCommand[]
数组吗?我该怎么办?
我只在 CodeProject 中找到类似的问题:
See Here
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
}
}
}
有几种方法可以做到。
可能涉及更改最少代码和涉及最少复杂性的方法是将多个 SQL 语句链接到一个查询中。为运行多个语句的 Query
参数构建一个字符串非常好,包括 BEGIN TRANSACTION
、COMMIT
和(如果需要)ROLLBACK
。基本上,在您的 C# 代码中保留整个存储过程。这还有一个好处,就是可以更轻松地在您的过程中使用版本控制。
但感觉还是有点老套。
减少这种影响的一种方法是将 Execute()
方法标记为 private。然后,在 class 中为每个查询添加一个方法。这样一来,长的SQL字符串就被隔离了,在使用数据库的时候感觉更像是在使用本地的API。对于更复杂的应用程序,这可能是一个完整的独立程序集,其中包含一些管理逻辑功能区域的类型,其中像 Exectue()
这样的核心方法是 internal
。不管您最终如何支持交易,这都是一个好主意。
说到过程,存储过程也是处理此问题的完美方式。有一个存储过程来完成所有工作,并在准备就绪时调用它。
另一种选择是重载接受多个查询和参数集合的方法:
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
关键字如何与数组的数组一起使用...我只是没有尝试过该选项,但是按照这些思路进行操作会起作用。这里的弱点还在于,稍后的查询依赖于先前查询的结果并不是微不足道的,即使没有参数的查询仍然需要一个参数数组作为占位符。
最后一个选项是扩展持有 Execute()
方法的类型以支持交易。这里的诀窍是让这种类型成为 static
是常见的(并且是可取的),但是支持事务需要重新使用公共连接和事务对象。鉴于交易的隐含长 运行 性质,您必须一次支持多个,这意味着实例和实现 IDisposable
。
无需更改您的 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,并在每次调用 Execute 时重用它。所以你甚至会得到一个本地(非分布式)交易。
这一切都在此处的文档中进行了解释:System.Transactions Integration with SQL Server
我有以下代码:
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 次,但方式相同。
例如:
- 插入员工
- 插入员工证书
- 在另一个 table 上更新员工的学历 table [ 此处可能会导致失败。例如]
如果第 [3] 点失败,所有已提交的命令都不应执行,必须回滚。
我知道我可以把 SqlTransaction
放在上面并使用 Commit()
方法。但是如果失败了第三点呢?我认为第 3 点只会回滚而其他第 1,2 点不会?如何解决这个问题,我应该采取什么方法??
我应该使用 SqlCommand[]
数组吗?我该怎么办?
我只在 CodeProject 中找到类似的问题:
See Here
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
}
}
}
有几种方法可以做到。
可能涉及更改最少代码和涉及最少复杂性的方法是将多个 SQL 语句链接到一个查询中。为运行多个语句的 Query
参数构建一个字符串非常好,包括 BEGIN TRANSACTION
、COMMIT
和(如果需要)ROLLBACK
。基本上,在您的 C# 代码中保留整个存储过程。这还有一个好处,就是可以更轻松地在您的过程中使用版本控制。
但感觉还是有点老套。
减少这种影响的一种方法是将 Execute()
方法标记为 private。然后,在 class 中为每个查询添加一个方法。这样一来,长的SQL字符串就被隔离了,在使用数据库的时候感觉更像是在使用本地的API。对于更复杂的应用程序,这可能是一个完整的独立程序集,其中包含一些管理逻辑功能区域的类型,其中像 Exectue()
这样的核心方法是 internal
。不管您最终如何支持交易,这都是一个好主意。
说到过程,存储过程也是处理此问题的完美方式。有一个存储过程来完成所有工作,并在准备就绪时调用它。
另一种选择是重载接受多个查询和参数集合的方法:
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
关键字如何与数组的数组一起使用...我只是没有尝试过该选项,但是按照这些思路进行操作会起作用。这里的弱点还在于,稍后的查询依赖于先前查询的结果并不是微不足道的,即使没有参数的查询仍然需要一个参数数组作为占位符。
最后一个选项是扩展持有 Execute()
方法的类型以支持交易。这里的诀窍是让这种类型成为 static
是常见的(并且是可取的),但是支持事务需要重新使用公共连接和事务对象。鉴于交易的隐含长 运行 性质,您必须一次支持多个,这意味着实例和实现 IDisposable
。
无需更改您的 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,并在每次调用 Execute 时重用它。所以你甚至会得到一个本地(非分布式)交易。
这一切都在此处的文档中进行了解释:System.Transactions Integration with SQL Server