1

So this is a little bit code-ceptionlike.

I have a function that is checking the last ID in a table, this function is called within another function. At the end of that function, I have another function that's opening another datareader.

Error:

There is already an open Datareader associated with this connection which must be closed first.

getLastIdfromDB()

public string getLastIdFromDB()
{
     int lastIndex;
     string lastID ="";
     var dbCon = DB_connect.Instance();

     if (dbCon.IsConnect())
     {
         MySqlCommand cmd2 = new MySqlCommand("SELECT ID FROM `competitor`", dbCon.Connection);

         try
         {
             MySqlDataReader reader = cmd2.ExecuteReader();

             while (reader.Read())
             {
                   string item = reader2["ID"].ToString();
                   lastIndex = int.Parse(item);
                   lastIndex++;
                   lastID = lastIndex.ToString();
              }
         }
         catch (Exception ex)
         {
              MessageBox.Show("Error:" + ex.Message);
         }
     }

     return lastID;
}

This function is later-on used in this function:

private void addPlayerBtn_Click(object sender, EventArgs e)
{
     ListViewItem lvi = new ListViewItem(getLastIdFromDB());
     .........................................^ 
     ...                                    HERE
     ...
     ...   irrelevant code removed
     .........................................

            var dbCon = DB_connect.Instance();

            if (dbCon.IsConnect())
            {
                MySqlCommand cmd = new MySqlCommand("INSERT INTO `competitor`(`ID`, `Name`, `Age`) VALUES(@idSql,@NameSql,@AgeSql)", dbCon.Connection);
                cmd.Parameters.AddWithValue("@idSql", getLastIdFromDB());
                cmd.Parameters.AddWithValue("@NameSql", playerName.Text);
                cmd.Parameters.AddWithValue("@AgeSql", playerAge.Text);

                try
                {
                    cmd.ExecuteNonQuery();
                    listView1.Items.Clear();
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error:" + ex.Message);
                    dbCon.Connection.Close();
                }
                finally 
                { 
                    updateListView();
                }
        }
}

What would be the best way for me to solve this problem and in the future be sure to close my connections properly?

UPDATE: (per request, included DB_connect)

    class DB_connect
    {
        private DB_connect()
        {
        }

        private string databaseName = "simhopp";

        public string DatabaseName
        {
            get { return databaseName; }
            set { databaseName = value; }
        }

        public string Password { get; set; }
        private MySqlConnection connection = null;

        public MySqlConnection Connection
        {
            get { return connection; }
        }

        private static DB_connect _instance = null;

        public static DB_connect Instance()
        {
            if (_instance == null)
                _instance = new DB_connect();
            return _instance;
        }

        public bool IsConnect()
        {
            bool result = true;

            try
            {
                if (Connection == null)
                {
                    if (String.IsNullOrEmpty(databaseName))
                        result = false;

                    string connstring = string.Format("Server=localhost; database={0}; UID=root;", databaseName);

                    connection = new MySqlConnection(connstring);
                    connection.Open();

                    result = true;
                }
            }
            catch (Exception ex)
            {
                Console.Write("Error: " + ex.Message);
            }

            return result;
        }

        public void Close()
        {
            connection.Close();
        }
    }
}
sudheeshix
  • 1,541
  • 2
  • 17
  • 28
Joel
  • 5,732
  • 4
  • 37
  • 65
  • `DB_connect` - what is this? – mason Feb 22 '17 at 15:24
  • @mason just a function that is making sure that i'm connected to the DB – Joel Feb 22 '17 at 15:24
  • First I would enclose the `MySqlDataReader reader` in a `Using` block – Pikoh Feb 22 '17 at 15:25
  • No, that's an object. And you haven't included the code for it in your question, even though it looks like a critical part of your connection handling. – mason Feb 22 '17 at 15:25
  • You need to close the data reader for the same connection before you execute any other query. See http://stackoverflow.com/questions/5440168/exception-there-is-already-an-open-datareader-associated-with-this-connection-w – sudheeshix Feb 22 '17 at 15:26
  • @mason yes it is, but not for this particular problem. It's handling the connection to the db. If you want me to include it, i can go ahead and do that. – Joel Feb 22 '17 at 15:26
  • @sudheeshix yes i saw that, but after trying for 30min or so with different previously posed questions and still having that same problem, i decided to post my own question. – Joel Feb 22 '17 at 15:27
  • @Joel Yes, your connection handling here is atrocious. It'll help whoever is answering to have the code so they can tell you how to improve it best. – mason Feb 22 '17 at 15:27
  • 1
    As is told you,as a good practice, you should be using `Using` blocks, something like `using(MySqlDataReader Reader = cmd2.ExecuteReader()) { ...}`. That way you'll make sure all the connections are released when not used – Pikoh Feb 22 '17 at 15:31
  • I would suggest that you call `getLastIdFromDB` once at the start of `addPlayerBtn_Click`, store it into a variable local to the latter, and then use that in the rest of the method. Also, as @Pikoh mentioned, you should use a `using` statement when initializing the reader. This takes care of closing & disposing the reader at the end of the block. – sudheeshix Feb 22 '17 at 15:34

3 Answers3

2

You are trying to have multiple open readers on the same connection. This is commonly called "MARS" (multiple active result sets). MySql seems to have no support for it.

You will have to either limit yourself to one open reader at a time, or use more than one connection, so you can have one connection for each reader.

My suggestion would be to throw away that singleton-like thingy and instead use connection pooling and proper using blocks.

Community
  • 1
  • 1
nvoigt
  • 75,013
  • 26
  • 93
  • 142
0

As suggested by Pikoh in the comments, using the using clause indeed solved it for me.

Working code-snippet:

getLastIdFromDB

using (MySqlDataReader reader2 = cmd2.ExecuteReader()) { 

      while (reader2.Read())
      {
         string item = reader2["ID"].ToString();
         lastIndex = int.Parse(item);
         lastIndex++;
         lastID = lastIndex.ToString();
      }
}
Joel
  • 5,732
  • 4
  • 37
  • 65
  • Additionally, not related to this issue, but I would suggest a more efficient way to get the next id from db. Retrieving all rows from that table, and setting the same value in the while loop seems quite inefficient. If you can't use an auto-incrementing column, would it be possible to use MAX(ID) instead? That way, you can use `if` instead of `while`. – sudheeshix Feb 22 '17 at 15:40
  • sorry, this was the only method that actually got me the latest ID-record in the db. I tried using MAX but mySQL in c# didnt like that, always gave me -1 in return, (i've tried multiple solutions, hence why i came back to this basic solution) – Joel Feb 26 '17 at 19:30
0

Your connection handling here is not good. You need to ditch the DB_connect. No need to maintain a single connection - just open and close the connection each time you need it. Under the covers, ADO.NET will "pool" the connection for you, so that you don't actually have to wait to reconnect.

For any object that implements IDisposable you need to either call .Dispose() on it in a finally block, or wrap it in a using statement. That ensures your resources are properly disposed of. I recommend the using statement, because it helps keep the scope clear.

Your naming conventions should conform to C# standards. Methods that return a boolean should be like IsConnected, not IsConnect. addPlayerBtn_Click should be AddPlayerButton_Click. getLastIdFromDB should be GetlastIdFromDb or getLastIdFromDatabase.

public string GetLastIdFromDatabase()
{
     int lastIndex;
     string lastID ="";

     using (var connection = new MySqlConnection(Configuration.ConnectionString))
     using (var command = new MySqlCommand("query", connection))
     {
         connection.Open();
         MySqlDataReader reader = cmd2.ExecuteReader();

         while (reader.Read())
         {
               string item = reader2["ID"].ToString();
               lastIndex = int.Parse(item);
               lastIndex++;
               lastID = lastIndex.ToString();
         }   
     }

     return lastID;
}

Note, your query is bad too. I suspect you're using a string data type instead of a number, even though your ID's are number based. You should switch your column to a number data type, then select the max() number. Or use an autoincrementing column or sequence to get the next ID. Reading every single row to determine the next ID and incrementing a counter not good.

mason
  • 31,774
  • 10
  • 77
  • 121
  • Thank you for the feedback. The naming conventions, first letter of self-declared functions or variables is starting with lower-case letters to distinguish them from built-in functionality. However, Bool-functions i'm using uppercase naming on. Secondly. I tried the max() query, as well as TOP 1, but both gave me -1 as output. this was the only thing that actually gave me the correct query-response. I am using finally blocks too, but i removed them from the this question to reduce the amount of code. – Joel Feb 22 '17 at 16:38