SqlCommand.Dispose() before SqlTransaction.Commit(

2019-06-15 02:44发布

问题:

would it work to dispose a command assigned to a transaction before the transaction is committed? I tested the following code myself, and it seems to have worked fine, but this is a rather small example, so I'm looking for confirmation if someone positively knows.

internal static void TestTransaction()
{
    try
    {
        Program.dbConnection.Open();
        using (SqlTransaction transaction = Program.dbConnection.BeginTransaction())
        {
            Boolean doRollback = false;
            for (int i = 0; i < 10; i++)
            {
                using (SqlCommand cmd = new SqlCommand("INSERT INTO [testdb].[dbo].[transactiontest] (testvalcol) VALUES (@index)", Program.dbConnection, transaction))
                {
                    cmd.Parameters.AddWithValue("@index", i);
                    try
                    {
                        cmd.ExecuteNonQuery();
                    }
                    catch (SqlException)
                    {
                        doRollback = true;
                        break;
                    }
                }
            }
            if (doRollback)
                transaction.Rollback();
            else
                transaction.Commit();
        }
    }
    finally
    {
        Program.dbConnection.Close();
    }
}

回答1:

The connection, transaction and command objects are just vehicles to send commands to a database. Once a command is executed the database has received it. Whatever you do with the command object afterwards, dispose it, burn it, or shoot it to the moon, this fact does not change. (It can only be rolled back).

You can create and dispose as many commands as you like within the scope of one SqlConnection (with or without SqlTransaction). And you can start and dispose as many transactions as you like within one SqlConnection. To demonstrate this, see:

using (var conn = new SqlConnection(@"server=(local)\sql2008;database=Junk;Integrated Security=SSPI"))
{
  conn.Open();
  // Repeat this block as often as you like...
  using (var tran = conn.BeginTransaction())
  {
    using (var cmd = new SqlCommand("INSERT INTO Mess VALUES ('mess1')", conn, tran))
    {
      cmd.ExecuteNonQuery(); // Shows in Sql profiler
    }
    tran.Commit(); // Commits
  }
  using (var cmd = new SqlCommand("INSERT INTO Mess VALUES ('mess2')", conn))
  {
    cmd.ExecuteNonQuery(); // Executes and commits (implicit transaction).
  }
}

Of course, for healthy code you need to dispose of all objects in the correct order. Disposing a command after disposing a SqlConnection may cause the connection object to stay in memory.



回答2:

Yes, it's probably safe. The using() is closing the Command, not the Connection.

But you should put that Connection in another using() block or in a try/finally construct.



回答3:

Confirmed, this works very well and (at least here at our company) is even considered the correct approach.

  1. create connection
  2. create transaction
  3. create command(s), use the transaction, execute
  4. dispose command(s)
  5. commit transaction
  6. dispose connection