MySQL Connector/NET 连接每个连接多个 DataReader?
我现在正在从 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 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
(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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
不,我敢打赌 Java 世界也是如此。
连接正在被积极使用/保持来检索该数据,如果在java世界中起作用是因为它执行了以下操作之一:
我没有看到太多出现问题时,您只需将阅读器移动到代码中的适当位置即可。也就是说,无论如何您都应该检查该代码,因为如果发生异常,您的 dispose/close 调用将不会被正确调用。使用 using 语句确保所有内容都得到适当释放,位于经过这些更改的代码修改版本下方(以及其他一些使其在右侧不那么深的地方):
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:
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):
好吧,伙计们,经过更多的研究,我意识到我错了。 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.
来自 MSDN
我通常为解决此问题所做的事情是嵌套我需要的连接,以便当我第一次使用关闭时,所有其他连接都会被释放。
From MSDN
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.