从SqlDataReader中获取结果集数量

4
我是一位有用的助手,可以为您翻译文本。下面是需要翻译的内容:

我有一个返回多个结果的SQL Server存储过程。存储过程的主体可能如下所示:

SELECT * FROM tableA;
SELECT * FROM tableB;
SELECT * FROM tableC;

在这种情况下,存储过程会返回3个结果集。其他存储过程可能会返回1、0或任意数量的结果集。每个结果集中可能包含0个或多个行。在加载这些结果时,我需要调用IDataReader.NextResult()来在结果集之间导航。
在C#中,如何可靠地获取结果集的计数(而不是行数)?

你的意思是什么,是结果集的数量还是每行的计数? - Tim Schmelter
像他说的那样,支持从所有这些选择中计算结果的数量。 - butterbox
1
我指的是结果集的数量(树上方)。每个集合可以包含一些行。 - isxaker
2
我认为没有办法计算结果集的数量 - 您只需枚举它们(使用 reader.GetNextResult())并处理它们。您无法预先确定要处理多少个结果集,但必须逐个处理。 - marc_s
1
只是为了扩展marc_s的评论。我会假设将读者移到结果之后,我将无法返回 - 因此我无法以动态方式可靠地提前知道。即使今天它工作,明天我也可以期望实现更改,它会破坏我的假设或使其工作的黑客。 - Sql Surfer
3个回答

11

IDataReader中,似乎没有直接计算结果数量的属性或方法。该接口的意图是以增量/流式方式消耗数据,所以要计算返回的结果集数,请在每次调用IDataReader.NextResult()并且它返回true时递增计数器。

但是需要注意的是,IDataReader.NextResult()文档中指出:

默认情况下,数据读取器定位在第一个结果上。

考虑以下情况:

  • 命令未返回任何结果集。你第一次调用IDataReader.NextResult()将返回false
  • 命令返回1个结果集。你第一次调用IDataReader.NextResult()将返回false
  • 命令返回2个结果集。你第二次调用IDataReader.NextResult()将返回false

可以看出,只要有一个结果集,我们就有足够的信息来计算结果集的数量。这将是IDataReader.NextResult()返回true的次数加上1。

为了检测是否存在0个结果集,我们使用另一个读取器属性:IDataRecord.FieldCount。该属性的文档说明如下:

当未定位在有效记录集中时,返回0;否则,返回当前记录中的列数。默认值为-1。

因此,在首次打开读取器时,我们可以读取该字段以确定是否处于有效的结果集中。如果命令没有生成结果集,则读取器上的IDataRecord.FieldCount值最初将小于1。如果命令至少生成一个结果集,则该值最初将为正数。这假设结果集不可能具有0列(我认为你可以在SQL中假设)。

因此,我将使用类似以下内容来计算结果集的数量。如果您还需要保存数据,则必须将逻辑插入其中:

using (var reader = command.ExecuteReader())
{
    var resultCount = 0;
    do
    {
        if (reader.FieldCount > 0)
            resultCount++;

        while (reader.Read())
        {
            // Insert logic to actually consume data here…
            // HandleRecordByResultIndex(resultCount - 1, (IDataRecord)reader);
        }
    } while (reader.NextResult());
}
我已经使用了System.Data.SqlClient和命令PRINT 'hi'(0个结果集),SELECT 1 x WHERE 1=0(1个结果集)以及SELECT 1 x WHERE 1=0; SELECT 1 x WHERE 1=0(2个结果集)进行了测试。

1
根据情况,我有时会使用SqlDataReader.HasRows而不是FieldCount(我可能比FieldCount更经常使用HasRows)。但是,是的,do {...} while()比接受的答案中显示的复杂且不够灵活的方法要好得多(也更清晰)。并且正确的是,结果集必须至少有一列。 - Solomon Rutzky
@srutzky 我写的代码可能永远不会超出 System.Data.SqlClient,而且我没有对原始 T-SQL 进行抽象,但我仍然觉得尝试限制自己使用可用接口很有趣(除了利用 TPL 兼容的 Async() 变体,我使用 DbCommand)。FieldCount 确实感觉像是 hacky 的,但至少我可以获得“纯”的接口式代码;-) - binki
这是我问题的完美答案。谢谢。 - Bumblebee

7
使用DataReader.NextResult来将读取器推进到下一个结果集。
using (var con = new SqlConnection(Properties.Settings.Default.ConnectionString))
{
    using (var cmd = new SqlCommand("SELECT * FROM TableA; SELECT * FROM TableB; SELECT * FROM TableC;", con))
    {
        con.Open();
        using (IDataReader rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
            {
                int firstIntCol = rdr.GetInt32(0); // assuming the first column is of type Int32
                // other fields ...
            }
            if (rdr.NextResult())
            {
                while (rdr.Read())
                {
                    int firstIntCol = rdr.GetInt32(0); // assuming the first column is of type Int32
                    // other fields ...
                }
                if (rdr.NextResult())
                {
                    while (rdr.Read())
                    {
                        int firstIntCol = rdr.GetInt32(0); // assuming the first column is of type Int32
                        // other fields ...
                    }
                }
            }
        }
    }
}

如果(rdr.NextResult())count ++; 你有任何想法吗?方法NextResult()移动sqldatareader的位置。 - isxaker
2
@Mikhail:请解释一下你的问题。你需要每个表行的计数,还是需要结果集的计数,或者你需要每个结果集的行(如上所示)?如果你只需要结果集的计数:int count = 0; while(rdr.NextResult())count++; 就可以了。 - Tim Schmelter
2
好的,如果我使用这种方法,我该如何在计数后返回sqldatareader第一条记录之前的位置? - isxaker
2
@isxaker 重点是你无法这样做。你可以将所有数据读入集合中,比如 List<T>(如果处理多个结果集需要更复杂的结构),然后你既可以计算数据点数,也可以使用数据 - binki

0

除了接受的答案中手动使用 SqlDataReader 方法之外,还有一个需要注意的解决方案是使用 SqlDataAdapterDataSet 以及 DataTable

使用这些类时,整个结果集一次性从服务器检索出来,您可以随意迭代它们。此外,其他几个 .net 类也知道 DataSets 和 DataTables,并且可以直接连接到它们,用于只读或读写数据访问,如果您还设置了 DeleteCommandInsertCommandUpdateCommand 属性。通过这种方式,您可以免费获得修改 DataSet 中数据的能力,然后只需调用 Update() 将本地更改推送到数据库即可。您还可以获得 RowUpdated 事件处理程序,可以使用它自动完成此操作。

DataSetDataTable 还保留了来自数据库模式的元数据,因此您仍然可以按名称或索引访问列。

总的来说,它们是一个不错的功能,但肯定比 SqlDataReader 更重量级。

SqlDataAdapter 的文档在这里:https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldataadapter


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