Return datareader from method - c#

I have the following method
public static SqlDataReader MenuDataReader(string url)
{
using (SqlConnection con = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("spR_GetChildMenus", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#PageUrl", url);
cmd.Parameters.AddWithValue("#MenuId", ParameterDirection.Output);
cmd.Parameters.AddWithValue("#ParentId", ParameterDirection.Output);
cmd.Parameters.AddWithValue("#TitleText", ParameterDirection.Output);
cmd.Parameters.AddWithValue("#ExternalUrl", ParameterDirection.Output);
cmd.Parameters.AddWithValue("#FullUrl", ParameterDirection.Output);
cmd.Parameters.AddWithValue("#ChildCount", ParameterDirection.Output);
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.HasRows)
{
//return reader;
while (reader.Read())
{
return reader;
}
}
}
}
return null;
}
which im calling like this
SqlDataReader reader = MenuDataReader(url);
if (reader.HasRows)
{
while (reader.Read())
{ }}
however im getting the error message
Invalid attempt to call HasRows when reader is closed.
can anyone help me out
thanks

As seen in https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand(v=vs.110).aspx :
public static SqlDataReader ExecuteReader(String connectionString, String commandText,
CommandType commandType, params SqlParameter[] parameters) {
SqlConnection conn = new SqlConnection(connectionString);
using (SqlCommand cmd = new SqlCommand(commandText, conn)) {
cmd.CommandType = commandType;
cmd.Parameters.AddRange(parameters);
conn.Open();
// When using CommandBehavior.CloseConnection, the connection will be closed when the
// IDataReader is closed.
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return reader;
}
}

Do you really need the reader, or do you just need some way to iterate over the rows inside it? I suggest an iterator block. You can iterate over your rows inside the source method, and yield each row in turn to the caller.
There is a twist with this technique: because you're yielding the same object with each iteration, there are cases where this can cause a problem, and so you're best off also asking for a delegate to copy the contents of the row somewhere. I also like to abstract this to a generic method that can be used for any query, and use the same delegate technique to handle parameter data, like so:
private IEnumerable<T> GetRows<T>(string sql, Action<SqlParameterCollection> addParameters, Func<IDataRecord, T> copyRow)
{
using (var cn = new SqlConnection("Connection string here"))
using (var cmd = new SqlCommand(sql, cn)
{
cmd.CommandType = CommandType.StoredProcedure;
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return copyRow(rdr);
}
rdr.Close();
}
}
}
public IEnumerable<MenuItem> GetChildMenus(string url)
{
return GetRows<MenuItem>("spR_GetChildMenus", p =>
{
//these lines are copied from your question, but they're almost certainly wrong
p.AddWithValue("#PageUrl", url);
p.AddWithValue("#MenuId", ParameterDirection.Output);
p.AddWithValue("#ParentId", ParameterDirection.Output);
p.AddWithValue("#TitleText", ParameterDirection.Output);
p.AddWithValue("#ExternalUrl", ParameterDirection.Output);
p.AddWithValue("#FullUrl", ParameterDirection.Output);
p.AddWithValue("#ChildCount", ParameterDirection.Output);
}, r =>
{
return new MenuItem( ... );
}
}

I would not return the reader - the Dispose of your connection and command are closing the connection. I would instead return a representative model of your data.

When you return inside the using statement the code calls Dispose on the SqlConnection. This closes the DataReader, causing the error.

Triggered by a question under Danan's answer, here is a solution based on abstractions. Additionally, it uses good practices like using declarations, async programming (with cancellation tokens omitted for brevity), and proper object disposal (especially with regards to the connection).
// Example invocation
public async Task DemonstrateUsage()
{
var query = #"SELECT * FROM Order WHERE Id = #Id;";
var parameters = new Dictionary<string, object>()
{
["#Id"] = 1,
};
// Caller disposes the reader, which disposes the connection too
await using var reader = await this.ExecuteReader(this.CreateConnection, query, parameters);
while (await reader.ReadAsync())
Console.Log("And another row!");
}
// Concrete implementation of how we produce connections
private DbConnection CreateConnection()
{
return new SqlConnection("ConnectionString");
}
// Fully abstract solution
private async Task<DbDataReader> ExecuteReader(Func<DbConnection> connectionFactory,
string query, IReadOnlyDictionary<string, object> parameters,
CommandType commandType = CommandType.Text)
{
var connection = connectionFactory();
try
{
await using var command = connection.CreateCommand();
command.CommandType = commandType;
command.CommandText = query;
foreach (var pair in parameters)
{
var parameter = command.CreateParameter();
parameter.ParameterName = pair.Key;
parameter.Value = pair.Value;
command.Parameters.Add(parameter);
}
await connection.OpenAsync();
return await command.ExecuteReaderAsync(CommandBehavior.CloseConnection);
}
catch
{
// We have failed to return a disposable reader that can close the connection
// We must clean up by ourselves
await connection.DisposeAsync();
throw;
}
}

Related

Task when all, connection is closing

I'm trying to execute multiple SqlDataReaders using Task.WhenAll. But when the tasks are awaited I get
"System.InvalidOperationException: Invalid operation. The connection
is closed".
Creation of tasks:
List<Task<SqlDataReader>> _listTasksDataReader = new List<Task<SqlDataReader>>();
_listTasksDataReader.Add(GetSqlDataReader1(10));
_listTasksDataReader.Add(GetSqlDataReader2(10));
SqlDataReader[] _dataReaders = await Task.WhenAll(_listTasksDataReader);
My "SqlDataReader" methods:
public Task<SqlDataReader> GetSqlDataReader1(int recordCount)
{
using (var sqlCon = new SqlConnection(ConnectionString))
{
sqlCon.Open();
using (var command = new SqlCommand("sp_GetData", sqlCon))
{
command.Parameters.Clear();
command.Parameters.Add(new SqlParameter("#recordCount", recordCount));
command.CommandType = System.Data.CommandType.StoredProcedure;
return command.ExecuteReaderAsync();
}
}
}
Shouldn't the database connections be opened when the Task.WhenAll is executed or am I missing something?
It is possible to pass a CommandBehavior.CloseConnection to the ExecuteReaderAsync. Then the connection will remain open until the returned datareader object is closed: see MSDN here and here. In that case, the SqlConnection does not need to be in a using statement.
Like this:
public Task<SqlDataReader> GetSqlDataReader1(int recordCount)
{
var sqlCon = new SqlConnection(ConnectionString);
sqlCon.Open();
using (var command = new SqlCommand("sp_GetData", sqlCon))
{
command.Parameters.Clear();
command.Parameters.Add(new SqlParameter("#recordCount", recordCount));
command.CommandType = System.Data.CommandType.StoredProcedure;
return command.ExecuteReaderAsync(CommandBehavior.CloseConnection);
}
}
am I missing something?
You're trying to get a SqlDataReader that doesn't have an underlying connection? I don't think that will work well. What happens as you read from the reader? The connection is already closed.
So, you probably just need to read the actual data before closing the connection:
public async Task<List<T>> GetData1(int recordCount)
{
using (var sqlCon = new SqlConnection(ConnectionString))
{
sqlCon.Open();
using (var command = new SqlCommand("sp_GetData", sqlCon))
{
command.Parameters.Clear();
command.Parameters.Add(new SqlParameter("#recordCount", recordCount));
command.CommandType = System.Data.CommandType.StoredProcedure;
var result = new List<T>();
var reader = await command.ExecuteReaderAsync();
// TODO: use `reader` to populate `result`
return result;
}
}
}
UPDATE: I'm going to leave this here, but I've just remembered that you're not allowed to combine yield and await... at least, not yet.
Remember that calling command.ExecuteReaderAsync(), even with the return keyword, doesn't stop execution of the method. That's the whole point of _Async() methods. So immediately after that function call, the code exits the using block. This has the effect of disposing your connection object before you ever have a chance to use it to read from your DataReader.
Try returning an Task<IEnumerable<IDataRecord>> instead:
public async Task<IEnumerable<IDataRecord>> GetSqlDataReader1(int recordCount)
{
using (var sqlCon = new SqlConnection(ConnectionString))
using (var command = new SqlCommand("sp_GetData", sqlCon))
{
command.Parameters.Add("#recordCount", SqlDbType.Int).Value = recordCount;
command.CommandType = System.Data.CommandType.StoredProcedure;
sqlCon.Open();
var rdr = await command.ExecuteReaderAsync();
while (rdr.Read())
{
yield return rdr;
}
}
}
Note that there is a "gotcha" with this pattern. Each yield return uses the same object, and therefore some weird things can happen if you aren't careful. I recommend further changing this include code that puts the data from each record in the rdr object into it's own (strongly-typed) object instance:
public async Task<IEnumerable<SomeObject>> GetSqlDataReader1(int recordCount)
{
using (var sqlCon = new SqlConnection(ConnectionString))
using (var command = new SqlCommand("sp_GetData", sqlCon))
{
command.Parameters.Add(new SqlParameter("#recordCount", recordCount));
command.CommandType = System.Data.CommandType.StoredProcedure;
sqlCon.Open();
var rdr = await command.ExecuteReaderAsync();
while (rdr.Read())
{
yield return new SomeObject() {Field1 = rdr[1], Field2 = rdr[2], etc};
}
}
}

Why does the DataTableReader lose its data when using MySqlDataAdapter?

I have the following method:
public DataTableReader Get<T>(string sql, T[] parameters, CommandType commandType = CommandType.Text)
{
DataTableReader result;
using (var connection = new MySqlConnection(_connectionString))
{
using (var command = new MySqlCommand())
{
command.CommandType = commandType;
command.CommandText = sql;
command.Connection = connection;
if (parameters != null)
{
command.Parameters.AddRange(parameters);
}
using (var adapter = new MySqlDataAdapter())
{
adapter.SelectCommand = command;
using (var dataTable = new DataTable())
{
adapter.Fill(dataTable);
result = dataTable.CreateDataReader();
} // result contains data here
} // result "loses" data here
}
}
return result;
}
However, when I get to the row that I've commented about losing the data, the DataTableReader is empty. If I put a breakpoint in the row above it (where I've commented that the result contains the data) result does indeed contain the results.
What's going on? It's almost like something is being passed as a reference.
Edit: I should probably note that I've got the same code to connect to a SQL Server where any instance of a MySQL specific class above is replaced by SqlCommand,SqlDataAdapter instead. This all seems to work.
As requested, here is the corresponding method for MSSql:
public virtual DataTableReader Get(string sql, SqlParameter[] parameters, CommandType commandType = CommandType.Text)
{
DataTableReader result;
using (var sqlConnection = new SqlConnection(_coreConnectionString))
{
using (var myCommand = new SqlCommand())
{
myCommand.CommandType = commandType;
myCommand.CommandText = sql;
myCommand.Connection = sqlConnection;
if (parameters != null)
{
myCommand.Parameters.AddRange(parameters);
}
using (var myAdapter = new SqlDataAdapter())
{
myAdapter.SelectCommand = myCommand;
using (var myDataTable = new DataTable())
{
myAdapter.Fill(myDataTable);
result = myDataTable.CreateDataReader();
}
}
}
}
return result;
}
I've updated the top method to return the DataReader a bit sooner:
public DataTableReader Get<T>(string sql, T[] parameters, CommandType commandType = CommandType.Text)
{
using (var connection = new MySqlConnection(_connectionString))
{
using (var command = new MySqlCommand())
{
command.CommandType = commandType;
command.CommandText = sql;
command.Connection = connection;
if (parameters != null)
{
command.Parameters.AddRange(parameters);
}
using (var adapter = new MySqlDataAdapter())
{
adapter.SelectCommand = command;
using (var dataTable = new DataTable())
{
adapter.Fill(dataTable);
var reader = dataTable.CreateDataReader();
return reader;
}
}
}
}
}
I've left the assignment of the variable on a different line to the return statement so I can verify that reader is being filled. However, when it gets returned to the calling method, there's nothing there.
The code that is calling Get() is var dt = _dbHelper.Get(sql, parameters) and I know that the sql works because before it is returned, there is the data I am expecting in the object.
More Edits:I've been doing some more digging as this is really annoying me. Firstly, if I use var reader = dataTable.Copy().CreateDataReader(); the data is returned. But of course then this creates a new version of the object, so this might cause a memory issue.
Secondly (and this really annoys me) the Fill() method that MySqlDataAdapter uses isn't its own implementation. It uses the implementation from DbDataAdapter (which both it and SqlDataAdapter inherits from). So how come it doesn't work the same?
In this line:
using (var myDataTable = new DataTable())
{
myAdapter.Fill(myDataTable);
result = myDataTable.CreateDataReader();
}
with the using you are doing that when the code goes out from the using, the DataTable is disposed and then yo uwill get empty data.
Try declaring the DataTable and closing it at the end. Maybe using a try/catch/finally block is a good way to do this, and in the finally you can add the dispose.
I hope this helps

Issue with calling two methods in data access method

I have this method:
public bool ActivateUser(string username, string key)
{
var user = this.GetUser(username, true);
if (user != null)
{
if (user.NewEmailKey == key)
{
string query = "usp_ActivateUser";
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand(query, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#p_Username", username);
cmd.Parameters.AddWithValue("#p_LastModifiedDate", DateTime.Now);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
cmd.ExecuteNonQuery();
return true;
}
}
}
}
else
return false;
}
else
return false;
}
As you can see I call GetUser() method first to get user and later use data for another database call. But something goes wrong.
There is already an open DataReader associated with this Command which must be closed first.
Here is the get user method:
public User GetUser(string username, bool nonMembershipUser)
{
string query = "usp_GetUser";
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand(query, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#p_Username", username);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{...
Your problem is here.
using (SqlDataReader reader = cmd.ExecuteReader())
{
cmd.ExecuteNonQuery();
return true;
}
You are calling cmd.ExecuteNonQuery() but the command is already being used by the reader inside this using block.
Since your code doesn't really do anything meaningful with the reader why not remove the block entirely and call cmd.ExecuteNonQuery() ?
Why do you do cmd.ExecuteReader() in the using statement, then cmd.ExecuteNonQuery(); on the very next line?
Why use the ExecuteReader() at all as you are simply returning from the database call without checking the result - ExecuteNonQuery will suffice for this.
this is the problem, in ActivateUser:
using (SqlDataReader reader = cmd.ExecuteReader())
{
cmd.ExecuteNonQuery();
return true;
}
You can't open a Reader on an SqlCommand object and then execute another query on that command objct without first closing the Reader - which won't happen until that last "}".
Actually I'm not sure you even need the Reader in this case - did you maybe copy/paste from your GetUser function? All you should need is
cmd.ExecuteNonQuery();
return true;
Also, I would consider wrapping code to execute readers,queries, etc, into some functions so you can re-use them. Here's what I normally use as a wrapper for readers:
public static DataTable ExecuteReader (string query,CommandType commType, params SqlParameter[] Paramerters)
{
try
{
using (SqlConnection conn = new SqlConnection("your connection string here")
{
conn.Open();
using (SqlCommand comm = new SqlCommand(conn,query))
{
conn.CommandType=commType;
if (Parameters!=null) comm.Parameters.AddRange(Parameters);
DataTable dt = new DataTable();
using (SqlDataReader reader = comm.ExecuteReader())
{
dt.Load(reader);
}
return dt;
}//end using command
}//end using connection
}
catch(Exception)
{
throw;
}
}//end function
and you can write simple wrappers for nonquery, nonreader, etc, as well.

Call stored procedure in C#

I want to call the stored procedure using C#.
I would like to create a shorthand for the procedure call.
I still do not want to re-define the connection and open it.
How do I create a method - I still did not open connection to the database?
I use the following code:
SqlConnection conn = null;
SqlDataReader rdr = null;
conn = new SqlConnection("");
conn.Open();
SqlCommand cmd = new SqlCommand("Procedure", conn);
cmd.CommandType = CommandType.StoredProcedure;
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
}
I have no idea if I understand what you are asking or not, but do you mean something like:
public static SqlReader executeProcedure(SqlConnection oConn, string commandName, Dictionary<string, object> params)
{
SqlCommand comm = oConn.CreateCommand();
comm.CommandType = CommandType.StoredProcedure;
comm.CommandText = commandName;
if (params != null)
foreach(KeyValuePair<string, object> kvp in params)
comm.Parameters.Add(new SqlParameter(kvp.Key, kvp.Value));
return comm.ExecuteReader();
}
An example of use might be
Dictionary<string, object> paras = new Dictionary<string, object>();
paras.Add("user_name", "Timmy");
paras.Add("date", DateTime.Now);
SqlReader results = executeProcedure(oConn, "sp_add_user", paras);
while (results.Read())
{
//do something with the rows returned
}
results.Close();
FlyingStreudel's answer is good, but I've adapted that code to make this version that demonstrates best practices (links at the bottom.) You can also use Microsoft's Enterprise Library which will give you robust Data Access classes.
private string _connectionString = "yourconnectionstring"; // from web.config, or wherever you store it
public static SqlDataReader executeProcedure(string commandName,
Dictionary<string, object> params)
{
SqlConnection conn = new SqlConnection(_connectionString);
conn.Open();
SqlCommand comm = conn.CreateCommand();
comm.CommandType = CommandType.StoredProcedure;
comm.CommandText = commandName;
if (params != null)
{
foreach(KeyValuePair<string, object> kvp in params)
comm.Parameters.Add(new SqlParameter(kvp.Key, kvp.Value));
}
return comm.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
}
used, like so:
Dictionary<string, object> paras = new Dictionary<string, object>();
paras.Add("user_name", "Timmy");
paras.Add("date", DateTime.Now);
using(SqlDataReader results = executeProcedure("sp_add_user", paras))
{
while (results.Read())
{
//do something with the rows returned
}
}
References:
How Microsoft use Connections in Enterprise Library
Keeping an SqlConnection open is 'foo bar'
Returning a data reader from a class
using (SqlConnection sqlConnection1 = new SqlConnection("Your Connection String")) {
using (SqlCommand cmd = new SqlCommand()) {
Int32 rowsAffected;
cmd.CommandText = "StoredProcedureName";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = sqlConnection1;
sqlConnection1.Open();
rowsAffected = cmd.ExecuteNonQuery();
}}
If you're looking to reuse this kind of code, one possible solution is to wrap this kind of a method (one that executes a stored procedure and returns results) into a generic data access layer of your application. The variations you'd have to consider are for procedures not returning results, or expecting parameters.
You could, for example, wrap this shell code as an ExecuteProcedure() that expects a connection string back to the database.
There are myriad other ways to accomplish this kind of task, so you need to determine what would be the best option suited to your particular requirements.
You can wrap this code and take the procedure as a parameter. Something like this:
public SqlCommand GetData(string procedure)
{
var conn = new SqlConnection(connectionString);
var cmd = new SqlCommand(procedure, conn);
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
return cmd;
}
The only problem with this method is that you are not properly disposing resources and are relying on the caller to do so.

How do I return the result of a SELECT COUNT statement as a string in C#?

I'm wondering how to return the result from a SELECT COUNT statement in C#.
I have a sql statement that returns the count of 15.
Currently, I'm returning the datareader. Can I somehow return the result of that as a string?
static public SqlDataReader FillDataReader(string sql, SqlParameter[] parms)
{
SqlConnection conn = new SqlConnection(ConnectionString);
SqlCommand cmd = new SqlCommand(sql, conn);
SqlDataReader dr = null;
conn.Open();
cmd.CommandTimeout = 120; //120 seconds for the query to finish executing
foreach (SqlParameter p in parms)
{
cmd.Parameters.Add(p);
}
try
{
dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (SqlException ex)
{
if (dr != null)
{
dr.Close();
}
conn.Close();
//DBUtilExceptionHandler(ex, sql);
throw ex;
}
finally
{
}
return dr; //This could be null...be sure to test for that when you use it
}
Or I could use a different method. I just don't know what it should be.
Any help is appreciated.
This is my select statement:
select count(LeadListID) from LeadLists WHERE SalesPersonID = 1
AND LeadListDateCreated BETWEEN '9/1/11' AND '10/1/11 23:59:59'
Sure - just use:
int count = (int) query.ExecuteScalar();
// TODO: Decide the right culture to use etc
return count.ToString();
Notes:
Use using statements instead of manual try/catch/finally blocks
You should close the connection whether or not there was an error
Given that the natural result of the query is an integer, I would change it to return an int, not a string. Let the caller make that conversion if they want to
If there's an error, you should almost certainly let the exception bubble up, rather than returning null
I would write the code as:
public static int ExecuteScalarInt32(string sql, SqlParameter[] parms)
{
using (SqlConnection conn = new SqlConnection(ConnectionString))
using (SqlCommand command = new SqlCommand(sql, conn) { Parameters = parms })
{
conn.Open();
command.CommandTimeout = 120;
return (int) command.ExecuteScalar();
}
}
If you really needed a version to work on an arbitrary data reader, you could write it as:
public static T ExecuteQuery<T>(string sql, SqlParameter[] parms,
Func<SqlDataReader, T> projection)
{
using (SqlConnection conn = new SqlConnection(ConnectionString))
using (SqlCommand command = new SqlCommand(sql, conn) { Parameters = parms })
{
conn.Open();
command.CommandTimeout = 120;
return projection(command.ExecuteReader());
}
}
And then call it with:
int count = ExecuteQuery<int>(sql, parms, reader => {
if (!reader.MoveNext()) {
throw new SomeGoodExceptionType("No data");
}
return reader.GetInt32(0);
});
Sure, you can always call the ToString() method in .NET on the single field when reading it:
dr[0].ToString()
Are there many records that you want to concat as a string? Then you loop through each row, grab the value as a string, and create a master string in a for loop fashion.
Since you're expecting only a single value, a better alternative to ExecuteReader is the ExecuteScalar method:
try
{
var count = cmd.ExecuteScalar().ToString();
}
Either cast it as a varchar in your sql:
select cast(count(LeadListID) as varchar(10))
from LeadLists
WHERE SalesPersonID = 1
AND LeadListDateCreated BETWEEN '9/1/11' AND '10/1/11 23:59:59'
or just call .ToString() on the result, as shown in other answers.
Additionally, I'm not a fan of relying on CommandBehavior.CloseConnection for DataReaders. I much prefer code like this:
static public IEnumerable<IDataRecord> GetDataReader(string sql, SqlParameter[] parms)
{
using (var conn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, conn))
{
cmd.CommandTimeout = 120; //120 seconds for the query to finish executing
foreach (SqlParameter p in parms)
{
cmd.Parameters.Add(p);
}
conn.Open();
using (var dr= cmd.ExecuteReader())
{
while (dr.Read())
{
yield return dr;
}
}
}
}
Use ExecuteScalar instead, that will return the first field from the first record of the returned recordset, which is what you want. That will return to you an object that is really an integer. Add a ToString to that and you should be good.

Categories

Resources