如何判断一个SqlConnection是否有已连接的SqlDataReader?

14

这现在更多是出于好奇而非实际目的。如果您打开了一个 SqlConnection 并将一个 SqlDataReader 附加到它上面,然后尝试使用相同的 SqlConnection 运行另一个查询,那么它会抛出错误。我的问题是,SqlConnection 怎么知道有一个 reader 已经附加到它上面了。它没有公共属性或其他东西来表示是否具有 HasDataReader,那么 SqlConnection 类是怎么知道的?


原始问题:(已不再相关)

嗨,我正在设置一个小型连接池,我们遇到的一个更常见的 bug(通常很容易修复,但我们却无法记住 reader.Close()!)就是当一个连接被许多类/方法使用且一个方法打开了数据阅读器并忘记关闭它时。这不是真正的坏事,因为很多时候你只需要进入调试器,向上一级并查看之前的函数,检查它是否有未关闭的数据阅读器。

现在,这里有一个更大的问题。在这个连接池中,如果一个数据阅读器是打开的,则直到一个线程获取连接并尝试使用它之前,我们不知道是否有数据阅读器打开。最初打开数据阅读器的东西可能已经不存在了。

因此,简而言之,如何检测连接上是否有数据阅读器打开,是否有一种方法可以关闭 reader 而不关闭连接?


只是想确认一下 - 你不是在尝试编写自己的连接池机制吧?连接池已经由框架在后台处理了;你不需要自己做任何事情。 - Greg Beech
2
哇.. 我实际上不知道它提供连接池.. 我猜它是如此隐含,启用它只需要很少的工作,我错过了它..那么这个问题就不再相关了。 - Earlz
@earlz - 非常好!详情请参见:http://msdn.microsoft.com/zh-cn/library/8xx3tyca.aspx。 - Jeff Sternal
12个回答

14

确保关闭DataReaders(和数据库连接)的方法是始终在using块中打开它们,如下所示:

using (SqlDataReader rdr = MySqlCommandObject.ExecuteReader())
{
    while (rdr.Read())
    {
        //...
    }
} // The SqlDataReader is guaranteed to be closed here, even if an exception was thrown.

这也是我们的做法。需要注意的是,这仅适用于.NET 2.0及更高版本。如果您使用较早版本,则应使用Try/Finally块,并确保在Finally块中调用.Close()方法,并在调用Close()之前检查空引用。 - Sonny Boy
3
这很好,但并没有回答我的问题。我们已经开始使用using块,但我们仍然有旧代码没有使用它。在维护该代码时,可能会发生某些情况导致读取器未关闭。我们如何检测并抛出错误? - Earlz
2
所以你不想改变你的旧代码使其正确,而是添加一堆新代码来检测错误?这有点反向。 - Joel Coehoorn
实际上,在我的Windows XP机器上,使用.NET Framework 3.5调用Dispose(它会在using块的结尾自动发生)不会关闭IDataReader。似乎应该始终显式地调用其Close方法,例如在finally块中。 - stakx - no longer contributing
@stakx - 你说的部分正确。close方法应该被显式调用,但它不需要在finally块中。所有重要的非托管资源都在Dispose()方法中得到处理。然而,与读取器相关联的连接应该在finally或using块中释放。 - Joel Coehoorn
显示剩余2条评论

13
SqlConnection如何知道已经连接了一个reader呢?
据我所见,SqlConnection知道它已经连接了一个reader是因为它在内部维护了对它的引用。
审慎使用Reflector会发现,SQLConnection对象有一个私有字段,类型为DBConnectionInternal,它填充了该抽象类的许多个具体实现。当你尝试添加第二个活动reader到连接时,内部连接上的方法'ValidateConnectionForExecute'被调用,并且这通过跟踪检查一个内部的'ReferenceCollection'来实现。当这揭示了一个现有的活动reader时,就会抛出异常。
我想,如果你想的话,你可以使用反射在运行时自己挖掘所有这些信息。

+1,很可能OP不会得到比这更准确、更简洁的答案。 - stakx - no longer contributing
+1好答案 - 尊重@Joel和他的建议,但这实际上回答了问题 - slugster
1
顺便提一下,数据读取器的完整路径是 Connection.[Private]_innerConnection.[Private]ReferenceCollection.[Private]_items[i].Target,其中 [Private] 表示非公共字段/属性,i 是索引。 - Arithmomaniac

4
没有人真正回答earlz的问题。("为什么你要那样做?"不是一个答案。)我认为答案是,你无法仅通过查看连接本身来确定连接是否有关联的开放数据读取器。连接不公开任何属性来告诉你这一点。打开连接会将其State属性设置为ConnectionState.Open。在其上打开数据读取器不会改变连接状态。像ConnectionState.Fetching这样的状态值仅在数据操作(如SqlDataReader.Read())正在进行时使用。当连接只是在Reads之间坐着时,连接状态就是Open。因此,要确定何时使用连接的开放读取器,必须检查可能正在使用它的读取器的状态。

数据读取器如何将连接标记为“已打开的数据读取器”?由于没有公共可访问的方法或字段,因此数据读取器如何告诉连接在其他地方尝试使用它时抛出错误。我不理解的是,在SqlConnection / SqlCommand代码中的哪里检测到已打开的数据读取器。 - Earlz
2
虽然 Joel 没有“回答”这个问题,但他给出了正确的做法。特别是面对错误解决方案和正确解决方案的复杂性时,我会非常不赞成(即在文件中写下警告)任何试图使用 hackish 解决方案而不是使用 USING 块的员工。 - Godeke
1
我有同样的问题,这就是我正在寻找的确切答案。我的一个可重用的通用DAL中会将一个外部实例化的连接对象作为参数传递进来,我需要确定它是否是可重用的。 - Jon Davis
1
有时候,我们不能按照正确的方式去做事情。有时候,我们被困在错误的方式中(因为闭合依赖等原因),我们必须利用手头的资源。Stack Overflow的答案至少应该先尝试回答问题,而不是首先描述世界应该如何运作。 - Jon Davis

4

哇..很多人都没有回答问题!没有人提到的是多线程应用程序。我认为这里的每个人都明白你必须关闭读者,但我似乎没有看到任何人解决的问题是读者可能在下一个请求到来时还没完成。例如.. 我有一个通过单独的线程填充表格的表格,以便保留UI交互。在连接被使用时,让第二个、第三个和第四个线程等待会很好。然后当它释放时再做业务。如果没有一种干净的方法来确定连接是否附加了阅读器,我必须花费几分钟创建一些可能想要使用连接的每个类中的每个读者的静态布尔标志系统。比需要更复杂得多。


1
如果您尝试使用相同的SqlConnection运行另一个查询,那么它将抛出错误。当然,您可以启用多个活动结果集 - 然后它就不会抛出异常。当然,这也有一些限制(总是有限制吧?),但它能够工作。当然,这仅适用于嵌套操作。如果问题是您意外地保持了某些未关闭的内容,则答案是(如已经说明的)using

0

今天我也遇到了同样的情况,但是......在网络上没有运气。

所以,我编写了下面的代码来查找是否在连接中打开了读取器,或者通常查找连接是否准备好使用:

private bool IsConnectionReady(SqlConnection Connection)
{
    bool nRet = true;

    try
    {
        String sql = "SELECT * FROM dummy_table";

        using (SqlCommand cmd = new SqlCommand(sql, Connection))
        {
            using (SqlDataReader rdr = cmd.ExecuteReader())
            { }
        }
    }
    catch (Exception ex)
    {
        nRet = false;
    }

    return nRet;
}

“dummy_table”是我数据库中的一个空虚拟表,用于检查可访问性。

这只是一个解决方法,但我应该确保事情能够正常运行并能够在任何情况下检查连接可用性。

所以,我希望它能对你有所帮助。


0

如果由于某种原因无法使用using子句,您也可以使用委托。以下是一个实现的示例:

public delegate void TransactionRunner(DbConnection sender, DbTransaction trans, object state);

public void RunTransaction(TransactionRunner runner, object state)
    {
        RunTransaction(runner, IsolationLevel.ReadCommitted, state);
    }

public void RunTransaction(TransactionRunner runner, IsolationLevel il, object state)
    {

        DbConnection cn = GetConnection from pool
        DbTransaction trans = null;

        try
        {  
            trans = cn.BeginTransaction(il);
            runner(cn, trans, state);
            trans.Commit();
        }
        catch (Exception err)
        {
            if (trans != null)
                trans.Rollback();
            throw err;
        }
        finally
        {
            //Here you can close anything that was left open
        }
    }

然后当您需要使用它时,只需使用该函数并将函数传递为参数即可。

public void DoStuff(){
    TransactionRunner tr = new TransactionRunner(MyFunction);
    RunTransaction(tr, <a parameter>);
}
public void DoStuffInternal(DbConnection cn, DbTransaction trans, object state){
    //Do Stuff and Im sure that the transaction will commit or rollback
}

在 .Net 3.5 中,这似乎有些过度了,但在 .Net 1.0 中,这就是我们的做法...希望能对你有所帮助...


0
根据文章,即使您使用using块,完成后也应始终关闭读取器。使用using块将关闭连接,但不会关闭读取器。为什么存在这种不一致性?我也不知道。

0
为避免这种情况,请在 using 块中包装您的 DataReader,这将保证它像这样处置连接:
using (IDataReader reader = command.ExecuteReader())
{
      //do stuff
}

在IDataReader接口上有一个属性叫IsClosed,可以告诉你它的状态。


我需要通过连接判断数据读取器是否打开。我们将无法从应该检查此事的代码中访问数据读取器。 - Earlz
看一下SqlConnection上的ConnectionState枚举,我不确定但是ConnectionState.Fetching或者ConnectionState.Executing可能会给你想要的结果。然而,如果你没有对DataReader本身的引用,你将很难在不关闭连接本身并重新打开(至少据我所知)的情况下清理它。另外,你是否在整个应用程序中使用一个连接引用? - Jason Ruckman

0

检查它是否打开,如果是,则关闭它。请注意,如果您正在使用SqlHelper类,则存在一个错误 - 在某些情况下它不会关闭连接。解决方案是在代码中使用try/catch或using块,具体取决于您是否使用的是2.0版本之前的版本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接