Invalid Attempt To Call Read when reader is closed .NET - c#

I currently have a DB library used for database access and I use it across several projects. I currently use the following code to get a recordset.
METHODS
public static IDataReader GetRs(string sql)
{
using (var con = NewSqlConnection())
{
con.Open();
return GetRs(sql, con);
}
}
public static IDataReader GetRs(string sql, SqlConnection dbconn)
{
using (var cmd = new SqlCommand(sql, dbconn))
{
int tries = 1;
while (tries <= 3)
{
try
{
if (dbconn.State == ConnectionState.Closed)
{
dbconn.Open();
}
DataTable myTable = new DataTable();
var reader = cmd.ExecuteReader();
myTable.Load(reader);
return myTable.CreateDataReader();
//return cmd.ExecuteReader();
}
catch (SqlException ex)
{
if (ex.Message.Contains("Timeout expired") || ex.Number == 1205) // Deadlock
{
Thread.Sleep(1000);
if (tries == 3)
{
throw ex;
}
tries += 1;
cmd.CommandTimeout *= 10;
}
else
{
throw ex;
}
}
}
}
throw new Exception("Could not get RecordSet");
}
USAGE
public static void Test()
{
using(var reader = GetRs("SELECT Col FROM TABLE"))
{
while(reader.Read())
{
// do stuff with data here e.g. var value = reader[0];
}
}
}
While this method works, as you can see it loads the entire dataset into memory thus causing issues with scaling.
I tried replacing the following code in the GetRs(string sql, SqlConnection con) method
DataTable myTable = new DataTable();
var reader = cmd.ExecuteReader();
myTable.Load(reader);
return myTable.CreateDataReader();
and tried returning just the return cmd.ExecuteReader();
However an error is thrown on the while (reader.Read()) - Invalid attempt to call read when the reader is closed. I am guessing this is because the SqlConnection property is disposed (and hence closed) after returning the IDataReader.
I'm aware that I can wrap the GetRs method with a new sql connection but this means rewriting a lot of my code, and I was hoping that I would be able to dispose the reader AND the connection with my using(var reader = GetRs()) method.
Is there any way I can still use these methods without loading the whole dataset into memory?

You can inject in your code with a Action<DataTableReader> parameter to you GetRs call.
Try this:
public static void GetRs(string sql, Action<DataTableReader> consumer)
{
using (var con = NewSqlConnection())
{
con.Open();
GetRs(sql, con, consumer);
}
}
public static void GetRs(string sql, SqlConnection dbconn, Action<DataTableReader> consumer)
{
using (var cmd = new SqlCommand(sql, dbconn))
{
if (dbconn.State == ConnectionState.Closed)
{
dbconn.Open();
}
DataTable myTable = new DataTable();
var reader = cmd.ExecuteReader();
myTable.Load(reader);
consumer(myTable.CreateDataReader());
}
}
(I removed your try/catch code for clarity.)
Then you call it like this:
public static void Test()
{
GetRs("SELECT Col FROM TABLE", reader =>
{
while(reader.Read())
{
// do stuff with data here e.g. var value = reader[0];
}
});
}

Your connection object was closed by the following line
using (var con = NewSqlConnection())
{
con.Open();
return GetRs(sql, con);
}//Connection object gets released here
But still, you are returning the reader object which was created by SqlCommand object using the above connection object. Hence the reader object is closed.

Related

Returning Type SqlDataReader even when try catch fails - getting not all paths return a value

I'm trying to read from a DB with a try/catch and I want to obviously return data. I'm getting a message saying not all code paths return a value. I get that I need to return something, but how should I handle this scenario? Can I return an empty SqlDataReader object?
The stored procedures being run will return different types of objects depending on what's executed, otherwise I would return the specific type.
private SqlDataReader RunSql(string connectionString, string procName)
{
using(SqlConnection = conn = new SqlConnection(connectionString))
{
using(SqlCommand SqlCmd = new SqlCommant(procName, conn))
{
SqlCmd.CommandType = CommandType.StoredProcedure;
conn.Open();
try
{
using(SqlDataReader reader = SqlCmd.ExecuteReader())
{
if(reader.HasRows)
{
return reader;
}
}
}
catch(Exception e)
{
//?
}
finally
{
conn.Close();
}
}
}
//?
}
The accepted answer will work but, as written, it "swallows" the exception that you caught; this prevents the caller from even knowing that something went awry. A much better approach is to rethrow the exception so that the caller is still informed that something bad happened and then they can then deal with the issue however they want to.
Another issue is that the reader and connection objects will be disposed of before you ever consume them in downstream code; thanks to the using and finally statements. One solution to this issue is to make it the responsibility of the caller to handle the connection object.
private static SqlDataReader RunSql(SqlConnection connection, string procedureName) {
using (var command = connection.CreateCommand()) {
command.CommandText = procedureName;
command.CommandType = CommandType.StoredProcedure;
try {
if (connection.State != ConnectionState.Open) {
connection.Open();
}
var reader = command.ExecuteReader();
if (reader.HasRows) {
return reader;
}
else {
return null;
}
}
catch (Exception e) {
// log the exception or whatever you wanna do with it
throw; // rethrow the exception
}
}
}
Here's an alternative version that uses IAsyncEnumerable; notice that we're now allowed to dispose of the objects within the same method. Also, the logic that handled the empty reader case is no longer required as our enumerable will simply be empty if there was nothing to read.
// define a container for information relevant to each row
public sealed class SqlResultSetRow : IEnumerable<(string fieldName, Type fieldType, object fieldValue)>
{
private readonly (string fieldName, Type fieldType, object fieldValue)[] m_fields;
public int ResultSetIndex { get; }
public SqlResultSetRow((string, Type, object)[] fields, int resultSetIndex) {
m_fields = fields;
ResultSetIndex = resultSetIndex;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<(string fieldName, Type fieldType, object fieldValue)> GetEnumerator() {
var rows = m_fields;
foreach (var row in rows) {
yield return row;
}
}
public object GetFieldName(int fieldOffset) => m_fields[fieldOffset].fieldName;
public object GetFieldValue(int fieldOffset) => m_fields[fieldOffset].fieldValue;
}
// define a class to hold our generic method(s)
public static class SqlClientExtensions
{
// implement a refactored version of the original method
public static async IAsyncEnumerable<T> ProcessRows<T>(this SqlCommand command, Func<SqlResultSetRow, CancellationToken, ValueTask<T>> rowCallback, CommandBehavior commandBehavior = CommandBehavior.SequentialAccess, [EnumeratorCancellation] CancellationToken cancellationToken = default) {
using (var dataReader = await command.ExecuteReaderAsync(commandBehavior, cancellationToken)) {
var resultSetIndex = 0;
do {
var fieldCount = dataReader.FieldCount;
var fieldNames = new string[fieldCount];
var fieldTypes = new Type[fieldCount];
for (var i = 0; (i < fieldCount); ++i) {
fieldNames[i] = dataReader.GetName(i);
fieldTypes[i] = dataReader.GetFieldType(i);
}
while (await dataReader.ReadAsync(cancellationToken)) {
var fields = new (string, Type, object)[fieldCount];
for (var i = 0; (i < fieldCount); ++i) {
fields[i] = (fieldNames[i], fieldTypes[i], dataReader.GetValue(i));
}
yield return await rowCallback(new SqlResultSetRow(fields, resultSetIndex), cancellationToken);
}
} while (await dataReader.NextResultAsync(cancellationToken));
}
}
}
class Program
{
// a minimal implementation of a rowCallBack function
public static async ValueTask<ExpandoObject> OnProcessRow(SqlResultSetRow resultSetRow, CancellationToken cancellationToken) {
var rowValue = (new ExpandoObject() as IDictionary<string, object>);
foreach (var field in resultSetRow) {
rowValue[field.fieldName] = field.fieldValue;
}
return (rowValue as ExpandoObject);
}
// put everything together
static async Task Main(string[] args) {
try {
var programTimeout = TimeSpan.FromMinutes(3);
var cancellationTokenSource = new CancellationTokenSource(programTimeout);
using (var connection = new SqlConnection(#"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=master;Integrated Security=True;"))
using (var command = connection.CreateCommand()) {
command.CommandText = "select 1 as [a];";
command.CommandType = CommandType.Text;
await connection.OpenAsync(cancellationToken: cancellationTokenSource.Token);
await foreach (dynamic row in command.ProcessRows(rowCallback: OnProcessRow, cancellationToken: cancellationTokenSource.Token)) {
Console.WriteLine($"a: {row.a}");
}
}
}
catch (Exception e) {
// do something with exception here
throw;
}
}
}
As the method doesn't return anything if the condition failed this is the reason you are getting that compile error. You could try bellow snippet. Just return null on your commented line //?
private SqlDataReader RunSql(string connectionString, string procName)
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand SqlCmd = new SqlCommand(procName, conn))
{
SqlCmd.CommandType = CommandType.StoredProcedure;
conn.Open();
try
{
using (SqlDataReader reader = SqlCmd.ExecuteReader())
{
if (reader.HasRows)
{
return reader;
}
}
}
catch (Exception e)
{
}
finally
{
conn.Close();
}
}
}
return null;
}
Hope that might resolve your compile error.
{
SqlCmd.CommandType = CommandType.StoredProcedure;
conn.Open();
try
{
using(SqlDataReader reader = SqlCmd.ExecuteReader())
{
if(reader.HasRows)
{
return reader;
}
}

Why can't I get data from my NPGSQL server?

I need to write a C# program and it has to be able to manage my data on my server. I have an NPGSQL server set up with a data table, I can write data into it, but I just can't get to read the data while running a program.What do I do wrong?
public NpgsqlDataReader reader;
public NpgsqlCommand InsertCommand = new NpgsqlCommand();
public String sConnectionString;
public Npgsql.NpgsqlConnection Conn;
public void DataBaseOpen()
{
sConnectionString = "Server=192.168.1.100;Port=5432;Username=postgres;Password=admin;Database=analoginput;Pooling=false;MinPoolSize=1;MaxPoolSize=999;Timeout=15;";
Conn = new Npgsql.NpgsqlConnection(sConnectionString);
InsertCommand = Conn.CreateCommand();
Conn.Open();
}
public void DataBaseClose()
{
Npgsql.NpgsqlConnection.ClearAllPools();
Conn.Close();
}
InsertCommand.CommandText = "Select * From public.sensorlog WHERE \"date\" > '2019.07.08.' And \"date\" < '2019.07.10.' order by Date asc;";
System.Windows.MessageBox.Show(InsertCommand.CommandText);
Npgsql.NpgsqlDataReader reader = InsertCommand.ExecuteReader();
System.Data.DataTable CSV = new System.Data.DataTable();
while (reader.Read())
{
CSV.Load(reader);
}
I want to load the data into the CSV datatable, but I just can't get it to work. The datatable is just empty.
What if you refactor your code to something like below.
The using statement will guarantee that your connection & command is closed/disposed when it goes out of scope and with the try/catch block you will catch any exceptions and report it to the UI via messagebox. This will assist in capturing exceptions if there are any.
public Npgsql.NpgsqlConnection DatabaseOpen()
{
var sConnectionString = "Server=192.168.1.100;Port=5432;Username=postgres;Password=xxx;Database=analoginput;Pooling=false;MinPoolSize=1;MaxPoolSize=999;Timeout=15;";
var Conn = new Npgsql.NpgsqlConnection(sConnectionString);
Conn.Open();
return Conn;
}
public void Main()
{
try
{
using (var conn = DatabaseOpen())
{
using (var InsertCommand = conn.CreateCommand())
{
InsertCommand.CommandText = "Select * From public.sensorlog WHERE \"date\" > '2019.07.08.' And \"date\" < '2019.07.10.' order by Date asc;";
System.Windows.MessageBox.Show(InsertCommand.CommandText);
Npgsql.NpgsqlDataReader reader = InsertCommand.ExecuteReader();
System.Data.DataTable CSV = new System.Data.DataTable();
while (reader.Read())
{
CSV.Load(reader);
}
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
finally
{
Npgsql.NpgsqlConnection.ClearAllPools();
}
}

C# Mysql Connection must be valid and open

First of all: I got my code running without using oop. I declared all my variables inside the same class and opened/closed the connection right before and after passing the query to the db. That worked! Now with some new experiences I tried to split my code into different classes. Now it wont work anymore.
It tells me "Connection must be valid and open". Enough text, here's my current code:
Services.cs
public static MySqlConnection conn // Returns the connection itself
{
get
{
MySqlConnection conn = new MySqlConnection(Services.ServerConnection);
return conn;
}
}
public static string ServerConnection // Returns the connectin-string
{
get
{
return String.Format("Server={0};Port=XXXX;Database=xxx;Uid=xxx;password=xxXxxXxXxxXxxXX;", key);
}
}
public static void DB_Select(string s, params List<string>[] lists)
{
try
{
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
string command = s;
cmd.CommandText = command;
MySqlDataReader sqlreader = cmd.ExecuteReader();
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
}
sqlreader.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
LoginForm.cs
private void checkUser(string username, string password)
{
using (Services.conn)
{
Services.conn.Open();
Services.DB_Select("..a short select statement..");
Services.conn.Close();
}
I guess this is all we need. I have shortened my code to get a focus on the problem.
I created Services.cs to get a global way to access the db from all forms without copy&pasting the connection info. Now when I reach my LoginForm.cs it throws an error "Connection must be valid and open". I've already debugged my code. It's all time closed. Even when passing conn.Open() it stays closed. Why?
Another try: I've also tried placing conn.Open() and conn.Close() inside Services.DB_Select(..) at the beginning and end. Same error here.
I have to say: The code worked before and I've used the same connection-string. So the string itself is surely valid.
I appreciate any help given here!
The problem is that you don't store the connection that was returned from your factory property. But don't use a property like a method. Instead use it in this way:
using (var con = Services.conn)
{
Services.conn.Open();
Services.DB_Select("..a short select statement..", con ));
//Services.conn.Close(); unnecessary with using
}
So use the same connection in the using that was returned from the property(or better created in the using) and pass it to the method which uses it. By the way, using a property as factory method is not best practise.
But in my opinion it's much better to create the connection where you use it, best place is in the using statement. And throw the con property to the garbage can, it is pointless and a source for nasty errors.
public static void DB_Select(string s, params List<string>[] lists)
{
try
{
using(var conn = new MySqlConnection(Services.ServerConnection))
{
conn.Open();
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandText = s;
using( var sqlreader = cmd.ExecuteReader())
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
} // unnecessary to close the connection
} // or the reader with the using-stetement
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
Try to restructure your Services class as follows
public static MySqlConnection conn // Returns the connection itself
{
get
{
MySqlConnection conn = new MySqlConnection(Services.ServerConnection);
return conn;
}
}
private static string ServerConnection // Returns the connectin-string - PRIVATE [Improved security]
{
get
{
return String.Format("Server={0};Port=XXXX;Database=xxx;Uid=xxx;password=xxXxxXxXxxXxxXX;", key);
}
}
// Rather than executing result here, return the result to LoginForm - Future improvement
public static void DB_Select(MySqlConnection conn ,string s, params List<string>[] lists)
{
try
{
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
string command = s;
cmd.CommandText = command;
MySqlDataReader sqlreader = cmd.ExecuteReader();
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
}
sqlreader.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
In LoginForm.cs use returning connection and store it there. When you need to execute query, use
MySqlConnection conn=Services.conn(); // Get a new connection
Services.DB_Select(conn,"..a short select statement.."); // Executing requirement
Services.conn.Close();
Additional - I suggest you need to return MySqlDataReader to LoginForm and handle results there
private MySqlConnection _conn;
public MySqlConnection conn // Returns the connection itself
{
get
{
if(_conn == null)
_conn = new MySqlConnection(Services.ServerConnection);
return _conn;
}
}

passing a string to a class

I have a problem with my SQL datareader, i want to make an external class and make the code in my xaml.cs as short as possible, because there are a lot of sqldatareaders needed in my program. for this I want to pass following two strings to the datareader class:
public void refreshcombobox()
{
cbGebruiker.Items.Clear();
database = new DataBase();
string sqlrdr = "(rdr.GetString(1).ToString().Trim())";
List<string> reader = database.ReaderRdr("Select * from Gebruikers", ref sqlrdr);
foreach (String str in reader)
{
cbGebruiker.Items.Add(str);
}
}
however, when I do this this is the result in my program instead of the actual results that are stored in the database:
http://i58.tinypic.com/301j2vo.jpg (I can't post images)
can somebody help me with this? I've searched everywhere...
I don't know how to pass the rdr.GetString(1).ToString().Trim() to make it actually look stuff up in the db. Instead of just copying the string directly into the list.
This is the class:
namespace ClassLib
{
public class DataBase
{
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["kassadatabase"].ConnectionString);
public object ScalarObject(string sql)
{
object value = null;
try
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
value = cmd.ExecuteScalar();
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
if (conn != null) conn.Close();
}
return value;
}
public List<string> ReaderRdr(string sql)
{
SqlDataReader rdr = null;
List<string> reader = new List<string>();
try
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
//reader.Add(rdr.GetString(1).ToString().Trim());
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
if (rdr != null) rdr.Close();
if (conn != null) conn.Close();
}
return reader;
}
//public List<string> ReaderRdr(string sql, ref string str)
//{
// SqlDataReader rdr = null;
// List<string> reader = new List<string>();
// try
// {
// conn.Open();
// SqlCommand cmd = new SqlCommand(sql, conn);
// rdr = cmd.ExecuteReader();
// while (rdr.Read())
// {
// //MessageBox.Show(str.ToString());
// //var strRdr = str;
// //MessageBox.Show(strRdr.ToString());
// //reader.Add(rdr.GetString(1).ToString().Trim());
// reader.Add(str);
// Console.WriteLine(String.Format("{0}", rdr[0]));
// }
// }
// catch (SqlException ex)
// {
// MessageBox.Show(ex.Message);
// }
// finally
// {
// if (rdr != null) rdr.Close();
// if (conn != null) conn.Close();
// }
// return reader;
//}
public void ExecuteNQuery(string insertString)
{
try
{
conn.Open();
SqlCommand cmd2 = new SqlCommand(insertString, conn);
cmd2.ExecuteNonQuery();
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
if (conn != null) conn.Close();
}
}
}
}
public List<string> ReaderRdr(string sql)
{
SqlDataReader rdr = null;
List<string> reader = new List<string>();
try
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
//reader.Add(rdr.GetString(1).ToString().Trim());
}
.....
now in the methode public list i want to replace the //reader.Add(rdr.GetString(1).ToString().Trim()); part(wich works fine)
with a string that is passed to the method.
public List<string> ReaderRdr(string sql, string strRdr)
{
SqlDataReader rdr = null;
List<string> reader = new List<string>();
try
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
//reader.Add(strRdr);
}
I'm not 100% sure what you're trying to do, but I can tell you right now that this is what you're doing wrong - the string (str) you're passing to ReaderRdr is just a string literal of C# code. There's super hacky (super inadvisable) things you can do to mimic what exists in other languages as eval(), but there's no built-in way to do that in C#. Nothing (sensible) you do to "(rdr.GetString(1).ToString().Trim())" is ever going to get a string, or cast it to string, or trim anything.
Within your ReaderRdr function, all you're accomplishing is just to add the string str to your List<string> reader. This accomplishes nothing and has no bearing whatsoever on the results you get from your database query in your SqlDataReader rdr. If you want to store the data you actually get from your database, use rdr, not the (useless) string argument str.
Also, I feel like you must have left something out of your code - you're instantiating your SqlCommand cmd with conn as your second argument, but I don't see that defined anywhere within your ReaderRdr method, and it's not an argument passed to ReaderRdr. You don't have an SqlConnection object as a field or property within your class, do you?
As far as what you should maybe do, despite lacking much of any context in terms of your actual aims - if you want to get any given column of the result for each row returned by your SqlDataReader:
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
var yourDataCell = rdr[yourColumnIndex];
// or:
var yourDataCellOtherWay = rdr["YourColumnName"];
}
Alternately, you can just iterate through each of the cells in any given row produced by your SqlDataReader like so:
for(int i = 0 ; i < numberOfColumns; i++) {
// do something with rdr[i] here
}
I'm not sure if there's anything you can do establish numberOfColumns based on the state of your SqlDataReader, but others might know better.
You actually adding the String that you passed to a function to your reader reader.Add(str); You get the response from SQL I your rdr.
This item will show you something from your database:
Console.WriteLine(String.Format("{0}", rdr[0]));

Using MySQLConnection in C# does not close properly

I try to write a class to make MySql Connections easier. My problem is, after I open a connection and close it. It is still open in the Database and gets aborted.
I'm using the 'using' statement' of course, but the connection is still open and gets aborted after I exit the program.
Here's what my code looks like:
using (DatabaseManager db = new DatabaseManager())
{
using (MySqlDataReader result = db.DataReader("SELECT * FROM module WHERE Active=1 ORDER BY Sequence ASC"))
{
foreach (MySqlDataReader result in db.DataReader("SELECT * FROM module WHERE Active=1 ORDER BY Sequence ASC"))
{
//Do stuff here
}
}
}
The class Database manager opens the connection and closes it when disposed:
public DatabaseManager()
{
this.connectionString = new MySqlConnectionStringBuilder("Server=localhost;Database=businessplan;Uid=root;");
connect();
}
private bool connect()
{
bool returnValue = true;
connection = new MySqlConnection(connectionString.GetConnectionString(false));
connection.Open();
}
public void Dispose()
{
Dispose(true);
}
public void Dispose(bool disposing)
{
if (disposing)
{
if (connection.State == System.Data.ConnectionState.Open)
{
connection.Close();
connection.Dispose();
}
}
//GC.SuppressFinalize(this);//Updated
}
//Updated
//~DatabaseManager()
//{
// Dispose(false);
//}
So, I checked it in the debugger and the Dispose()-method is called and executes correctly.
What am I missing? Is there something I did wrong or misunderstood?
Just in case, the DataReader()-method (Updated version):
public IEnumerable<IDataReader> DataReader(String query)
{
using (MySqlCommand com = new MySqlCommand())
{
com.Connection = connection;
com.CommandText = query;
using (MySqlDataReader result = com.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
while (result.Read())
{
yield return (IDataReader)result;
}
}
}
}
Ok, I tried to use the yield return:
foreach (MySqlDataReader result in db.DataReader("SELECT * FROM module WHERE Active=1 ORDER BY Sequence ASC"))
{
//...
}
And I changed the DataReader-method:
public IEnumerable<IDataReader> DataReader(String query)
{
using (MySqlCommand com = new MySqlCommand())
{
com.Connection = connection;
com.CommandText = query;
using (MySqlDataReader result = com.ExecuteReader())
{
while (result.Read())
{
yield return (IDataReader)result;
}
}
}
}
It works in the way that I can retrieve the data, yet I still have the same problem: The connection isn't closed properly.
Im unsure about mysqlconnection but the sql server counter part uses Connection pooling and does not close when you call close instead it puts it in the connection pool!
Edit: Make sure you dispose the Reader, Command, and Connection object!
Edit: Solved with the ConnectionString Parameter "Pooling=false" or the static methods MySqlConnection.ClearPool(connection) and MySqlConnection.ClearAllPools()
You need to wrap the Command and the DataReader in using statements as well.
According to the mysql docs, the MySQLConnection is not closed when it goes out of scope. Therefore you must not use it inside a using.
Quote...
"If the MySqlConnection goes out of scope, it is not closed. Therefore, you must explicitly close the connection by calling MySqlConnection.Close or MySqlConnection.Dispose."
Have a look at using something like this:
private static IEnumerable<IDataRecord> SqlRetrieve(
string ConnectionString,
string StoredProcName,
Action<SqlCommand> AddParameters)
{
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(StoredProcName, cn))
{
cn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (AddParameters != null)
{
AddParameters(cmd);
}
using (var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (rdr.Read())
yield return rdr;
}
}
}

Categories