5

JavaからC#に移行しているので、Javaの機能よりもC#言語の機能の方が好きだと気づきましたが、この小さな問題があります。MySQL Connector / JおよびJDBCでは、アプリケーションの1つで、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へのブリッジの他の実装はありますか?1つのアプリケーションで多くのDB接続を作成したくはありませんが、(ThreadLocalのように)アプリケーションのスレッドごとに1つ作成したい場合があります。ThreadLocal DB接続は、2つの異なるメソッドで同時に2つのクエリを実行する場合に役立ちますが、明らかにこれら2つのコマンドを異なるスレッドに分離することはできず、余分なスレッドを作成したくありません。

ちなみに、これがコードそのものです。はい、リーダーを閉じた後、更新コードを下に移動できますが、同様の方法がたくさんあり、それらのいくつかはこれよりも修正が困難です。

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();

更新コードを移動することが唯一の可能性である場合、またはそれが最良のものである場合、私はそれでやらなければならないと思いますが、最初に他の可能性についてより多くのアイデアを得てからオプションを選択したいと思います。

4

3 に答える 3

2

いいえ、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();
        }
    }
}
于 2010-09-10T17:52:24.833 に答える
2

さて、もう少し研究して、私は自分が間違っていることに気づきました。次のページで証明されているように、JavaのResultSetは、実際にはデータベースへのアクティブな接続を保持しています:www.geekinterview.com/question_details/591

ResultSet.next()メソッドが正しく機能してデータベースから次の行をフェッチできるように、ResultSetを接続する必要があります。これは、接続がResultSetのサービスでビジーであることを意味するのではなく、代わりにResultSetが接続を保持するだけであるため、コマンドが与えられたときに接続が先に進むことができることに注意してください。

どうやらSQLサーバーにはこれに似たものがあり、MARS(Multiple Active Result Sets)と呼ばれる、同じ接続で別のクエリを開いているときに、複数の読み取り専用、転送専用のクエリを開くことができます。 http://www.codeguru.com/csharp/csharp/cs_network/database/article.php/c8715

もう少し調べてみると、MySQL Connector/NETはこの機能をサポートしていないことがわかりました。少なくともJava開発者を移行するためには、現在の実装よりも理にかなっていると思うので、それは残念です。

于 2010-10-06T02:43:25.967 に答える
0

MSDNから

SqlDataReaderが使用されている間、関連するSqlConnectionはSqlDataReaderの提供でビジーであり、SqlConnectionを閉じる以外の操作を実行することはできません。これは、SqlDataReaderのCloseメソッドが呼び出されるまでの場合です。たとえば、Closeを呼び出すまで、出力パラメータを取得することはできません。

これを解決するために私が通常行うことは、必要な接続をネストして、最初に使用したときに他のすべての接続が破棄されるようにすることです。

于 2010-09-10T14:32:19.247 に答える