MySQL Connector/NET 连接每个连接多个 DataReader?

发布于 2024-09-18 18:24:27 字数 2210 浏览 1 评论 0原文

我现在正在从 Java 迁移到 C#,因为我意识到我更喜欢 C# 语言功能而不是 Java 中的功能,但我有一个小问题。在 MySQL Connector/J 和 JDBC 中,我相信我的一个应用程序允许在另一个应用程序打开时执行多个 PreparedStatement,就像我可以执行返回 ResultSet 的查询一样code> 并且当 ResultSet 仍然打开时,我可以打开另一个 PreparedStatement 并获取另一个 ResultSet 或者我可以根据我从第一个 ResultSet 获得的数据(即,当我意识到该行在密码列中有明文密码时,插入盐值并使用 SHA512 哈希更新密码列)。

然而,使用 Connector/NET,我开始意识到每当我尝试这样做时,我都会收到此错误: MySql.Data.MySqlClient.MySqlException:已经有一个与此连接关联的打开的 DataReader,必须首先关闭它。

有没有一种简单的方法来修复此错误,也许 MySQL 到 .网桥?我真的不想在一个应用程序中创建大量数据库连接,尽管我可能想为应用程序中的每个线程创建一个数据库连接(如在 ThreadLocal 中)。当我以两种不同的方法同时执行两个查询时,ThreadLocal DB 连接将有所帮助,但显然我无法将这两个命令分离到不同的线程中,并且我不想创建多余的线程。

顺便说一句,这是代码本身。是的,我可以将更新代码移至关闭阅读器后,但我有更多类似的方法,其中一些比这个方法更难修复:

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

如果移动更新代码是唯一的可能性,或者如果它是最好的,我想我只能凑合着用它,但我想首先对其他可能性有更多的想法,然后选择一个选项。

I am migrating from Java to C# now that I've realized I prefer the C# language features over the ones in Java, but I have this small issue. In MySQL Connector/J and JDBC, I believe that one of my applications allowed multiple PreparedStatements to be executed while another one is open, like I could perform a query that returns a ResultSet and while that ResultSet is still open, I could open another PreparedStatement and get another ResultSet or I could just execute an update, based on the data I got from my first ResultSet (i.e., insert a salt value and update the password column with a SHA512 hash when I realize that the row has a plaintext password in the password column).

However, with Connector/NET, I've come to realize whenever I try doing this, I get this error:
MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first.

Is there a easy way to fix this error, maybe any other implementations of a MySQL to .NET bridge? I don't really want to create a lot of DB connections in one application, although I might want to create one for every thread in my application (as in a ThreadLocal). A ThreadLocal DB connection will help when I perform two queries at the same time in two different methods, but obviously I cannot separate these two commands into different threads and I don't want to create excess threads.

By the way, here's the code itself. Yes, I can move the update code down to after I close the reader, but I have many more similar methods and some of them are more difficult to fix up than this one:

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

If moving the update code is the only possibility, or if it's the best one, I suppose I'll have to make do with it, but I want to get more ideas on other possibilities first and then pick an option.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

美人骨 2024-09-25 18:24:27

不,我敢打赌 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();
        }
    }
}

No, and I bet that's the case as well in the java world.

The connection is being actively used/hold to retrieve that data, if that worked in the java world is because it did one of:

  • read/cached the whole result set
  • did it in a separate connection behind the scenes

I don't see much on an issue, you just have to move the reader.Close to the appropriate place in your code. That said, you should go through that code anyway, as your dispose/close calls won't be correctly called if an exception occurs. Use the using statement to ensure everything is freed appropriately, below a modified version of your code with these changes (and a couple others that make it be less deep to the right):

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();
        }
    }
}
孤云独去闲 2024-09-25 18:24:27

好吧,伙计们,经过更多的研究,我意识到我错了。 Java 的 ResultSet 实际上确实保持与数据库的活动连接,如本页所示:
www.geekinterview.com/question_details/591

必须连接 ResultSet,以便 ResultSet.next() 方法正常工作以从数据库中获取下一行。请注意,这并不意味着连接正忙于为 ResultSet 提供服务,而是 ResultSet 仅保留连接,以便在收到命令时可以向前移动。

显然,SQL Server 具有与此类似的功能,允许您打开多个只读、只进查询,同时在同一连接上打开另一个查询,称为 MARS(多个活动结果集)。
http://www.codeguru.com/csharp/csharp/cs_network /database/article.php/c8715

经过更多研究,我意识到 MySQL Connector/NET 不支持此功能。这太糟糕了,因为我相信它比当前的实现更有意义,至少对于迁移 Java 开发人员来说是这样。

Alright guys, with a bit more of research, I realized I was wrong. Java's ResultSets do in fact hold an active connection to the database, as evidenced by this page:
www.geekinterview.com/question_details/591

ResultSets must be connected so that the ResultSet.next() method works properly to fetch the next row from the database. Note that this does not mean that the connection is busy servicing the ResultSet, but instead the ResultSet only holds on to the connection so that it could move forward when given the command to.

Apparently SQL server has something similar to this that allows you to open multiple read-only, forward-only queries while another is open on the same connection, called MARS (Multiple Active Result Sets).
http://www.codeguru.com/csharp/csharp/cs_network/database/article.php/c8715

With a little bit more research, I realized MySQL Connector/NET does not support this feature. It's too bad because I believe it makes more sense than the current implementation, at least for migrating Java developers.

旧城空念 2024-09-25 18:24:27

来自 MSDN

当使用 SqlDataReader 时,
关联的 SqlConnection 正忙
为 SqlDataReader 提供服务,并且没有
可以执行其他操作
除了关闭之外的 SqlConnection
它。直到关闭为止都是这种情况
调用 SqlDataReader 的方法。
例如,您无法检索
输出参数直到调用之后
关闭

我通常为解决此问题所做的事情是嵌套我需要的连接,以便当我第一次使用关闭时,所有其他连接都会被释放。

From MSDN

While the SqlDataReader is being used,
the associated SqlConnection is busy
serving the SqlDataReader, and no
other operations can be performed on
the SqlConnection other than closing
it. This is the case until the Close
method of the SqlDataReader is called.
For example, you cannot retrieve
output parameters until after you call
Close

What I typically do to resolve this is to nest my connections I need so that when I the first using closes all the other connections are disposed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文