5

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

没有的是跟踪/记录功能,因为该解决方案部署在另一个位置。这样当过程部分失败时,我至少可以遵循日志。

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

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

有没有办法在没有另一个无事务连接的情况下实现我想要做的事情?

或者,如果有更好的建议以不同的方式通过单个连接优化我的代码,我愿意学习它。

4

1 回答 1

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 以及与异步的不良交互。

如果您不希望“日志”命令与主命令位于同一事务中,则必须为它们使用另一个连接,或者仅在该主命令期间保留事务:

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();
    }
}
于 2015-03-03T04:40:45.870 回答