3

In my testing project, I have a static class called FixtureSetup which I use to setup my integration testing data for validation.

I use the same SqlCommand and SqlParameter variable (not the object itself) within that class, repeatedly, using the same variable references over and over, assigning new SqlCommand and SqlParameter objects each time. My connection itself is created once and passed into the methods performing the setup, so each setup uses it's own distinct connection reference, and while the same conn is used multiple times, it's always in a linear sequence.

In one such method, I ran into a very odd situation, where my SqlCommand variable simply appears to have gotten tired.

        cmd = new SqlCommand("INSERT INTO Subscription (User_ID, Name, Active) VALUES (@User_ID, @Name, @Active)", conn);
        parameter = new SqlParameter("@User_ID", TestUserID); cmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Name", "TestSubscription"); cmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Active", true); cmd.Parameters.Add(parameter);
        cmd.ExecuteNonQuery();

        cmd = new SqlCommand("SELECT Subscription_ID FROM [Subscription] WHERE Name = 'TestSubscription'", conn);
        parameter = new SqlParameter("@User_ID", TestUserID);
        cmd.Parameters.Add(parameter);
        using (dr = cmd.ExecuteReader())
        {
            while (dr.Read())
            {
                TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
            }
        }

        cmd = new SqlCommand("INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (@Subscription_ID, @Company_ID)", conn);
        parameter = new SqlParameter("@Subscription_ID", TestSubscriptionID); cmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Company_ID", KnownCompanyId); cmd.Parameters.Add(parameter);
        cmd.ExecuteNonQuery();

In the above, at the last line shown, doing the same thing I've done quite literally in dozens of other places (insert data, read the ID column and capture it), I get the following:

SetUp : System.InvalidOperationException : ExecuteNonQuery requires an open and available Connection. The connection's current state is closed. at System.Data.SqlClient.SqlConnection.GetOpenConnection(String method)

BUT - replace cmd with new variable myCmd, and everything works swimmingly!

        SqlCommand myCmd;
        myCmd = new SqlCommand("INSERT INTO Subscription (User_ID, Name, Active) VALUES (@User_ID, @Name, @Active)", conn);
        parameter = new SqlParameter("@User_ID", TestUserID); myCmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Name", "TestSubscription"); myCmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Active", true); myCmd.Parameters.Add(parameter);
        myCmd.ExecuteNonQuery();

        myCmd = new SqlCommand("SELECT Subscription_ID FROM [Subscription] WHERE Name = 'TestSubscription'", conn);
        parameter = new SqlParameter("@User_ID", TestUserID);
        myCmd.Parameters.Add(parameter);
        using (dr = myCmd.ExecuteReader())
        {
            while (dr.Read())
            {
                TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
            }
        }

        myCmd = new SqlCommand("INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (@Subscription_ID, @Company_ID)", conn);
        parameter = new SqlParameter("@Subscription_ID", TestSubscriptionID); myCmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Company_ID", KnownCompanyId); myCmd.Parameters.Add(parameter);
        myCmd.ExecuteNonQuery();

What the heck is going on here? Did my command var just get tired???

What clued me to the "fix" was I noticed in my tracing that in my "read the id" block, my cmd.Parameters block had only ONE parameter in it, the 2nd one added, and when I forced the first cmd.Parameters.Add line to execute again, the number of parameters in the list dropped to 0. That's what prompted me to try a method level SqlCommand...cause I had the crazy idea that my cmd was tired... Imagine my shock when I apparently turned out to be right!

Edit: I'm not recycling any objects here - just the variable reference itself (static SqlCommand at the class level). My apologies for the earlier confusion in my wording of the question.

The Evil Greebo
  • 7,013
  • 3
  • 28
  • 55

4 Answers4

3

use one command per query and call dispose (or better yet, wrap in a using statement). you don't want to be "reusing" ado.net components.

Jason Meckley
  • 7,589
  • 1
  • 24
  • 45
  • Why not? cmd is nothing more than a static variable reference. In every "reuse" I explicitly state: cmd = new SqlCommand -- And I'm using it in dozens and dozens of other reuses throughout this same class, too. – The Evil Greebo Mar 26 '12 at 20:45
  • The problem isnt the command, it's the **connection**. Read the error message (and my answer). – Chris Shain Mar 26 '12 at 20:47
  • I did read your answer, and I did read the error. I also inspected the error and the connection at the time the problem arose. The connection reports itself to be in a valid, open state, and changing from the use of static cmd to the method local myCmd using the *same* connection fixed the issue, so I am still not seeing how it is the *connection* that is the issue? – The Evil Greebo Mar 26 '12 at 20:53
2

Big Edit:

From: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.close.aspx

You must explicitly call the Close method when you are through using the SqlDataReader to use the associated SqlConnection for any other purpose.

The Close method fills in the values for output parameters, return values and RecordsAffected, increasing the time that it takes to close a SqlDataReader that was used to process a large or complex query. When the return values and the number of records affected by a query are not significant, the time that it takes to close the SqlDataReader can be reduced by calling the Cancel method of the associated SqlCommand object before calling the Close method.

so try:

using (dr = cmd.ExecuteReader())
{
    while (dr.Read())
    {
        TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
    }
    dr.Close();
}
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • I'm passing the connection in. It's the SqlCommand object reference that I'm recycling. I'm not getting how your answer relates... – The Evil Greebo Mar 26 '12 at 20:48
  • Aha! Now that link helps crystallize the issue. I guess it serves me right for being lazy about the ADO objects in my test project. – The Evil Greebo Mar 26 '12 at 20:56
  • No... it still doesn't click - because as Paul pointed out in the question's comments, I'm only reusing variables, not the objects themselves. – The Evil Greebo Mar 26 '12 at 21:04
  • Well now that's a good point. Am I wrong in thinking that putting SqlDataReader within a Using block automatically closes the reader? – The Evil Greebo Mar 26 '12 at 21:11
  • It automatically disposes the reader, and I would have thought that it would close it also, but as per the docs that doesn't seem to be the case! – Chris Shain Mar 26 '12 at 21:12
  • Hmm. Annoying in cases where I find the value and return from within the using block. Means I have to rework each of those. Will do that and let you know here in a few... – The Evil Greebo Mar 26 '12 at 21:14
  • Yep, you're absolutely right. using(dr = cmd.ExecuteReader()) {} does NOT call dr.Close as a part of the dispose. Once I made sure dr.Close was being called in every case, this issue vanished. – The Evil Greebo Mar 26 '12 at 21:18
  • @TheEvilGreebo: Actually disposing a `SqlDataReader` [**will** close it implicitely](http://www.bilder-hochladen.net/files/big/4709-os-dd45.png)(from ILSpy reflector, you see where `close` is called from) – Tim Schmelter Mar 26 '12 at 21:40
  • @TimSchmelter I see a "used by" from Dispose, but can you show the decompiled Dispose method? The fact that adding it fixed TheEvilGreebo's problem seems to suggest otherwise. Maybe it isn't used in all code paths? – Chris Shain Mar 26 '12 at 21:42
  • [Yes i can](http://www.bilder-hochladen.net/files/4709-ot-49c9.png). That it has seemingly fixed your issue might be a side-effect. – Tim Schmelter Mar 26 '12 at 21:49
  • Very Curious. I will have to have a deeper look when I get home. – Chris Shain Mar 26 '12 at 21:55
  • Hmm. So perhaps the data reader wasn't disposing yet, despite the call to Dispose? That seems unlikely... and still doesn't explain why when I wasn't explicitly closing and kept reusing cmd it failed, but using the new var myCmd worked (with the same conn) or why cmd's parameters didn't add up to 2 when examined in the build up to the execute following the datareader usage... Where's Alice, the Mad Hatter's here with her tea. – The Evil Greebo Mar 26 '12 at 22:59
2

Check that you haven't set the DataReader to CommandBehavior.CloseConnection since you mentioned that you're re-using the connection for your test initialization.

Also, the DataReader does take resources, so utilize Dispose

Brett Veenstra
  • 47,674
  • 18
  • 70
  • 86
  • Good thought, but I checked that. I even tried explicitly giving the DataReader its own distinct connection at one point... didn't make a difference. – The Evil Greebo Mar 26 '12 at 20:50
0

Do you really need to do make a new connection object after each try?

myCmd = new SqlCommand(...)
//your code
myCmd = new SqlCommand(...)
//etc

You can just say:

myCmd.CommandText = "INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (@Subscription_ID, @Company_ID)";

so that you may re-use your command object. Additionally you can reset your parameters as well after each call. Just call myCmd.Parameters.Clear().

Also, make sure you wrap your SqlCommand in a using statement so they will be properly cleaned up.

using (SqlCommand cmd = new SqlCommand())
{
    cmd.CommandText = "some proc"
    cmd.Connection = conn;
    //set params, get data, etc
    cmd.CommandText = "another proc"
    cmd.Parameters.Clear();
    //set params, get date, etc.
}
Bryan Crosby
  • 6,486
  • 3
  • 36
  • 55
  • Well, yes, I can do that, but that doesn't really answer why the existing SqlCommand cmd variable which is being reset repeatedly is stopping working after dozens of successful uses. – The Evil Greebo Mar 26 '12 at 20:49
  • @TheEvilGreebo: Give it a try and see if it still happens. – Bryan Crosby Mar 26 '12 at 20:50
  • Not necessary, I've shown that I already got it to work. The question isn't "how do I fix it?" but "why doesn't it work as is?" – The Evil Greebo Mar 26 '12 at 20:52
  • Is your `SqlConnection` object static? That could be a problem. – Bryan Crosby Mar 26 '12 at 20:56
  • No, the connection object is created in my fixture setup method and passed in. – The Evil Greebo Mar 26 '12 at 21:03
  • 1
    It usually ends up being a problem with the connection. To echo on what others have said here, make sure to do all your work within the scope of a SqlConnection block. As long as the connection is open, you can let ADO.NET do it's job. – Bryan Crosby Mar 26 '12 at 21:06
  • Actually per this link - reusing the objects themselves is a BAD idea: http://stackoverflow.com/questions/9705637/executereader-requires-an-open-and-available-connection-the-connections-curren/9707060#9707060 – The Evil Greebo Mar 26 '12 at 21:07
  • 1
    Let me clarify what I meant by "re-use" above was when you dispose the connection via `using`, ADO.NET doesn't necessarily close the connection to the server; rather, it returns it to the pool by default. You can open one connection, execute your couple of sprocs, and then close OR it's fine to use using(SqlConnection x =...) {}, a few lines later, using(SqlConnection y = ...), of course depending on what you are doing. Tim's 3rd bullet point explains that. In your case, your tests appear to be relatively small enough that either way won't make much of a difference, so it's up to you. – Bryan Crosby Mar 26 '12 at 21:25
  • All true. Still not related to the question, but true! – The Evil Greebo Mar 26 '12 at 21:26