简体   繁体   English

MySQL 连接器/NET 连接每个连接有多个 DataReaders?

[英]MySQL Connector/NET connection multiple DataReaders per Connection?

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.我现在从 Java 迁移到 C#,因为我意识到我更喜欢 C# 语言功能而不是 Java 中的功能,但我有这个小问题。 In MySQL Connector/J and JDBC, I believe that one of my applications allowed multiple PreparedStatement s 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 (ie, 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).在 MySQL Connector/J 和 JDBC 中,我相信我的一个应用程序允许在另一个应用程序打开时执行多个PreparedStatement ,就像我可以执行一个返回ResultSet的查询,而当ResultSet仍然打开时,我可以打开另一个PreparedStatement并获得另一个ResultSet或者我可以根据我从我的第一个ResultSet获得的数据执行更新(即,当我意识到该行在密码栏)。

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.但是,使用连接器/NET,我开始意识到每当我尝试这样做时,我都会收到此错误: 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?有没有一种简单的方法可以解决这个错误,也许是 MySQL 到 .NET 桥的任何其他实现? 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).我真的不想在一个应用程序中创建很多数据库连接,尽管我可能想为我的应用程序中的每个线程创建一个(如在 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.当我以两种不同的方法同时执行两个查询时,ThreadLocal DB 连接会有所帮助,但显然我不能将这两个命令分成不同的线程,而且我不想创建多余的线程。

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.如果移动更新代码是唯一的可能性,或者如果它是最好的,我想我将不得不使用它,但我想首先获得有关其他可能性的更多想法,然后选择一个选项。

No, and I bet that's the case as well in the java world.不,我敢打赌,在 Java 世界中也是如此。

The connection is being actively used/hold to retrieve that data, if that worked in the java world is because it did one of:正在积极使用/保持连接来检索该数据,如果在 Java 世界中有效是因为它执行了以下操作之一:

  • 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.也就是说,无论如何您都应该检查该代码,因为如果发生异常,您的 dispose/close 调用将不会被正确调用。 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 语句确保适当释放所有内容,在您的代码的修改版本下方进行这些更改(以及其他一些使其右侧不那么深的):

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

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 Java 的 ResultSet 实际上确实保持与数据库的活动连接,如本页所示: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.必须连接 ResultSets,以便 ResultSet.next() 方法正常工作以从数据库中获取下一行。 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.请注意,这并不意味着连接正忙于为 ResultSet 提供服务,而是 ResultSet 仅保留连接,以便在收到命令时可以继续前进。

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).显然 SQL Server 有类似的东西,它允许您打开多个只读、只进查询,而另一个在同一连接上打开,称为 MARS(多个活动结果集)。 http://www.codeguru.com/csharp/csharp/cs_network/database/article.php/c8715 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.通过更多的研究,我意识到 MySQL Connector/NET 不支持此功能。 It's too bad because I believe it makes more sense than the current implementation, at least for migrating Java developers.这太糟糕了,因为我相信它比当前的实现更有意义,至少对于迁移 Java 开发人员而言。

From MSDN来自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.在使用SqlDataReader 的同时,关联的SqlConnection 正忙于为SqlDataReader 服务,除了关闭SqlConnection 之外,无法对SqlConnection 执行其他操作。 This is the case until the Close method of the SqlDataReader is called.在调用 SqlDataReader 的 Close 方法之前都是这种情况。 For example, you cannot retrieve output parameters until after you call Close例如,只有在调用 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.我通常用来解决这个问题是嵌套我需要的连接,以便当我第一次使用关闭所有其他连接时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM