2

I am trying to either pass a reader by reference or to have one returned. And I am having issues with it on the return.

public static SqlDataReader GetSql(string businessUnit, string taskId)
{
            const string connstring = "Connection string";
            SqlConnection conn = new SqlConnection(connstring);
            SqlCommand command = new SqlCommand();
            SqlDataReader reader;
            try
            {
                conn.Open();
                command.Connection = conn;
                command.CommandType = CommandType.Text;
                command.CommandText =
                    "SELECT * FROM Audits WHERE BusinessUnit = @BU AND TaskID = @TID";
                command.Parameters.AddWithValue("@BU", businessUnit);
                command.Parameters.AddWithValue("@TID", taskId);
                return reader = command.ExecuteReader(CommandBehavior.CloseConnection);
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                conn.Close();
            }
}


SqlDataReader reader = QaqcsqlLib.GetSql("job", "Task1");

if (reader.HasRows)
{
   while (reader.Read())
      MessageBox.Show(reader[0].ToString());
}

but I get the following error

Invalid attempt to call HasRows when reader is closed.

Any Ideas?

4 Answers 4

12

This is the problem:

finally
{
    conn.Close();
}

You're closing the connection before the method returns. The reader isn't going to be able to function without an open connection.

(It's not clear why you've got the reader variable at all, given that you only use it when returning.)

Returning a SqlDataReader in a method which itself opens the connection is generally tricky - because it means you don't have a nice way of closing the connection. Better would be to let the caller pass in the connection, at which point you can have:

using (var connection = new SqlConnection(...))
{
    using (var reader = QaqcsqlLib.GetSql(connection, "job", "Task1"))
    {
        // Use the reader here
    }
}

EDIT: As noted by Scott, your use of CommandBehavior.CloseConnection would allow closing the reader to close the connection. However, it makes other things trickier. For example, you'd have to dispose of the connection if an exception occurs (because then the caller won't have a chance), but not otherwise - I still prefer my approach.

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

5 Comments

so, I should pass both a connection and a reader? I think i get it now. I was thinking that a reader could be executed then read disconnected. would a dataadapter work like I describe?
@Herrozerro: The reader doesn't actually contain all of the data from your query, it fetches it as you need it to conserve memory. msdn.microsoft.com/en-us/library/haa3afyz(v=vs.80).aspx
@Herrozerro: No, you pass a connection, and return a reader associated with that connection. A DataAdapter wouldn't do it either - but you could return a DataTable if you really wanted.
@JonSkeet I have to disagree with you. He is passing in CommandBehavior.CloseConnection to ExecuteReader, when he disposes of his reader it will close the underlying connection for him, the only correction he needed to make is remove the finally and add a using block around his reader.
@ScottChamberlain: That's true, good point... (And I'll edit something along those lines into the ansewr) although I would personally still prefer the approach taken here, as it's clearer IMO.
4

John is right about why you are having the problem but you don't need to pass in a connection.

I can see that you are already using the SqlCommand.ExecuteReader(CommandBehavior) overload when you start your reader and passing in CommandBehavior.CloseConnection. However the thing you are doing wrong is you are never disposing of the reader that is returned from your function. Remove the finally block then wrap your returned reader in a using block. This will close the underlying connection for you when you exit the block (becuse you passed in CommandBehavior.CloseConnection)

using(SqlDataReader reader = QaqcsqlLib.GetSql("job", "Task1"))
{
    while (reader != null && reader.Read()) //Added null check as your GetSql could return null.
      MessageBox.Show(reader[0].ToString());
}

I also stripped out reader.HasRows because it is unnessasary. If no results where returned the first call to reader.Read() will return false and the code inside the while loop will never execute.


You still need to close the connection when a exception occurs but you can just move the close from the finally in to the catch.

catch (Exception ex)
{
    conn.Dispose(); //Disposing closes the connection.
    return null;
}

Comments

0

you can try follow the next example http://msdn.microsoft.com/en-us/library/haa3afyz.aspx .

You shouldn't call to close method before to use your reader.

Comments

0

Jon Skeet and Scott Chamberlain are both correct. If you wish to keep with your way of structuring the code (as opposed to Jon Skeet's approach), you will need to modify your code so that the connection is closed should an error occur while executing the SqlCommand.

public static SqlDataReader GetSql(string businessUnit, string taskId)
{
    const string connstring = "Connection string";
    SqlConnection conn = null;
    SqlCommand command = null;
    SqlDataReader reader = null;
    try
    {
        conn = new SqlConnection(connstring);
        command = new SqlCommand();
        command.Connection = conn;
        command.CommandType = CommandType.Text;
        command.CommandText = "SELECT * FROM Audits WHERE BusinessUnit = @BU AND TaskID = @TID";
        command.Parameters.AddWithValue("@BU", businessUnit);
        command.Parameters.AddWithValue("@TID", taskId);
        conn.Open();
        reader = command.ExecuteReader(CommandBehavior.CloseConnection);
        conn = null;
        return reader;
    }
    catch (Exception ex)
    {
        return null;
    }
    finally
    {
        if (conn != null) conn.Dispose();
        if (command != null) command.Dispose();
    }
}

2 Comments

That conn = null + handling it in the finally block is a really weird way of doing it. I think if you planned on explicitly disposing in the event of an exception, do it in the catch block.
@ScottChamberlain That's the way it is suggested to do it on MSDN msdn.microsoft.com/query/… - CA2000: Dispose objects before losing scope. If you do it via the catch block, FxCop warns that SqlConnection is not disposed of along all exception paths.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.