Which of these methods is better for closing SqlDataReader:
SqlDataReader reader = comm.ExecuteReader();
while (reader.Read())
{
}
reader.Close();
reader.Dispose();
or
SqlDataReader reader = comm.ExecuteReader(CommandBehavior.CloseConnection);
while (reader.Read())
{
}
or there are another further closing methods?
The correct way of handling this is the using statement:
using(SqlDataReader reader = comm.ExecuteReader(CommandBehavior.CloseConnection)) {
while (reader.Read())
{
}
}
This way the object gets disposed correctly (and you don't need to call Close()).
A using statement is the best practice in such situations from my experience. It makes sure the connection is properly closed even if an exception happens somewhere inside.
using (SqlDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
//Do stuff...
}
}
Of course you could do the same with a try { } finally { }, which is what the using statement does internally. I found it's generally a good idea to get in the habit of always handling readers via the using statement to avoid the possibility of leaked connections.
Use first scenario if you work with reader within one method and second one if you pass reader as return value (not within one scope).
the doc you need is this one: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.close.aspx
To quote: "You must explicitly call the Close method when you are through using the SqlDataReader to use the associated SqlConnection for any other purpose."
Related
I'm trying to make a generic SQL call, which led me to an interesting question. I have a method that executes the SQL, and returns a SQLDataReader.
private SqlDataReader ExecuteSql(SqlCommand command)
{
using (var connection = new SqlConnection(ConnectionText, Credentials))
{
command.Connection = connection;
connection.Open();
return command.ExecuteReader();
}
}
The calling command takes the reader and processes the returned data correctly. However, knowing that the reader needs to be disposed of, I enclosed it in a using statement.
using (SqlDataReader reader = ExecuteSql(command))
{
while (reader.Read())
{
try { ... }
catch(Exception e) { ... }
}
}
I think Dispose should be called on the SqlDataReader at the end of the using statement despite where in the code it was created. However, I have not been able to find anything that specifically confirms this.
Generalizing, can the using statement be successfully used on an object that was created elsewhere in the code?
As a side note, I do realize that if the SqlDataReader was created as an object in the ExecuteSql method rather than returned directly, then there could be an issue with it throwing an exception while in the ExecuteSql method and not being disposed of.
You can accomplish this by passing an Action like so:
private void ExecuteSql(SqlCommand command, Action<SqlDataReader> action)
{
using (var connection = new SqlConnection(ConnectionText, Credentials))
{
command.Connection = connection;
connection.Open();
using (var reader = command.ExecuteReader())
{
action(reader);
}
}
}
Then the calling function:
var myCommand = //...
int id;
ExecuteSql(myCommand, (reader) => {
id = reader.GetInt32(0);
});
Now any caller doesn't need to know if they have to dispose of it or not and your connection will be disposed after the method has done it work on the reader.
It's OK to use the object created in another method in the using statement.
However, in your case, you are using an SqlDataReader which uses the SqlConnection which is disposed at the end of the ExecuteSql call.
As explained here, you need a valid connection object to use the SqlDataReader
I am trying to layer my Sql client object calls such that they get disposed of reliably. Something like this:
Open database connection -> Create command -> Read results -> close
command -> close database connection
So far this has succeeded when I do all of these things in the same method.
The problem is this is error prone. And a mess to read through.
When I try to create a common method to handle this that cleans up everything and returns a reader the connection gets closed before the reader starts.
//closes connection before it can be read...apparently the reader doesn't actually have any data at that point ... relocating to disposable class that closes on dispose
public SqlDataReader RunQuery(SqlCommand command)
{
SqlDataReader reader = null;
using (var dbConnection = new SqlConnection(_dbConnectionString))
{
try
{
dbConnection.Open();
command.Connection = dbConnection;
reader = command.ExecuteReader(); // connection closed before data can be read by the calling method
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
dbConnection.Close();
}
}
return reader;
}
I can get around this by creating my own class that implements IDispose (etc) but then when I wrap it with the same using statement it takes up just as many lines as a database connection using statement.
How can I take care of the data base connection in a repeatable class that takes care of all these artifacts and closes the connection?
You could create a class that holds an open database connection that is reusable, but I suggest reading the data into a list and returning the result:
public List<object> RunQuery(SqlCommand command)
{
List<object> results = new List<object>();
using (var dbConnection = new SqlConnection(_dbConnectionString))
{
try
{
dbConnection.Open();
command.Connection = dbConnection;
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
// Repeat for however many columns you have
results.Add(reader.GetString(0));
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
return results;
}
I don't know the structure of your data, but the important point is that you need to read your data (reader.GetString does this) before you dispose of the connection. You can find more information on how to properly read your data here.
Edit: As mentioned, I removed your finally statement. This is because your using statement is essentially doing the same thing. You can think of a using statement as a try-finally block. Your disposable object will always be disposed after the using statement is exited.
so there's no way to make a reusable method that tucks away all/most of the nested using statements?
There is a specific pattern supported for returning a DataReader from a method, like this:
static IDataReader GetData(string connectionString, string query)
{
var con = new SqlConnection(connectionString);
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = query;
var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return rdr;
}
Then you can call this method in a using block:
using (var rdr = GetData(constr, sql))
{
while (rdr.Read())
{
//process rows
}
} // <- the DataReader _and_ the connection will be closed here
If I instantiate a SqlDataReader inside a using block, do I need to call close() on the reader?
Simple example of looking up a user shown below.
using (var connection = new SqlConnection(Settings.GetValue("SqlConnection")))
{
SqlCommand command = new SqlCommand("select * from Users where Id = #Id", connection);
command.Parameters.Add("#Id", SqlDbType.UniqueIdentifier);
command.Parameters["#Id"].Value = id;
using (SqlDataReader reader = command.ExecuteReaderWithRetry())
{
reader.Read();
if (reader.HasRows)
{
//Do work
}
//Is this neccesary?
reader.Close();
}
}
If it's in a using block, then it is automatically closed. You do not need to explicitly close it.
BTW the SqlCommand in your example is disposable. You should create it in a using block too, otherwise any resources it controls won't be let go until the garbage collector collects.
Your undisposed SqlCommand is actually a good example of why C#'s emulation of RAII is not "real" RAII. You must take an explicit action (making blocks) for the RAII to kick in which is equivalent to an explicit close albeit with different syntax.
I have a problem where it appears that the reader indicates that it has rows from the returned SQL but the while loop for the reader never runs. I put a messagebox in the reader.hasrows as a verification and I put a messagebox on the first line after the while loop as well. The messagebox for the hasrows is executed but the messagebox for the Read is not executed. It is very puzzling. I tried the query against the database and it indeed does return rows. Here is the code snippet.
using (DbConnection connection = CADBase.DbProviderFactory.CreateConnection())
{
connection.ConnectionString = CADBase.DbConnectionString;
connection.Open();
using (DbCommand command = connection.CreateCommand())
{
SQL = <statement here>;
command.CommandText = SQL
using (DbDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
//NEVER MAKES IT HERE
}
}
}
}
To future readers of this question: note that the problem occurred because the OP was returning too many columns in the query. See the comments below this answer.
I'm not quite sure why this is happening, but you really only need to check for rows once, not twice, and the Read() method already does this.
So all you really need is
while (reader.Read())
{
// Do your thing
}
call MoveNext() before first call to read
You should use reader.HasRows property and not the method.
Maybe you're getting an exception on reader.Read().
But since HasRows is a property and not a method. You need to write if(reader.HasRows) to make it compile.
if(reader.HasRows)
{
//MAKES IT HERE
while (reader.Read())
{
//NEVER MAKES IT HERE
}
}
So i'm wondering what the actual code is.
Just a guess. Maybe you are accessing the reader object in that messagebox shown after reader.HasRows and before reader.Read(). Apparently the Read() method returns true only if the reader has NOT reached the last row (see: https://stackoverflow.com/a/1540626/516481). So if the reader is at the last row it will return false! Maybe the query returns just one row and you change the internal state of the reader object somehow by accessing it (maybe using the debugger) and moving the reader to the last row?
I have a C#/WPF application with a tabbed interface that has been behaving strangely. After thinking originally my problems were related to the TabControl, I now believe that it's something different and I'm completely stuck. The following method is just supposed to pull some data out of the database and load a couple of WPF ComboBoxes. The strange thing is that the code reaches a certain point, specifically the end of the loop that loads cboState's Item collection, and then continues on. No code placed below that loop executes, no errors are thrown than I can find or see, and no breakpoints placed below that loop ever get reached. I'm completely perplexed.
private void loadNewProjectTab() {
dpDate.SelectedDate = DateTime.Now;
cboProjectType.Items.Add("Proposal");
cboProjectType.Items.Add("Pilot");
cboProjectType.SelectedIndex = -1;
string sql = "SELECT State FROM States ORDER BY ID";
OleDbCommand cmd = new OleDbCommand(sql, connection);
if(connection.State == ConnectionState.Closed) {
connection.Open();
}
OleDbDataReader reader = cmd.ExecuteReader();
while(reader.HasRows) {
reader.Read();
cboState.Items.Add(reader["State"].ToString().Trim());
} // <-- Nothing below here executes.
connection.Close();
}
while(reader.HasRows) {
reader.Read();
cboState.Items.Add(reader["State"].ToString().Trim());
}
reader.HasRows will return true even after you've read all the rows and moved past the last one with reader.Read(); at that point, you'll get an exception on reader["State"].
Since reader.Read() returns a boolean to indicate whether there's a current row, you should skip calling reader.HasRows entirely:
while(reader.Read()) {
cboState.Items.Add(reader["State"].ToString().Trim());
}
Um I think is wrong your loop it should be.
if (reader.HasRows)
{
while(reader.Read())
{
cboState.Items.Add(reader["State"].ToString().Trim());
}
}
Note that the bucle is with while(reader.Read())
This is your problem:
while(reader.HasRows) {
reader.Read();
cboState.Items.Add(reader["State"].ToString().Trim());
}
HasRows indicates whether or not the reader retrieved anything; it doesn't change as you read through it (in other words, it's not analogous to an end-of-file indicator like you're using it). Instead, you should do this:
while(reader.Read()) {
cboState.Items.Add(reader["State"].ToString().Trim());
}
Reader should be closed.
using(var reader = cmd.ExecuteReader())
{
if(reader.HasRows)
{
while(reader.Read())
{
cboState.Items.Add(reader["State"].ToString().Trim());
}
}
}