Соединение MySQL Connector/NET с несколькими DataReaders на соединение?

Я перехожу с Java на C# теперь, когда понял, что предпочитаю функции языка C# функциям Java, но у меня есть небольшая проблема. Я считаю, что в MySQL Connector/J и JDBC одно из моих приложений позволяло выполнять несколько PreparedStatement, в то время как другое было открыто, например, я мог бы выполнить запрос, который возвращает ResultSet, и пока этот ResultSet все еще открыт, я мог бы открыть еще один PreparedStatement и получить еще один ResultSet, или я мог бы просто выполнить обновление на основе данных, которые я получил от моего первого ResultSet (т. е. вставить значение соли и обновить столбец пароля с помощью хэша SHA512, когда я понимаю, что строка имеет открытый текстовый пароль в столбец пароля).

Однако с Connector/NET я понял, что всякий раз, когда я пытаюсь это сделать, я получаю эту ошибку: MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first.

Есть ли простой способ исправить эту ошибку, возможно, какие-либо другие реализации моста MySQL для .NET? Я действительно не хочу создавать много подключений к БД в одном приложении, хотя мне может понадобиться создать по одному для каждого потока в моем приложении (как в ThreadLocal). Соединение с БД ThreadLocal поможет, когда я выполняю два запроса одновременно двумя разными методами, но, очевидно, я не могу разделить эти две команды на разные потоки и не хочу создавать лишние потоки.

Кстати, вот сам код. Да, я могу перенести код обновления вниз после того, как закрою читалку, но у меня есть еще много подобных методов, и некоторые из них исправить сложнее, чем этот:

MySqlConnection con = DatabaseConnection.GetConnection();
MySqlCommand cmd = con.CreateCommand();
cmd.CommandText = "SELECT `id`,`password`,`salt`,`pin`,`gender`,`birthday` FROM `accounts` WHERE `name` = '" + AccountName + "'";
MySqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
    AccountId = reader.GetInt32(0);
    string passhash = !reader.IsDBNull(1) ? reader.GetString(1) : null;
    string salt = !reader.IsDBNull(2) ? reader.GetString(2) : null;
    m_pin = !reader.IsDBNull(3) ? reader.GetString(3) : null;
    Gender = !reader.IsDBNull(4) ? reader.GetByte(4) : WvsCommon.Gender.UNDEFINED;
    m_birthday = !reader.IsDBNull(5) ? reader.GetInt32(5) : 0;
    if (!HashFunctions.HashEquals(pwd, HashAlgorithms.SHA512, passhash + salt))
    {
        if (passhash == pwd || salt == null && HashFunctions.HashEquals(pwd, HashAlgorithms.SHA1, passhash))
        {
            salt = HashFunctions.GenerateSalt();
            passhash = HashFunctions.GenerateSaltedSha512Hash(pwd, salt);
            MySqlCommand update = con.CreateCommand();
            update.CommandText = "UPDATE `accounts` SET `password` = '" + passhash + "', `salt` = '" + salt + "' WHERE `id` = " + AccountId;
            update.ExecuteNonQuery();
            update.Dispose();
        }
    }
}
reader.Close();
cmd.Dispose();

Если перемещение кода обновления является единственной или лучшей возможностью, я полагаю, что мне придется обойтись этим, но я хочу сначала получить больше идей о других возможностях, а затем выбрать вариант.


person Kevin Jin    schedule 10.09.2010    source источник


Ответы (3)


Нет, и я уверен, что это так и в мире Java.

Соединение активно используется/удерживается для извлечения этих данных, если это сработало в мире Java, потому что оно сделало одно из:

  • прочитать/кэшировать весь набор результатов
  • сделал это в отдельном соединении за кулисами

Я не вижу многого в проблеме, вам просто нужно переместить считыватель. Ближе к соответствующему месту в вашем коде. Тем не менее, вы все равно должны пройти через этот код, так как ваши вызовы dispose/close не будут правильно вызваны, если возникнет исключение. Используйте оператор using, чтобы убедиться, что все освобождено должным образом, ниже измененной версии вашего кода с этими изменениями (и парой других, которые делают его менее глубоким справа):

using(MySqlConnection con = DatabaseConnection.GetConnection())
using(MySqlCommand cmd = con.CreateCommand())
{
    cmd.CommandText = "SELECT `id`,`password`,`salt`,`pin`,`gender`,`birthday` FROM `accounts` WHERE `name` = '" + AccountName + "'";
    using(MySqlDataReader reader = cmd.ExecuteReader())
    {
        if(!reader.Read()) return;
        AccountId = reader.GetInt32(0);
        string passhash = !reader.IsDBNull(1) ? reader.GetString(1) : null;
        string salt = !reader.IsDBNull(2) ? reader.GetString(2) : null;
        m_pin = !reader.IsDBNull(3) ? reader.GetString(3) : null;
        Gender = !reader.IsDBNull(4) ? reader.GetByte(4) : WvsCommon.Gender.UNDEFINED;
        m_birthday = !reader.IsDBNull(5) ? reader.GetInt32(5) : 0;
        reader.Close();
        if (HashFunctions.HashEquals(pwd, HashAlgorithms.SHA512, passhash + salt))
            return;
        if(passhash != pwd && !(salt == null && HashFunctions.HashEquals(pwd, HashAlgorithms.SHA1, passhash)))
            return;
        salt = HashFunctions.GenerateSalt();
        passhash = HashFunctions.GenerateSaltedSha512Hash(pwd, salt);
        using(MySqlCommand update = con.CreateCommand())
        {
           update.CommandText = "UPDATE `accounts` SET `password` = '" + passhash + "', `salt` = '" + salt + "' WHERE `id` = " + AccountId;
           update.ExecuteNonQuery();
        }
    }
}
person eglasius    schedule 10.09.2010
comment
Просто быстрый вопрос новичка: если reader.Read() возвращает false, то нам не нужно выполнять для него reader.Close()? - person Kevin Jin; 12.09.2010
comment
@Kevin the Close происходит автоматически из-за Dispose читателя, который вызывается с помощью оператора using --- в зависимости от того, как вы структурируете код, вам не нужно вызывать это явно. - person eglasius; 12.09.2010
comment
Хорошо, спасибо! Я думаю, что благодаря этому я лучше понял MySQL Connector/NET. Я предполагаю, что ResultSet JDBC на самом деле является просто таблицей, в которой хранятся все данные, а затем закрывается PreparedStatement, чтобы сразу после этого можно было выполнить другие запросы и не запросы, это практически единственное объяснение. - person Kevin Jin; 12.09.2010
comment
@Kevin в этом случае эквивалентом будут наборы данных + адаптер данных. Хотя в настоящее время структура сущностей может предоставить вам либо функциональность/процесс, когда вы читаете, либо читаете все --- наряду со многими другими функциями. - person eglasius; 13.09.2010

Ладно, ребята, проведя еще немного исследований, я понял, что ошибался. Наборы результатов Java действительно поддерживают активное соединение с базой данных, о чем свидетельствует эта страница: www.geekinterview.com/question_details/591.

Наборы результатов должны быть подключены, чтобы метод ResultSet.next() работал правильно для получения следующей строки из базы данных. Обратите внимание, что это не означает, что соединение занято обслуживанием ResultSet, вместо этого ResultSet только удерживает соединение, чтобы оно могло двигаться вперед, когда ему дана команда.

По-видимому, в SQL-сервере есть что-то подобное, что позволяет вам открывать несколько запросов только для чтения и только для пересылки, в то время как другой открыт в том же соединении, называемом MARS (несколько активных наборов результатов). http://www.codeguru.com/csharp/csharp/cs_network/database/article.php/c8715

Проведя небольшое исследование, я понял, что MySQL Connector/NET не поддерживает эту функцию. Это очень плохо, потому что я считаю, что это имеет больше смысла, чем текущая реализация, по крайней мере, для мигрирующих разработчиков Java.

person Kevin Jin    schedule 06.10.2010

Из MSDN

Пока SqlDataReader используется, связанный SqlConnection занят обслуживанием SqlDataReader, и над SqlConnection нельзя выполнять никакие другие операции, кроме его закрытия. Это происходит до тех пор, пока не будет вызван метод Close объекта SqlDataReader. Например, вы не можете получить выходные параметры до тех пор, пока не вызовете функцию Close.

Что я обычно делаю, чтобы решить эту проблему, так это вложить мои соединения, которые мне нужны, чтобы при первом использовании закрывались все остальные соединения.

person msarchet    schedule 10.09.2010
comment
По какой-то причине мне требуется целая секунда, чтобы установить соединение с моей базой данных MySQL, если только я не делаю это совершенно неправильно. Дело в том, что я пытаюсь создать сервер, который хочу поддерживать в режиме реального времени со своими клиентами, а одна секунда — это довольно много времени. JDBC, кажется, устанавливает соединение в среднем около 250 мс в первый раз для каждого потока. - person Kevin Jin; 12.09.2010
comment
Ничего себе, кажется, что каждое последующее новое подключение к базе данных после первого, кажется, происходит намного быстрее. Я попробовал это, установив соединение с базой данных ThreadStatic, а затем выполнив новый поток с обновлением MySQL, в то время как в другом потоке все еще был открыт Reader. Никаких проблем, и я измеряю 11 мс. - person Kevin Jin; 12.09.2010