views:

45

answers:

3

I have some code that I want to execute as follows. But I keep getting the exception "This SqlTransaction has completed; it is no longer usable" on the 2nd iteration. Could someone help me point out what I am doing wrong here? Thanks!

    SqlConnection cn = (SqlConnection)SqlConnectionManager.Instance.GetUserConnection(user);
 cn.Open();
 try
 {
    foreach (Master mRecord in masterList)
  {
   if (sqlTransaction == null)
       sqlTransaction = cn.BeginTransaction();
   SqlCommand cm = cn.CreateCommand();
   cm.Transaction = sqlTransaction;
   cm.CommandType = CommandType.StoredProcedure;
   cm.CommandText = "pr_InsertRecords";
       try
   {
    cm.ExecuteNonQuery();
    Debug.WriteLine("Auditor.Write: end sql table value param");
    sqlTransaction.Commit();
    sqlTransaction.Dispose();
   }
   catch (Exception Ex)
   {
    Debug.WriteLine(" Exception message: " + Ex.Message);
    if (Ex.InnerException != null)
    {
     Debug.WriteLine("Inner exception message" + Ex.InnerException.Message);
    }
    sqlTransaction.Rollback();
   }
  }
 }
 finally
 {
        cn.Close();
      }
A: 

You need to create a new SqlTransaction object on each iteration or move the transaction completely outside the loop if you are wanting all the operations inside the loop to occur in a single transaction. Once you commit a transaction, it is necessary to call BeginTransaction again on the connection to start a new one. You cannot reuse the old transaction object.

Mark Wilkins
A: 

Try setting your sqlTransaction object to null after disposing it. Side note, you really should be wrapping those IDisposable objects in using blocks so Dispose is called at all times.

sqlTransaction.Commit();
sqlTransaction.Dispose();
sqlTransaction = null;
Steve Danner
+1  A: 

Inside the loop you either commit or rollback, but you do not reset the reference to null. SqlTransaction in general is not used as this, is used in a using() block, just as a SqlConnection is:

using (SqlConnection cn = SqlConnectionManager.Instance.GetUserConnection(user)) 
{
  foreach (Master mRecord in masterList)
  {
  try
  {
    using (SqlTransaction sqlTransaction = cn.BeginTransaction()) 
    {
      using (SqlCommand cm = cn.CreateCommand()) 
      {
        cm.Transaction = sqlTransaction;
        cm.CommandType = CommandType.StoredProcedure;
        cm.CommandText = "pr_InsertRecords";
        cm.ExecuteNonQuery();
      }
      sqlTransaction.Commit();
      Debug.WriteLine("Auditor.Write: end sql table value param");
    }
  }
  catch (Exception Ex)
  {
    Debug.WriteLine(" Exception message: " + Ex.Message);
  }
}
Remus Rusanu
Hi Remus, one question regarding this approach. What if the SqlTransaction is being passed to the method as a parameter?eg:public void Write(SqlTransaction sqlTransaction, List<Master> masterRecords, User user, InsertMethod method){}
In that case the caller must also pass the SqlConnection to be used, and is the caller responsibility to have an `using` block. Your method, as the callee, should not commit nor dispose the SqlTransaction object passed in, because if it does it would render it unusable further by the caller.
Remus Rusanu