4

嘿,我正在从Java迁移到C#,现在我已经意识到我更喜欢C#语言特性而不是Java语言特性,但是我有这个小问题。在MySQL Connector/J和JDBC中,我相信我的一个应用程序允许在另一个打开时执行多个PreparedStatement s,就像我可以执行返回ResultSet的查询并且该ResultSet仍然打开时,我可以打开另一个PreparedStatement并得到另一个ResultSet或者我可以执行一个更新,根据我从我的第一个ResultSet得到的数据(即,插入一个salt值并用SHA512哈希更新密码列时,我意识到该行中有明文密码密码栏)。MySQL连接器/ NET连接每个连接多个DataReader?

然而,连接器/ NET,我已经认识到,每当我尝试这样做,我得到这个错误: MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first.

有没有一种简单的方法来解决这个错误,也许的任何其他实现MySQL到.NET桥梁?我并不想在一个应用程序中创建很多数据库连接,尽管我可能想为应用程序中的每个线程创建一个(如在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(); 

如果移动的更新代码唯一的可能性,或者如果它是最好的,我想我必须做到这一点,但我想先获得关于其他可能性的更多想法,然后选择一个选项。

回答

2

不,我敢打赌,在Java世界中也是如此。

连接正在积极使用/ HOLD检索数据,如果是在Java世界中的工作,是因为它做了一个:

  • 读/缓存整个结果集
  • 做到了在一个单独的连接幕后

我没有看到太多的问题,你只需要移动reader.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(); 
     } 
    } 
} 
+0

只是一个快速的新手问题,如果reader.Read()返回false,那么我们不必对它做一个reader.Close()? – 2010-09-12 15:56:20

+0

@Kevin the Close会自动发生,因为读者的Dispose是用using语句调用的---这取决于你如何构造代码,你不需要明确地调用它。 – eglasius 2010-09-12 16:00:11

+0

好的,谢谢!由于这一点,我想我对MySQL Connector/NET有了更好的理解。我想JDBC的ResultSet实际上只是一个存储所有数据的表,然后关闭PreparedStatement,以便其他查询和非查询可以在之后执行,这几乎是唯一的解释。 – 2010-09-12 16:24:55

0

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

我通常如何解决这个是我的窝连接,我需要这样,当我第一个使用关闭所有其他连接设置。

+0

出于某种原因,我需要一整秒才能建立到我的MySQL数据库的连接,除非我正在做的事情完全错误。 事情是,我试图创建一个服务器,我想尝试与我的客户实时保持一秒钟,而且相当多的时间。对于每个线程,JDBC似乎首次为250毫秒左右建立连接。 – 2010-09-12 15:42:28

+0

没关系,似乎每个后续的数据库新连接看起来都要快得多。我试图通过使数据库连接ThreadStatic,然后用MySQL更新执行一个新线程,而另一个线程有一个Reader仍然打开。没有问题,我测量了11毫秒。 – 2010-09-12 16:28:01

2

好的家伙,带着几分更多的研究,我意识到我错了。 Java的结果集实际上保持与数据库的活动连接,如本页所示:www.geekinterview。com/question_details/591

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

显然SQL服务器有类似的东西,允许您打开多个只读,只进的查询,而另一个打开在同一个连接上,称为MARS(多活动结果集)。 http://www.codeguru.com/csharp/csharp/cs_network/database/article.php/c8715

随着多一点研究,我意识到MySQL连接器/ NET不支持此功能。这太糟糕了,因为我相信它比目前的实现更有意义,至少对于Java开发人员的迁移而言。