4

I am using the SqlConnection class and running into problems with command time outs expiring.

First off, I am using the SqlCommand property to set a command timeout like so:

command.CommandTimeout = 300;

Also, I have ensured that the Execution Timeout setting is set to 0 to ensure that there should be no timeouts on the SQL Management side of things.

Here is my code:

using (SqlConnection conn = new SqlConnection(connection))
            {
                conn.Open();

                SqlCommand command = conn.CreateCommand();

                var transaction = conn.BeginTransaction("CourseLookupTransaction");

                command.Connection = conn;
                command.Transaction = transaction;
                command.CommandTimeout = 300;

                try
                {
                    command.CommandText = "TRUNCATE TABLE courses";
                    command.ExecuteNonQuery();

                    List<Course> courses = CourseHelper.GetAllCourses();

                    foreach (Course course in courses)
                    {
                        CourseHelper.InsertCourseLookupRecord(course);
                    }

                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    Log.Error(string.Format("Unable to reload course lookup table: {0}", ex.Message));
                }
            }

I have set up logging and can verify exactly 30 seconds after firing off this function, I receive the following error message in my stack trace:

Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.

In the interest of full disclosure: InsertCourseLookupRecord() found inside the above using statements foreach, is performing another query to the same table in the same database. Here is the query it is performing:

INSERT INTO courses(courseid, contentid, name, code, description, url, metakeywords, metadescription)
VALUES(@courseid, @contentid, @name, @code, @description, @url, @metakeywords, @metadescription)"

There is over 1400 records in this table.

I will certify any individual(s) that helps me solve this as a most supreme grand wizard.

jayrue
  • 151
  • 1
  • 11
  • 1
    That `CommandTimeout` is only for the `command` object. The call to `InsertCourseLookupRecord` wouldn't have any inheritance of that 300 second timeout from `command`. I doubt your `TRUNCATE TABLE` command is causing the timeout. Can you verify whether or not the table was actually truncated? – Thomas Stringer Aug 13 '15 at 21:28
  • I doubt its the `TRUNCATE` query too, I should look into setting a timeout for the `INSERT` query done in InsertCourseLookupRecord... Because I am rollingback the transaction in the `catch`, I can not verify if the truncate is actually working. – jayrue Aug 13 '15 at 22:02

3 Answers3

4

I believe what is happening is that you have a deadlock situation that is causing your query in the InsertCourseLookupRecord() function to fail. You are not passing your connection to InsertCourseLookupRecord() so I am assuming you are running that in a separate connection. So what happens is:

  • You started a transaction.
  • You truncate the table.
  • InsertCourseLookupRecord starts another connection and tries to insert data into that table, but the table is locked because your transaction isn't committed yet.
  • The connection in the function InsertCourseLookupRecord() times out at the timeout value defined for that connection of 30 seconds.

You could change the function to accept the command object as a parameter and use it inside the function instead of creating a new connection. This will then become part of the transaction and will all be committed together.

To do this change your function definition to:

public static int InsertCourseLookupRecord(string course, SqlCommand cmd)

Take all the connection code out of the function because you're going to use the cmd object. Then when you're ready to execute your query:

myCommand.Parameters.Clear();  //need only if you're using command parameters
cmd.CommandText = "INSERT BLAH BLAH BLAH";
cmd.ExecuteNonQuery();

It will run under the same connection and transaction context.

You call it like this in your using block:

CourseHelper.InsertCourseLookupRecord(course, command);

You could also just take the code in the InsertCourseLookupRecord and put it inside the for loop instead and then reuse the command object in your using block without needing to pass it to a function at all.

Brian Pressler
  • 6,653
  • 2
  • 19
  • 40
  • Is there anyway to prevent a deadlock from occurring when a transaction has begin? My understanding would be that a separate transaction would be created for the second query. When you say pass the connection to the function, are you referring to the `conn` variable in the using statement? I am not quite sure how this would be done... – jayrue Aug 13 '15 at 22:12
  • You are correct, every call to InsertCourseLookupRecord in your for loop must be opening a new connection and so it must be running in a separate transaction. That is the problem. As soon as you truncate the table, no other transaction will be allowed to do anything to it until your first transaction commits. So you either have to do all of your inserts in the same transaction or commit the truncate table statement and then do your inserts. – Brian Pressler Aug 13 '15 at 22:30
  • Thanks Brian, this is getting very close to being my questions accepted answer. Your status of certified Supreme Grand Wizard is near. Based on the problem I initially tried fixing with this using statement, my only option is to do all of my inserts within the same transaction. Unfortunately there is around 1400 inserts to be done independently in this foreach loop. I am a little bit stumped on how I can pass a transaction to my InserCourseLookupRecord. Is there anyway you could provide a helpful example in your initial answer? – jayrue Aug 13 '15 at 22:36
  • Are you using the InsertCourseLookupRecord() function elsewhere? If not I would just recommend getting rid of the function and putting it's code inline. Just don't recreate any connection objects and reuse the command object to execute your insert statement. – Brian Pressler Aug 13 '15 at 22:42
  • Hi Brain, Thanks so much you have provided much clarity! Yes the function is being used elsewhere, so unfortunately I cant dump that code somewhere else. It is a rather complex method as well. The previous developer responsible for making this used the SqlHelper class and is using SqlParameter to build out the values to insert into the query. In this instance I will use Paramaters.Clear(). – jayrue Aug 14 '15 at 14:51
  • I have not attempted this yet, but it looks good and makes sense. I am satisfied with this as answer so I will mark it as so. Thank you for helping me figure this out :) – jayrue Aug 14 '15 at 14:52
1

Because you are using two separate SqlConnection objects you are deadlocking your self due to the SqlTransaction you started in your outer code. The query in InsertCourseLookupReacord and maybe in GetAllCourses get blocked by the TRUNCATE TABLE courses call which has not yet been committed. They wait 300 seconds for the truncate to be committed then time out.

You have a few options.

  1. Pass the SqlConnection and SqlTransaction in to GetAllCourses and InsertCourseLookupRecord so they can be part of the same transaction.
  2. Use a "ambient transaction" by getting rid of the SqlTransaction and using a System.Transaction.TransactionScope instead. This causes all connections that are opened to the server all share a transaction. This can cause maintenance issues as depending on what the queries are doing it as it may need to invoke the Distributed Transaction Coordinator which may be disabled on some computers (from the looks of what you showed you would need the DTC as you have two open connections at the same time).

The best option is try to change your code to do option 1, but if you can't do option 2.

Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • There is no query done in `GetAllCourses()`. Also, there is no wait of 300 seconds performed at all. I am getting the exception exactly 30 seconds after the using statement begins. Are you saying that it is not possible to have two separate SqlConnection objects? Only one can be open in an application at a time? – jayrue Aug 13 '15 at 22:04
  • You can have more than one, but the 2nd one is waiting for the 1st one to finish before it can start and the 1st one is waiting on the 2nd one to finish before it finishes. They must be part of the same transaction, you can do that by them being in the same connection or by using a `TransactionScope` and having the "Distributed Transaction Coordinator" service running on the machine. (often the service is set to "Manual" and is not started on most desktop machines) – Scott Chamberlain Aug 13 '15 at 22:42
0

Taken from the documentation:

CommandTimeout has no effect when the command is executed against a context connection (a SqlConnection opened with "context connection=true" in the connection string)

Please review your connection string, thats the only possibility I can think of.

Michael Sander
  • 2,677
  • 23
  • 29