views:

1070

answers:

4

I have a generic method to call a stored Procedure in ASP.NET:

public SqlDataReader ExecuteStoredProc(string sprocName, SqlParameter[] SqlP)
        {
            SqlDataReader iReader;
            SqlCommand sql = new SqlCommand();

            sql.CommandText = sprocName;
            sql.CommandType = CommandType.StoredProcedure;
            sql.Connection = ConnStr;
            if (SqlP != null)
            {
                foreach (SqlParameter p in SqlP)
                {
                    sql.Parameters.Add(p);
                }

            }
            sql.Connection.Open();
            iReader = sql.ExecuteReader(CommandBehavior.CloseConnection);
            sql.Dispose();

            return iReader;
        }

Even though I am calling CommandBehavior.CloseConnection the connection is not closing. I can get the data fine the first time I request a page. On reload I get the following error:

The connection was not closed. The connection's current state is open. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: The connection was not closed. The connection's current state is open.

Source Error:

Line 35: Line 36: } Line 37: sql.Connection.Open(); Line 38: iReader = sql.ExecuteReader(CommandBehavior.CloseConnection); Line 39: sql.Dispose();

Finally if I put sql.Connection.Close(); before sql.Dispose(); I get an error that iReader is not readable because it's been closed already.

Obviously I am closing my connection incorrectly, can someone point me in the right direction?

+1  A: 

I would suggest wrap the sql connection with a "using" statement, and that will take care of most sql connection issue.

using (var conn = new SqlConnection("..."))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "...";
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                // ...
            }
        }
    }

}

J.W.
Also, I won't suggest return SqlDataReader , it's best to initialize your custom object, and then return your object.
J.W.
+4  A: 

When you return a DataReader, the underlying connection must remain open. It's the consumer's responsibility to properly clean up resources.

public SqlDataReader ExecuteStoredProc(string sprocName, SqlParameter[] SqlP)
{
    SqlCommand sql = new SqlCommand();

    sql.CommandText = sprocName;
    sql.CommandType = CommandType.StoredProcedure;
    sql.Connection = ConnStr;
    if (SqlP != null)
    {
        foreach (SqlParameter p in SqlP)
        {
            sql.Parameters.Add(p);
        }

    }
    sql.Connection.Open();
    return sql.ExecuteReader(CommandBehavior.CloseConnection);          
}

public void ConsumingMethod()
{
    using(SqlDataReader reader = ExecuteStoredProc("MyProc", params))
    {
        while(reader.Read())
        {
            //work with your reader
        }
    }
}
Corbin March
I agree with your approach "the consumer's responsibility to properly clean up resources.", but it's generally not to rely on that, the consumer is too lazy to do this.
J.W.
Thanks this helped. I added a iReader.Close() to my consumingmethod after my while loop.
RedWolves
Don't you need to close and dispose of that connection you're creating in ExecuteStoredProc?
romkyns
@romkyns: Since the Reader was created withCommandBehavior.CloseConnection, the Connection will be closed when the ConsumingMethod'sReader is closed by its using statement: http://msdn.microsoft.com/en-us/library/system.data.commandbehavior%28VS.80%29.aspx.It feels dangerous since we have to trust a consumer to do the right thing, but it's the only solution if a Reader leaves method scope.
Corbin March
A: 

The idea is to do a Connection.Close(); after you've finished with the SqlReader, so basically instead of placing the close() statement before the SqlReader.Dispose() command, you should place it below.

mtranda
A: 

This is my preferred way to process IDataReader. Let the caller creates SqlConnection instance and passes to methods.

It's expensive to create SqlConnection instance. And you’ll end up code call the same ExecuteStoredProc method multiple times in different situation.

Thus, I refactor ExecuteStoredProc method by adding SqlConnection instance as part of the parameter.

using (SqlConnection conn = new SqlConnection())
{
    conn.ConnectionString = // Connection String;
    conn.Open();

    using (IDataReader reader = foo.ExecuteStoredProc(conn, sprocName, SqlP))
    {
     // Process IDataReader
    }
}