我正在尝试只使用一个连接并一起运行两个命令,一个使用事务,一个不使用。
没有跟踪/日志记录功能,因为此解决方案部署在另一个位置。因此,当流程部分失败时,我至少可以按照日志进行操作。
我会在这里添加我的测试代码:
SqlConnection connection = GetConnection();
SqlTransaction transaction = null;
try
{
connection.Open();
transaction = connection.BeginTransaction();
SqlCommand logCommand = new SqlCommand("Log before main command", connection);
logCommand.ExecuteNonQuery();
string sql = "SELECT 1";
SqlCommand command = new SqlCommand(sql, connection, transaction);
int rows = command.ExecuteNonQuery();
logCommand = new SqlCommand("Log after main command", connection);
logCommand.ExecuteNonQuery();
// Other similar code
transaction.Commit();
command.Dispose();
}
catch { /* Rollback etc */ }
finally { /* etc */ }
我收到了一个错误:
当分配给命令的连接处于挂起的本地事务中时,ExecuteNonQuery要求命令具有事务。该命令的Transaction属性尚未初始化。
如果没有其他没有交易的连接,有没有办法实现我想做的事情?
或者,如果有更好的建议来通过单一连接以不同的方式优化我的代码,我就可以了解它。
答案 0 :(得分:2)
错误发生在这里:
SqlConnection connection = GetConnection();
SqlTransaction transaction = null;
try
{
connection.Open();
transaction = connection.BeginTransaction();
SqlCommand logCommand = new SqlCommand("Log before main command", connection); // <--- did not give the transaction to the command
logCommand.ExecuteNonQuery(); // <--- Exception: ExecuteNonQuery requires the command to have a transaction ...
string sql = "SELECT 1";
SqlCommand command = new SqlCommand(sql, connection, transaction);
int rows = command.ExecuteNonQuery();
logCommand = new SqlCommand("Log after main command", connection);
logCommand.ExecuteNonQuery(); // <--- Same error also would have happened here
// Other similar code
transaction.Commit();
command.Dispose();
}
catch { /* Rollback etc */ }
finally { /* etc */ }
发生这种情况的原因是,当在事务中登记连接时,该连接上的所有命令都在该事务中。换句话说,您不能让命令“退出”在事务中,因为事务适用于整个连接。
不幸的是,SqlClient API有点误导,因为在调用connection.BeginTransaction()之后,您仍然必须将SqlTransaction提供给SqlCommand。如果您没有明确地将该事务提供给该命令,那么当您执行该命令时,SqlClient将为您谴责它(“不要忘记告诉我有关我已经知道我正在进行的事务!”)这是您所见到的例外情况。
这种笨拙是有些人更喜欢使用TransactionScope的原因之一,虽然我个人不喜欢TransactionScope因为它的“隐式魔术”API以及与异步的不良交互而导致非分布式交易。
如果您不希望'log'命令与main命令位于同一事务中,则必须为它们使用另一个连接,或者仅在该主命令的持续时间内使用该事务:
try
{
connection.Open();
// No transaction
SqlCommand logCommand = new SqlCommand("select 'Log before main command'", connection);
logCommand.ExecuteNonQuery();
// Now create the transaction
transaction = connection.BeginTransaction();
string sql = "SELECT 1";
SqlCommand command = new SqlCommand(sql, connection, transaction);
int rows = command.ExecuteNonQuery();
transaction.Commit();
// Transaction is completed, now there is no transaction again
logCommand = new SqlCommand("select 'Log after main command'", connection);
logCommand.ExecuteNonQuery();
// Other similar code
command.Dispose();
}
//catch { /* Rollback etc */ }
finally
{
if (transaction != null)
{
transaction.Dispose();
}
}
如果您确实希望它们成为交易的一部分,您必须明确地将交易交给他们:
SqlConnection connection = GetConnection();
SqlTransaction transaction = null;
try
{
connection.Open();
transaction = connection.BeginTransaction();
SqlCommand logCommand = new SqlCommand("select 'Log before main command'", connection, /* here */ transaction);
logCommand.ExecuteNonQuery();
string sql = "SELECT 1";
SqlCommand command = new SqlCommand(sql, connection, transaction);
int rows = command.ExecuteNonQuery();
logCommand = new SqlCommand("select 'Log after main command'", connection, /* and here */ transaction);
logCommand.ExecuteNonQuery();
// Other similar code
transaction.Commit();
command.Dispose();
}
//catch { /* Rollback etc */ }
finally
{
if (transaction != null)
{
transaction.Dispose();
}
}