单个SqlConnection具有Transaction,多个命令的情况

时间:2014-09-16 04:39:36

标签: c# sqlconnection sqlcommand sqltransaction

我正在尝试只使用一个连接并一起运行两个命令,一个使用事务,一个不使用。

没有跟踪/日志记录功能,因为此解决方案部署在另一个位置。因此,当流程部分失败时,我至少可以按照日志进行操作。

我会在这里添加我的测试代码:

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属性尚未初始化。

如果没有其他没有交易的连接,有没有办法实现我想做的事情?

或者,如果有更好的建议来通过单一连接以不同的方式优化我的代码,我就可以了解它。

1 个答案:

答案 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();
    }
}