相信使用过ADO.NET的同志多半都见过这个exception吧:
There is already an open DataReader associated with this Command which must be closed first.
抛出这个exception的主要原因是:一个SqlConnection只能和一个开着的SqlDataReader相关联。当开发人员忘记关掉打开的SqlDataReader,而又尝试打开一个新的SqlDataReader的时候,BCL就会抛出上述异常。重现方法如下:
Code
SqlConnection conn = new SqlConnection(connStr);
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM Person";
SqlDataReader reader1 = cmd.ExecuteReader();
while (reader1.Read())
{ /* Read Fields */ }
// Forget to close the DataReader
cmd.CommandText = "SELECT * FROM Course";
SqlDataReader reader2 = cmd.ExecuteReader(); // Throws the above exception
while (reader2.Read())
{ /* Read Fields */ }
这段代码很简单,我们一眼就能看出那个罪恶的DataReader。但是在实际的开发环境中,代码的封装会造成当 SqlDataReader reader2 = cmd.ExecuteReader(); 抛出exception时,我们很难找到那个忘关的DataReader。
下面,我将介绍一种方法,从SqlConnection回溯到打开着的SqlDataReader。
SqlConnection自身是没有任何public的方法可以返回当前打开着的SqlDataReader的,但是SqlConnection却有一些internal method能帮助我们得到所要的信息。它们是:
SqlConnection |
SqlInternalConnection GetOpenConnection(); |
SqlInternalConnection |
SqlDataReader FindLiveReader(SqlCommand command); |
SqlDataReader |
SqlCommand Command { get; } |
由于这些方法都是internal的,我们需要依赖于.NET Reflection才能call到它们:
GetLiveDataReaderCommand
1 static string GetLiveDataReaderCommand(SqlConnection conn)
2 {
3 Type connType = conn.GetType();
4 object internalConn = connType.InvokeMember("GetOpenConnection",
5 BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
6 null, conn, null);
7 Type internalConnType = internalConn.GetType();
8 SqlDataReader reader = internalConnType.InvokeMember("FindLiveReader",
9 BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
10 null, internalConn, new object[] { null }) as SqlDataReader;
11 if (reader != null)
12 {
13 Type readerType = typeof(SqlDataReader);
14 SqlCommand cmd = readerType.InvokeMember("Command",
15 BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
16 null, reader, null) as SqlCommand;
17 return cmd.CommandText;
18 }
19 return null; // No live SqlDataReader
20 }
GetLiveDataReaderCommand 接收一个SqlConnection 对象,并寻找其中open着的SqlDataReader。如果找到,则返回这个SqlDataReader对应SqlCommand的SQL语句。
当然,为了彻底根除忘关SqlDataReader的错误,推荐您还是使用using关键字从而SqlDataReader.Dispose方法会被自动call到:
Code
1 using (SqlDataReader reader1 = cmd.ExecuteReader())
2 {
3 while (reader1.Read())
4 {
5 // Read Fields
6 }
7 }