如何在 C# 中使用 SqlDataReader 获取行数

107
我的问题是如何在C#中使用SqlDataReader获取查询返回的行数。我看到了一些回答,但没有一个清晰地定义了这个问题,除了一个回答建议使用while循环和Read()方法并增加一个计数器来解决。
我的问题是,我正在尝试用多维数组填充数据,第一行是列头名称,之后每一行都是行数据。
我知道我可以把所有东西都倒入列表控件中而不必担心它,但出于自己的兴趣,并且我也想随意将数据存入和提取出来,并以不同的格式显示它。
因此,我认为不能采用Read()然后递增++的方式,因为这意味着我必须打开Read(),再次打开Read()以获取行数和列数据。
以下是一个简单的示例:
int counter = 0;    

while (sqlRead.Read())
{
    //get rows
    counter++
}

然后使用一个for循环遍历列并弹出

something.Read();

int dbFields = sqlRead.FieldCount;

for (int i = 0; i < dbFields; i++)
{
   // do stuff to array
}
6个回答

105

只有两个选项:

  • 通过阅读所有行来找出结果(那么您可能需要将它们存储)

  • 预先运行一个专门的 SELECT COUNT(*) 查询。

两次通过 DataReader 循环非常昂贵,您必须重新执行查询。

而且(感谢 Pete OHanlon)只有在使用带有快照隔离级别的事务时,第二个选项才是并发安全的。

既然您最终想要将所有行存储在内存中,那么唯一明智的选择就是在灵活的存储(List<>DataTable)中读取所有行,然后将数据复制到任何您想要的格式。在内存操作始终更加高效。


5
Henk是正确的:DataReader中没有成员可以让你获取行数,因为它是一个只能向前读取的阅读器。最好先获取计数,然后再执行查询,可能在多结果查询中,这样你只需一次访问数据库。 - flipdoubt
17
专门计数的问题在于,由于其他人更改了数据以导致返回的行数发生变化,可能会导致计数结果与返回行数不同。为此,需要注意专门计数的可靠性。 - Pete OHanlon
1
Pete,你是对的,这需要一个昂贵的IsolationLevel。 - H H
1
谢谢大家!现在情况变得更加清晰了。那么是将所有信息都转储到 DataSet 中好呢,还是先运行 SQL COUNT(*),将结果存储起来,然后再运行所需的查询呢?或者我们是在讨论运行计数并将所有内容存储在 DataSet 中吗? - Tomasz Iniewicz
4
“RepeatableRead” 隔离级别不执行范围锁定,因此仍允许插入记录,您需要使用“Snapshot”或“Serializable”隔离级别。 - Lukazoid
显示剩余3条评论

10

如果您不需要检索所有行,并且想避免进行双重查询,您可以尝试类似以下的操作:

using (var sqlCon = new SqlConnection("Server=127.0.0.1;Database=MyDb;User Id=Me;Password=glop;"))
      {
        sqlCon.Open();

        var com = sqlCon.CreateCommand();
        com.CommandText = "select * from BigTable";
        using (var reader = com.ExecuteReader())
        {
            //here you retrieve what you need
        }

        com.CommandText = "select @@ROWCOUNT";
        var totalRow = com.ExecuteScalar();

        sqlCon.Close();
      }

你可能需要添加一个事务,不确定是否重用相同的命令会自动在其中添加一个事务...


1
任何人都可以说一下@@ROWCOUNT是否总是依赖于上面运行的最后一个查询吗?如果有许多连接并行运行查询会出现问题吗? - YvesR
1
需要执行 sqlCon.Close(); 吗?我认为 using 应该会自动关闭连接。 - bluish
2
在从读取器检索数据之前需要行计数时,此方法将无法正常运行。 - Heemanshu Bhalla

8
根据上述,数据集或类型化数据集可能是一个很好的临时结构,您可以使用它来进行过滤。SqlDataReader旨在快速读取数据。当您在while()循环中时,仍然连接到数据库,并且它正在等待您执行任何操作以便在继续之前读取/处理下一个结果。在这种情况下,如果您将所有数据都拉入并关闭与数据库的连接,然后“离线”处理结果,可能会获得更好的性能。
人们似乎讨厌数据集,因此以上内容也可以使用一组强类型对象完成。

2
我自己喜欢DataSets,因为它们是一种精心编写且极其有用的基于表格数据的通用表示方式。有趣的是,我注意到大多数避免使用DataSet的ORM用户都试图编写尽可能通用的自己的代码(通常是毫无意义的)。 - MusiGenesis
5
丹尼尔,“above”不是引用另一个答案的好方法。 - H H

7
您无法直接从数据读取器中获取行数,因为它是所谓的“firehose cursor” - 这意味着基于执行的读取,数据是逐行读取的。我建议不要对数据进行2次读取,因为在进行2次读取之间可能会更改数据,因此会得到不同的结果。
您可以将数据读取到临时结构中,并将其用于替代第二次读取。或者,您需要更改检索数据的机制,并使用类似DataTable的东西。

7

为了完成Pit答案并获得更好的性能: 在一个查询中获取所有内容,并使用NextResult方法。

using (var sqlCon = new SqlConnection("Server=127.0.0.1;Database=MyDb;User Id=Me;Password=glop;"))
{
    sqlCon.Open();
    var com = sqlCon.CreateCommand();
    com.CommandText = "select * from BigTable;select @@ROWCOUNT;";
    using (var reader = com.ExecuteReader())
    {
        while(reader.Read()){
            //iterate code
        }
        int totalRow = 0 ;
        reader.NextResult();
        if(reader.Read()){
            totalRow = (int)reader[0];
        }
    }
    sqlCon.Close();
}

1

我也遇到了这样的情况,需要返回一个顶部结果,但也想获取与查询匹配的总行数。最终我找到了这个解决方案:

   public string Format(SelectQuery selectQuery)
    {
      string result;

      if (string.IsNullOrWhiteSpace(selectQuery.WherePart))
      {
        result = string.Format(
@"
declare @maxResult  int;
set @maxResult = {0};

WITH Total AS
(
SELECT count(*) as [Count] FROM {2}
)
SELECT top (@maxResult) Total.[Count], {1} FROM Total, {2}", m_limit.To, selectQuery.SelectPart, selectQuery.FromPart);
      }
      else
      {
        result = string.Format(
@"
declare @maxResult  int;
set @maxResult = {0};

WITH Total AS
(
SELECT count(*) as [Count] FROM {2} WHERE {3}
)
SELECT top (@maxResult) Total.[Count], {1} FROM Total, {2} WHERE {3}", m_limit.To, selectQuery.SelectPart, selectQuery.FromPart, selectQuery.WherePart);
      }

      if (!string.IsNullOrWhiteSpace(selectQuery.OrderPart))
        result = string.Format("{0} ORDER BY {1}", result, selectQuery.OrderPart);

      return result;
    }

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