Why does the DataTableReader lose its data when using MySqlDataAdapter? - c#

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

Related

Linq query to execute stored procedure with return type as DataTable

I am trying to get result as DataTable from a stored procedure using a Linq query. Here is the code that I am trying to use:
_dbContext.Database.Sqlquery<DataTable>("dbo.uspGetOrdersDetails #orderID", orderParam);
but the result is empty
If I keep as viewmodel, then I am able to get the data
Just want to know that, is it possible with datatable.
EntityFramework or LinqToSql doesn't support this out of the box. Instead, you can use the connection of your context and fill your DataTable using Ado.Net. You can find a sample here. You can convert the solution provided in that answer to an extension method. A simple implementation:
public static class QueryExtensions
{
public static DataTable ExecuteQuery(this DbContext db, string commandText, CommandType commandType, IEnumerable<SqlParameter> parameters)
{
var conn = db.Database.Connection;
try
{
if (conn.State != ConnectionState.Open)
conn.Open();
using (var command = conn.CreateCommand())
{
command.CommandText = commandText;
command.CommandType = commandType;
command.Parameters.AddRange(parameters.ToArray());
using (var reader = command.ExecuteReader())
{
var dt = new DataTable();
dt.Load(reader);
return dt;
}
}
}
finally
{
if (conn.State != ConnectionState.Closed)
conn.Close();
}
}
}

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};
}
}
}

Dynamically choose which class to use within function

Is there a way to choose which class to use in a function based on how it is called? My question is best explained by code samples:
I have 2 separate files for dealing with each type of database (SQL, Access)
Access:
public static DataTable Select(string connString, string query, Dictionary<string, object> Parameters = null)
{
DataTable dt = new DataTable();
//Create Query
using (OleDbConnection conn = new OleDbConnection(connString))
using (OleDbCommand cmd = new OleDbCommand(query, conn))
using (OleDbDataAdapter da = new OleDbDataAdapter(cmd))
{
//Add Parameters
if (Parameters != null)
{
foreach (KeyValuePair<string, object> kvp in Parameters)
{
cmd.Parameters.AddWithValue(kvp.Key, kvp.Value);
}
}
//Execute Query
conn.Open();
da.Fill(dt);
return dt;
}
}
SQL:
public static DataTable Select(string connString, string query, Dictionary<string, object> Parameters = null)
{
DataTable dt = new DataTable();
//Create Query
using (SqlConnection conn = new SqlConnection(connString))
using (SqlCommand cmd = new SqlCommand(query, conn))
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
//Add Parameters
if (Parameters != null)
{
foreach (KeyValuePair<string, object> kvp in Parameters)
{
cmd.Parameters.AddWithValue(kvp.Key, kvp.Value);
}
}
//Execute Query
conn.Open();
da.Fill(dt);
return dt;
}
}
Notice the only difference is the type of the Connection, Command and Adapter.
Is there a way to merge these two functions so I can pass a parameter to specify which type to use?
What you ASKED for is called Double Dispatch. You don't need double dispatch.
public static DataTable Select(DbProviderFactory factory, string connString, string query, Dictionary<string, object> Parameters = null)
{
DataTable dt = new DataTable();
//Create Query
using (DbConnection conn = factory.CreateConnection())
{
conn.ConnectionString = connectionString;
using(DbCommand cmd = conn.CreateCommand())
using(DbDataAdapter da = factory.CreateDataAdapter())
{
cmd.CommandText = query;
da.SelectCommand = cmd;
if (Parameters != null)
{
foreach (KeyValuePair<string, object> kvp in Parameters)
{
DbParameter parameter = cmd.createParameter();
parameter.ParameterName = kvp.Key;
parameter.Value = kvp.Value;
cmd.Parameters.Add(parameter);
}
}
conn.Open();
da.Fill(dt);
return dt;
}
}
}
To answer your question, you could create an enumeration of database types, pass it into Select, and then inside Select you could switch on the value, where each case contains the corresponding code.
(However, this looks like bad design to me, since this looks like a good opportunity to use polymorphism, at least some of which you should already get through ADO.NET. You could fill the gaps yourself.)
You may want to look into the DbProviderFactory approach
http://msdn.microsoft.com/en-us/library/wda6c36e(v=vs.110).aspx

Return datareader from method

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;
}
}

Return Result from Select Query in stored procedure to a List

I'm writing a stored procedure that currently contains only a SELECT query. It will be expanded to do a number of other things, which is why it has to be a stored procedure, but for now, it is a simple query.
Something like this:
SELECT name, occupation, position
FROM jobs
WHERE ...
I'm looking to return the results of this query to be used in C#. I want to add it to a list so that I can bind it to a GridView component.
I don't know how to go about this, though. If I have to insert it into a list after returning all selected data, then that's alright, I just need to know how to properly return the data so that I can do that.
If I can return it in a format that can be popped right into a list, though, that would be ideal.
In stored procedure, you just need to write the select query like the below:
CREATE PROCEDURE TestProcedure
AS
BEGIN
SELECT ID, Name
FROM Test
END
On C# side, you can access using Reader, datatable, adapter.
Using adapter has just explained by Susanna Floora.
Using Reader:
SqlConnection connection = new SqlConnection(ConnectionString);
command = new SqlCommand("TestProcedure", connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
SqlDataReader reader = command.ExecuteReader();
List<Test> TestList = new List<Test>();
Test test = null;
while (reader.Read())
{
test = new Test();
test.ID = int.Parse(reader["ID"].ToString());
test.Name = reader["Name"].ToString();
TestList.Add(test);
}
gvGrid.DataSource = TestList;
gvGrid.DataBind();
Using dataTable:
SqlConnection connection = new SqlConnection(ConnectionString);
command = new SqlCommand("TestProcedure", connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
DataTable dt = new DataTable();
dt.Load(command.ExecuteReader());
gvGrid.DataSource = dt;
gvGrid.DataBind();
I hope it will help you. :)
SqlConnection connection = new SqlConnection(ConnectionString);
command = new SqlCommand("TestProcedure", connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
DataTable dt = new DataTable();
dt.Load(command.ExecuteReader());
gvGrid.DataSource = dt;
gvGrid.DataBind();
SqlConnection con = new SqlConnection("Data Source=DShp;Initial Catalog=abc;Integrated Security=True");
SqlDataAdapter da = new SqlDataAdapter("data", con);
da.SelectCommand.CommandType= CommandType.StoredProcedure;
DataSet ds=new DataSet();
da.Fill(ds, "data");
GridView1.DataSource = ds.Tables["data"];
GridView1.DataBind();
Passing Parameters in Stored Procedure and calling it in C# Code behind as shown below?
SqlConnection conn = new SqlConnection(func.internalConnection);
var cmd = new SqlCommand("usp_CustomerPortalOrderDetails", conn);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("#CustomerId", SqlDbType.Int).Value = customerId;
cmd.Parameters.Add("#Qid", SqlDbType.VarChar).Value = qid;
conn.Open();
// Populate Production Panels
DataTable listCustomerJobDetails = new DataTable();
listCustomerJobDetails.Load(cmd.ExecuteReader());
conn.Close();
I had the same question, took me ages to find a simple solution.
Using ASP.NET MVC 5 and EF 6:
When you add a stored procedure to your .edmx model, the result of the stored procedure will be delivered via an auto-generated object called yourStoredProcName_result.
This _result object contains the attributes corresponding to the columns in the database that your stored procedure selected.
The _result class can be simply converted to a list:
yourStoredProcName_result.ToList()
// GET: api/GetStudent
public Response Get() {
return StoredProcedure.GetStudent();
}
public static Response GetStudent() {
using (var db = new dal()) {
var student = db.Database.SqlQuery<GetStudentVm>("GetStudent").ToList();
return new Response {
Sucess = true,
Message = student.Count() + " Student found",
Data = student
};
}
}
Building on some of the responds here, i'd like to add an alternative way. Creating a generic method using reflection, that can map any Stored Procedure response to a List. That is, a List of any type you wish, as long as the given type contains similarly named members to the Stored Procedure columns in the response.
Ideally, i'd probably use Dapper for this - but here goes:
private static SqlConnection getConnectionString() // Should be gotten from config in secure storage.
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "it.hurts.when.IP";
builder.UserID = "someDBUser";
builder.Password = "someDBPassword";
builder.InitialCatalog = "someDB";
return new SqlConnection(builder.ConnectionString);
}
public static List<T> ExecuteSP<T>(string SPName, List<SqlParameter> Params)
{
try
{
DataTable dataTable = new DataTable();
using (SqlConnection Connection = getConnectionString())
{
// Open connection
Connection.Open();
// Create command from params / SP
SqlCommand cmd = new SqlCommand(SPName, Connection);
// Add parameters
cmd.Parameters.AddRange(Params.ToArray());
cmd.CommandType = CommandType.StoredProcedure;
// Make datatable for conversion
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(dataTable);
da.Dispose();
// Close connection
Connection.Close();
}
// Convert to list of T
var retVal = ConvertToList<T>(dataTable);
return retVal;
}
catch (SqlException e)
{
Console.WriteLine("ConvertToList Exception: " + e.ToString());
return new List<T>();
}
}
/// <summary>
/// Converts datatable to List<someType> if possible.
/// </summary>
public static List<T> ConvertToList<T>(DataTable dt)
{
try // Necesarry unfotunately.
{
var columnNames = dt.Columns.Cast<DataColumn>()
.Select(c => c.ColumnName)
.ToList();
var properties = typeof(T).GetProperties();
return dt.AsEnumerable().Select(row =>
{
var objT = Activator.CreateInstance<T>();
foreach (var pro in properties)
{
if (columnNames.Contains(pro.Name))
{
if (row[pro.Name].GetType() == typeof(System.DBNull)) pro.SetValue(objT, null, null);
else pro.SetValue(objT, row[pro.Name], null);
}
}
return objT;
}).ToList();
}
catch (Exception e)
{
Console.WriteLine("Failed to write data to list. Often this occurs due to type errors (DBNull, nullables), changes in SP's used or wrongly formatted SP output.");
Console.WriteLine("ConvertToList Exception: " + e.ToString());
return new List<T>();
}
}
Gist: https://gist.github.com/Big-al/4c1ff3ed87b88570f8f6b62ee2216f9f
May be this will help:
Getting rows from DB:
public static DataRowCollection getAllUsers(string tableName)
{
DataSet set = new DataSet();
SqlCommand comm = new SqlCommand();
comm.Connection = DAL.DAL.conn;
comm.CommandType = CommandType.StoredProcedure;
comm.CommandText = "getAllUsers";
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = comm;
da.Fill(set,tableName);
DataRowCollection usersCollection = set.Tables[tableName].Rows;
return usersCollection;
}
Populating DataGridView from DataRowCollection :
public static void ShowAllUsers(DataGridView grdView,string table, params string[] fields)
{
DataRowCollection userSet = getAllUsers(table);
foreach (DataRow user in userSet)
{
grdView.Rows.Add(user[fields[0]],
user[fields[1]],
user[fields[2]],
user[fields[3]]);
}
}
Implementation :
BLL.BLL.ShowAllUsers(grdUsers,"eusers","eid","euname","eupassword","eposition");

Categories

Resources