VB.NET控制台应用SqlDataReader报错:已有关联阅读器需先关闭
问题现象
执行第二个SqlDataReader时触发错误:
there is already a datareader associated with this command which must be closed first
原本依赖Using块自动释放资源,但问题依然出现,此前仅将数据存储从DataTable改为List(Of StoreDate)。
相关代码
主程序代码
Imports System.Data.SqlClient Dim SqlQuery = New StringBuilder Dim LstResults As New List(Of StoreDate) SqlQuery.Append("searh something ") Using Conn As New SqlConnection(CONSTR) Conn.Open() Using Cmd As New SqlCommand(SqlQuery.ToString, Conn) Cmd.Parameters.AddWithValue("@param1", Param1) Cmd.Parameters.AddWithValue("@param2", Param2) Cmd.Parameters.AddWithValue("@Param3", Param3) Cmd.CommandTimeout = 0 Using Reader As SqlDataReader = Cmd.ExecuteReader() While Reader.Read() LstResults.Add(New StoreDate(Reader!Store, Reader!Date)) End While End Using End Using Conn.Close() End Using If LstResults.Count > 0 Then Try Using Conn As New SqlConnection(CONSTR) Conn.Open() For Each Result As StoreDate In LstResults Store = Result.Store Date = Result.Date Using Cmd = New SqlCommand("STORE_PROCEDURE", Conn) Cmd.CommandType = CommandType.StoredProcedure Cmd.CommandTimeout = 0 Cmd.Parameters.AddWithValue("@Date", DateStore) Cmd.Parameters.AddWithValue("@Store", Tienda) Cmd.ExecuteNonQuery() End Using Next Conn.Close() End Using Catch ex As Exception log("Error: " & ex.Message, alog) End Try End If SqlQuery.Clear() LstResults.Clear() Try SqlQuery.Append("do another search ") Using Conn As New SqlConnection(CONSTR) Conn.Open() Using Cmd = New SqlCommand(SqlQuery.ToString, Conn) Cmd.Parameters.AddWithValue("@param1", Param1) Cmd.Parameters.AddWithValue("@param2", Param2) Cmd.Parameters.AddWithValue("@Param3", Param3) Cmd.CommandTimeout = 0 Using Reader As SqlDataReader = Cmd.ExecuteReader() While Reader.Read() LstResults.Add(New StoreDate(Reader!Store, Reader!Date)) End While End Using End Using Conn.Close() End Using Catch ex As Exception log("Error: " & ex.Message , alog) Environment.Exit(ex.HResult) End Try
StoreDate类代码
Public Class StoreDate Private _Store As String Private _Date As DateTime Public Property Store As String Get Return _Store End Get Set(value As String) _Store = value End Set End Property Public Property Date As DateTime Get Return _Date End Get Set(value As DateTime) _Date = value End Set End Property Public Sub New(ByVal Store As String, ByVal Date As DateTime) _Store = Store _Date = Date End Sub End Class
尝试过的解决方案及结果
- 更新1:尝试保持单个连接打开,改用
DataTable.Load(Cmd.ExecuteReader())替代SqlDataReader手动读取,仍触发相同错误。'Using Reader As SqlDataReader = Cmd.ExecuteReader() ' While Reader.Read() ' LstStoreDate.Add(New StoreDate(Reader!Store, Reader!Date)) ' End While 'End Using TblStoreDate.Load(Cmd.ExecuteReader()) - 更新2:改用
try-catch-finally手动关闭资源后,问题恢复正常。
问题根源分析
连接池复用导致资源关联残留
代码中每次创建新的SqlConnection实例,但.NET的SQL Server连接池会复用底层数据库连接。如果第一个操作的SqlDataReader未完全释放(即使Using块理论上会处理),复用的连接可能仍残留未关闭的读取器关联,导致第二个操作报错。Using块释放时机延迟Using块会在代码块结束时调用对象的Dispose方法,但对于SqlDataReader,Dispose内部尝试关闭读取器时,若存在网络延迟或数据库端未及时响应,可能导致释放不及时。加上连接池复用,就会出现后续操作读取器冲突。多余的
Conn.Close()调用
代码在Using块内部手动调用Conn.Close(),这是不必要的。Using块会自动关闭并释放连接,手动关闭可能打乱连接池的正常管理逻辑,导致资源释放异常。
修复建议
移除所有手动
Conn.Close()调用
完全依赖Using块管理连接生命周期,避免干扰连接池的正常运作。确保
SqlCommand和SqlDataReader的Using块完全嵌套
现有代码已做到这一点,但需确保没有遗漏任何资源的Using包裹。启用MultipleActiveResultSets(可选)
如果确实需要在同一个连接上同时运行多个读取器,可以在连接字符串中添加MultipleActiveResultSets=True,但更推荐确保每个读取器完全释放后再复用连接。修正SQL语句拼写错误
注意到SQL语句中写的是searh something(应为search),虽然不直接导致读取器错误,但可能引发查询异常,间接影响资源释放流程。
内容的提问来源于stack exchange,提问作者user26811754

