105

I'm working with legacy code here and there are many instances of SqlDataReader that are never closed or disposed. The connection is closed but, I am not sure if it is necessary to manage the reader manually.

Could this cause a slowdown in performance?

4 Answers 4

150

Try to avoid using readers like this:

SqlConnection connection = new SqlConnection("connection string");
SqlCommand cmd = new SqlCommand("SELECT * FROM SomeTable", connection);
SqlDataReader reader = cmd.ExecuteReader();
connection.Open();
if (reader != null)
{
      while (reader.Read())
      {
              //do something
      }
}
reader.Close(); // <- too easy to forget
reader.Dispose(); // <- too easy to forget
connection.Close(); // <- too easy to forget

Instead, wrap them in using statements:

using(SqlConnection connection = new SqlConnection("connection string"))
{

    connection.Open();

    using(SqlCommand cmd = new SqlCommand("SELECT * FROM SomeTable", connection))
    {
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
            if (reader != null)
            {
                while (reader.Read())
                {
                    //do something
                }
            }
        } // reader closed and disposed up here
        
    } // command disposed here
    
} //connection closed and disposed here

The using statement will ensure correct disposal of the object and freeing of resources.

If you forget then you are leaving the cleaning up to the garbage collector, which could take a while.

Sign up to request clarification or add additional context in comments.

10 Comments

You don't need the .Close() statement in either sample: it's handled by the .Dispose() call.
This sample doesn't show the underlying connection being closed/disposed, which is the most important thing.
Probably want to check if it .HasRows rather then null.
@Andrew If ExecuteReader throws an exception, how can it return null?
@JohH: the while (reader.Read()) in the example accomplishes the same as .HasRows, and you need to .Read anyways to advance the reader forward to the first row.
|
69

Note that disposing a SqlDataReader instantiated using SqlCommand.ExecuteReader() will not close/dispose the underlying connection.

There are two common patterns. In the first, the reader is opened and closed within the scope of the connection:

using(SqlConnection connection = ...)
{
    connection.Open();
    ...
    using(SqlCommand command = ...)
    {
        using(SqlDataReader reader = command.ExecuteReader())
        {
            ... do your stuff ...
        } // reader is closed/disposed here
    } // command is closed/disposed here
} // connection is closed/disposed here

Sometimes it's convenient to have a data access method open a connection and return a reader. In this case it's important that the returned reader is opened using CommandBehavior.CloseConnection, so that closing/disposing the reader will close the underlying connection. The pattern looks something like this:

public SqlDataReader ExecuteReader(string commandText)
{
    SqlConnection connection = new SqlConnection(...);
    try
    {
        connection.Open();
        using(SqlCommand command = new SqlCommand(commandText, connection))
        {
            return command.ExecuteReader(CommandBehavior.CloseConnection);
        }
    }
    catch
    {
        // Close connection before rethrowing
        connection.Close();
        throw;
    }
}

and the calling code just needs to dispose the reader thus:

using(SqlDataReader reader = ExecuteReader(...))
{
    ... do your stuff ...
} // reader and connection are closed here.

7 Comments

In the second code snippet where the method returns a SqlDataReader the command is not disposed. Is that ok and is it ok to dispose the command (enclose it in a using block) and then return the reader?
@alwayslearning that's exactly the scenario that I have......can you close/dispose of the SqlCommand when you are returning the SqlDataReader to the caller?
This is bad. If you REALLY can't bear to use usings then call dispose in the finally {} block after catch. The way this is written, successful commands would never be closed or disposed.
@smdrager, if you read the answer closer, he is talking about a method that returns a reader. If you use .ExecuteReader(CommandBehavior.CloseConnection); then by disposing the READER, the connection will be closed. So the calling method only needs to wrap the resulting reader in a using statement. using(var rdr = SqlHelper.GetReader()){ //... } if you were close it in the finally block, then your reader would fail to read because the connection is closed.
Disposing the command disposes the reader and any attempt to read fails, the command should not be disposed in this case.
|
15

To be safe, wrap every SqlDataReader object in a using statement.

4 Comments

Fair enough. However, does it actually make a difference in performance if there is no using statement?
A using statement is the same as wrapping the DataReader code in a try..finally... block, with the close/dispose method in the finally section. Basically, it just "guarantees" that the object will be disposed of properly.
This is straight from the link I provided: "The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object."
Continued... "You can achieve the same result by putting the object inside a try block and then calling Dispose in a finally block; in fact, this is how the using statement is translated by the compiler."
5

Just wrap your SQLDataReader with "using" statement. That should take care of most of your issues.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.