4

I have a common database class for my application and in that class i have a function

public MySqlDataReader getRecord(string query)
        {
            MySqlDataReader reader;
            using (var connection = new MySqlConnection(connectionString))
            {
                connection.Open();
                using (var cmd = new MySqlCommand(query, connection))
                {

                    reader = cmd.ExecuteReader();
                    return reader;

                }
            }

            return null;
        }

and on my code behind pages I use

String sql = "SELECT * FROM `table`";
MySqlDataReader dr = objDB.getRecord(sql);
if (dr.Read())
{
   // some code goes hear
} 

and I am having error as Invalid attempt to Read when reader is closed.

I know access the reader after the database connection is closed is not possible bot I am looking for a work around in which I need not to change in the codebehind

EDIT: I WILL LIKE THE SOLUTION IN WHICH THE READER IS ASSIGNED TO OTHER OBJECT (SIMILAR TO READER ) AND THEN RETURN THAT OBJECT so i need not to change in all the application pages

GajendraSinghParihar
  • 9,051
  • 11
  • 36
  • 64

3 Answers3

12

You can load the results of your query to memory, then close the connection and still return an IDataReader that works as expected. Note that this costs memory.

public IDataReader getRecord(string query)
    {
        MySqlDataReader reader;
        using (var connection = new MySqlConnection(connectionString))
        {
            connection.Open();
            using (var cmd = new MySqlCommand(query, connection))
            {

                reader = cmd.ExecuteReader();
                var dt = new DataTable();
                dt.Load( reader );
                return dt.CreateDataReader();
            }
        }

        return null;
    }

In the callers:

String sql = "SELECT * FROM `table`";
var dr = objDB.getRecord(sql); // or DataTableReader dr = ...
if (dr.Read())
{
    // some code goes here
} 
Alex
  • 7,728
  • 3
  • 35
  • 62
5

When the scope of your call to using (var connection = new MySqlConnection(connectionString)) ends, the connection will be closed.

However, you are still returning a reader based on that connection. Once you try to use it in your caller method, you will get the error as closed connection can't be used.

Besides, your method is called GetRecord but it returns a reader.

One of the options is to do this:

public void processQuery(string query, Action<MySqlDataReader> fn)
{
    using (var connection = new MySqlConnection(connectionString))
    {
        connection.Open();
        using (var cmd = new MySqlCommand(query, connection))
        {
            using (var reader = cmd.ExecuteReader())
            {
               fn(reader);
            }
        }
    }
}


// caller
String sql = "SELECT * FROM `table`";
objDB.procesQuery(sql, dr => {
    if (dr.Read())
    {
       // some code goes here
    } 
});

Your idea of creating an object 'similar to reader', so you don't have to change the caller, would not work: the returned object would need to contain both reader and an open connection, so that you can use the reader. This means you would have to close the connection in the caller. In best case, the caller would need to be modified as follows:

String sql = "SELECT * FROM `table`";
using (MyWrapper wr = objDB.getRecord(sql))
{
   if (wr.Reader.Read())
   {
      // some code goes here
   } 
}

You will not save that much work, but one missing using statement in the caller will result in your app not working after some time due to a connection leak.

Zdeslav Vojkovic
  • 14,391
  • 32
  • 45
  • @Champ You cannot return the datareader AND wrap it in a using statement. Once it gets out of the using statement, `Dispose` is called on the datareader which will close the connection. You must rethink your design. – Arran Oct 02 '12 at 11:15
  • You should keep connection open. – Devart Oct 02 '12 at 11:17
  • @Arran: Yes, I know. Did you commented on the wrong entry or am I missing something? – Zdeslav Vojkovic Oct 02 '12 at 11:17
  • if i keep the connection open maximum limit of mysql connection is reached and the application crash – GajendraSinghParihar Oct 02 '12 at 11:18
  • @Devart, that would a poor solution, IMO. Now he has the additional burden of handling the connection too (which is an implementation detail of the callee) – Zdeslav Vojkovic Oct 02 '12 at 11:18
  • @ZdeslavVojkovic Apologies, the comment was not aimed at you, but Champ. Your solution is a good one. – Arran Oct 02 '12 at 11:18
  • @Arran, ah sorry, I missed his comment somehow. no problem. – Zdeslav Vojkovic Oct 02 '12 at 11:20
  • 1
    guys please see the note ... it will be difficult for me to make change in the complete application so we can't have a solution like assining the reader to something other object simillar to reader and then return that object ? – GajendraSinghParihar Oct 02 '12 at 11:22
  • If your whole app is written with functions which return useless reader object (and I doubt it as I can't believe that you wouldn't already notice that none of your queries work) then I don't see any choice. you CAN'T have a usable reader on closed connection, unless reader itself provides access to the connection object, which it doesn't (from what I can see) – Zdeslav Vojkovic Oct 02 '12 at 11:27
  • Well, you could wrap both reader and connection in an object and return that. IMO, that's horrible design which will bite you back sooner or later. – Zdeslav Vojkovic Oct 02 '12 at 11:30
  • My whole application is written and is working fine. unless i tried to make it live . On live application i found there was a connection leak and after some time i reached the maximum limit of mysql connection and the application crashed. so i need to change the database class and this function is the result of the change i am doing to solve the mysql connection leak – GajendraSinghParihar Oct 02 '12 at 11:32
  • no, your app is broken - if it has connection leak, it doesn't work fine, as after few queries it stops to work altogether. Let's just be clear about that. If it doesn't have to work, you can replace the whole `getRecord` with `return null`, the result will be the same. Anyway, I provided an option in my last comment. Now you will have to change EVERY place which calls `getRecord` in order to handle the connection explicitly. – Zdeslav Vojkovic Oct 02 '12 at 11:36
  • I'd just like to clarify once again that "working fine. unless i tried to make it live" == "working fine, unless it is used". As you need anyway to change each caller even if you implement your 'similar object' approach, you might as well do it properly. – Zdeslav Vojkovic Oct 02 '12 at 11:39
0

What you want is possible, but it is not a nice solution, because you have to wrap all the functions of the MySqlDataReader class and forward it to the real MySqlDataReader. See the ConnectedMySqlDataReader class (hint: it does not implement all functions of MySqlDataReader, if you really want to use it, you have to do it yourself) and how it would fit in your solution:

public ConnectedMySqlDataReader GetRecord(string query)
{
    return new ConnectedMySqlDataReader(connectionString, query);
}

// ...

var sql = "SELECT * FROM `table`";
using(var dr = objDB.GetRecord(sql))
{
    if (dr.Read())
    {
        // some code goes hear
    } 
 }

i have not tested this class, it is just for demonstration!

using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using MySql.Data.MySqlClient;

namespace MySqlTest
{
    public class ConnectedMySqlDataReader : DbDataReader
    {
        private readonly MySqlConnection _connection;
        private readonly Lazy<MySqlDataReader> _reader;
        private MySqlCommand _command;


        public ConnectedMySqlDataReader(MySqlConnection connection, string query)
        {
            if(connection == null)
                throw new ArgumentNullException("connection");
            _connection = connection;
            _reader = new Lazy<MySqlDataReader>(() =>
            {                
                _connection.Open();
                _command = new MySqlCommand(query, _connection);
                return _command.ExecuteReader();
            });
        }

        public ConnectedMySqlDataReader(string connectionString, string query)
            : this(new MySqlConnection(connectionString), query) { }

        private MySqlDataReader Reader
        {
            get { return _reader.Value; }
        }

        public override void Close()
        {
            if (_reader.IsValueCreated)            
                _reader.Value.Close();
            if(_command != null)
                _command.Dispose();
            _connection.Dispose();
        }

        public override DataTable GetSchemaTable()
        {
            return this.Reader.GetSchemaTable();
        }

        public override bool NextResult()
        {
            return this.Reader.NextResult();
        }

        public override bool Read()
        {
            return this.Reader.Read();
        }

        public override int Depth
        {
            get { return this.Reader.Depth; }
        }

        public override bool IsClosed
        {
            get { return this.Reader.IsClosed; }
        }

        public override int RecordsAffected
        {
            get { return this.Reader.RecordsAffected; }
        }

        public override bool GetBoolean(int ordinal)
        {
            return this.Reader.GetBoolean(ordinal);
        }

        public override byte GetByte(int ordinal)
        {
            return this.Reader.GetByte(ordinal);
        }

        public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
        {
            return this.Reader.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
        }

        public override char GetChar(int ordinal)
        {
            return this.Reader.GetChar(ordinal);
        }

        public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
        {
            return this.Reader.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
        }

        public override Guid GetGuid(int ordinal)
        {
            return this.Reader.GetGuid(ordinal);
        }

        public override short GetInt16(int ordinal)
        {
            return this.Reader.GetInt16(ordinal);
        }

        public override int GetInt32(int ordinal)
        {
            return this.Reader.GetInt32(ordinal);
        }

        public override long GetInt64(int ordinal)
        {
            return this.Reader.GetInt64(ordinal);
        }

        public override DateTime GetDateTime(int ordinal)
        {
            return this.Reader.GetDateTime(ordinal);
        }

        public override string GetString(int ordinal)
        {
            return this.Reader.GetString(ordinal);
        }

        public override object GetValue(int ordinal)
        {
            return this.Reader.GetValue(ordinal);
        }

        public override int GetValues(object[] values)
        {
            return this.Reader.GetValues(values);
        }

        public override bool IsDBNull(int ordinal)
        {
            return this.Reader.IsDBNull(ordinal);
        }

        public override int FieldCount
        {
            get { return this.Reader.FieldCount; }
        }

        public override object this[int ordinal]
        {
            get { return this.Reader[ordinal]; }
        }

        public override object this[string name]
        {
            get { return this.Reader[name]; }
        }

        public override bool HasRows
        {
            get { return this.Reader.HasRows; }
        }

        public override decimal GetDecimal(int ordinal)
        {
            return this.Reader.GetDecimal(ordinal);
        }

        public override double GetDouble(int ordinal)
        {
            return this.Reader.GetDouble(ordinal);
        }

        public override float GetFloat(int ordinal)
        {
            return this.Reader.GetFloat(ordinal);
        }

        public override string GetName(int ordinal)
        {
            return this.Reader.GetName(ordinal);
        }

        public override int GetOrdinal(string name)
        {
            return this.Reader.GetOrdinal(name);
        }

        public override string GetDataTypeName(int ordinal)
        {
            return this.Reader.GetDataTypeName(ordinal);
        }

        public override Type GetFieldType(int ordinal)
        {
            return this.Reader.GetFieldType(ordinal);
        }

        public override IEnumerator GetEnumerator()
        {
            return this.Reader.GetEnumerator();
        }
    }
}

PS: this is what sealed in c# context means, and yes, MySqlDataReader is sealed.

esskar
  • 10,638
  • 3
  • 36
  • 57