DataReader中的倍数表

54

我通常使用DataSet,因为它非常灵活。最近我被分配了代码优化任务,为了减少对数据库的访问次数,我正在修改一个存储过程中的两个查询。其中一个查询返回计数,另一个查询返回实际数据。也就是说,我的存储过程返回两个表格。现在,我知道如何使用DataSets读取这两个表格,但我需要使用DataReader来读取这两个表格。在搜索过程中,我发现了这篇文章

我按照这篇文章写了我的代码:

dr = cmd.ExecuteReader();
while (dr.Read())
{


}
if (dr.NextResult()) // this line throws exception
{
   while (dr.Read())
{

但是我在 dt.NextResult 处遇到了异常。异常信息如下:

Invalid attempt to call NextResult when reader is closed.

我也谷歌了以上错误,但仍无法解决问题。非常感谢任何帮助。我需要使用 datareader 读取多个表,这是否可行?


1
我不理解的是:“一个查询返回计数,另一个返回实际数据。也就是说,我的存储过程返回两个表。”为什么计数(它是一个标量值)是一个 - Tim Schmelter
是的,它是标量值,但存储过程是使用动态查询编写的。两个查询非常大,并且查询是作为varchar编写的,最后使用Exec执行。如果将计数查询作为实际数据查询的子查询调用,则查询变量的大小非常大,并且会出现错误。因此,为了避免这种情况,我编写了两个不同的查询,这也是计数也来自表(第二个表)的原因。我希望我已经解释清楚了。 - muhammad kashif
4个回答

68

试试这个,因为它会在任务完成后关闭连接、数据读取器和命令,以便不会出现数据读取器关闭异常。

还要像这样检查 if(reader.NextResult()) 来检查是否有下一个结果。

using (SqlConnection connection = new SqlConnection("connection string here"))
{
    using (SqlCommand command = new SqlCommand
           ("SELECT Column1 FROM Table1; SELECT Column2 FROM Table2", connection))
    {
        connection.Open(); 
        using (SqlDataReader reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                MessageBox.Show(reader.GetString(0), "Table1.Column1");
            }

            if(reader.NextResult())
            {
               while (reader.Read())
              {
                MessageBox.Show(reader.GetString(0), "Table2.Column2");
              }
            }
        }
    }
}

@muhammadkashif- 你的错误是由于连接关闭或数据读取器关闭引起的,所以最好也像这样检查一下...还要在if语句中检查nextresultset,就像我做的那样,这也有助于你... - Pranay Rana
1
谢谢,由于我缺少 {},导致只有第一个 while 被视为 Using 语句的一部分。谢谢,五分钟后将接受您的答案。 - muhammad kashif
1
@muhammadkashif 你能否更新你的问题,展示一下你错误使用了 using …; 结构吗?这样可以澄清问题… - binki
@Pranay Rana,我正在返回4个表格,那么如何使用您的解决方案获取第3个和第4个表格的数据?您能帮我吗? - coderwill

14
我已经尝试重现这个问题(也因为我以前从未在阅读器中使用多个表格)。但是它按预期工作,因此我认为您省略了相关代码。
以下是我的测试代码:
using (var con = new SqlConnection(Properties.Settings.Default.ConnectionString))
{
    using (var cmd = new SqlCommand("SELECT TOP 10 * FROM tabData; SELECT TOP 10 * FROM tabDataDetail;", con))
    {
        int rowCount = 0;
        con.Open();
        using (IDataReader rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
            {
                String object1 = String.Format("Object 1 in Row {0}: '{1}'", ++rowCount, rdr[0]);
            }
            if (rdr.NextResult())
            {
                rowCount = 0;
                while (rdr.Read())
                {
                    String object1 = String.Format("Object 1 in Row {0}: '{1}'", ++rowCount, rdr[0]);
                }
            }
        }
    }
}

谢谢,我的问题已经解决了,我会为你的努力点赞。 - muhammad kashif
如果我想使用 DataTable.Load() 而不是循环读取器,那么第二个表将被填充。对此有任何线索吗? - Biki
3
我明白了。如果我们使用DataTable.Load()方法,那么我们就不需要再使用rdr.NextResult()方法,因为这在隐式中已经被处理了。 - Biki

3

我在 Pranay Rana 的答案基础上进行了改进,因为我喜欢尽可能保持它的简洁。

string rslt = "";
using (SqlDataReader dr = cmd.ExecuteReader())
{
    do
    {
        while (dr.Read())
        {
            rslt += $"ReqID: {dr["REQ_NR"]}, Shpr: {dr["SHPR_NR"]}, MultiLoc: {dr["MULTI_LOC"]}\r\n";
        }
    } while (dr.NextResult());
}

只有当返回的每个表的数据模式相同时,此选项才有效。在OP的问题的情况下,这不起作用,因为第一个结果的模式是具有单个“计数”列的单个记录。而第二个结果是具有多个列和记录的完整表。 - jwatts1980

1
这个问题很老,但我发现答案不正确。这是我的做法:
        List<DataTable> dataTables = new();
        using IDataReader dataReader = command.ExecuteReader();
        do
        {
            DataTable dataTable = new();
            dataTable.Load(dataReader);
            dataTables.Add(dataTable);
        }
        while (!dataReader.IsClosed);

请注意,这是有效的,因为DataTable.Load(reader)方法会自动在读取器上调用“NextResult()”。Load方法会消耗从已加载的IDataReader中获取的第一个结果集,并在成功完成后,如果有任何结果集,则将读取器的位置设置为下一个结果集。 https://learn.microsoft.com/en-us/dotnet/api/system.data.datatable.load?view=net-6.0 - jwatts1980

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