在读取行

时间:2018-03-23 07:34:04

标签: c# tsql

我的基本要求是在开始循环生成的行之前知道存储过程将输出的总行数。这是在数据导出中使用,因此当我遍历行时​​,我将把它们写入文件。我想首先获得总行数的原因是我可以向用户反馈到目前为止导出的程度 - 例如“已导出100个行中的100个”。

存储过程包装在事务中,除了选择要写入文件的结果外,还执行一些插入操作。这个想法是多个出口永远不应该包含相同的记录。这可能不是特别相关,但我提到它以防万一。

为了解决这个问题,我将总行数放入输出参数中。但是,当我尝试在读取器执行和关闭之前读取c#中的输出参数时,它会给我一个异常 - object reference not set to an instance of an object

以下是我的SP的简化版本:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[test]
    @TotalRows INT OUTPUT
AS
BEGIN
    SET NOCOUNT ON
    BEGIN TRANSACTION

    -- do inserts
    -- rollback if there were errors

    -- select the data for the export
    SELECT ... 
    FROM ...
    WHERE ...

    UNION

    SELECT ...
    FROM ...
    WHERE ...
    GROUP BY ...

    -- get the total rows (this works fine)
    SELECT @TotalRows = @@ROWCOUNT

    -- do some updates using the previous select results

    -- rollback if there were errors

    COMMIT TRANSACTION
END

在编写之前尝试获取总数的代码:

int totalRows = 0;
try
{
    using (SqlConnection db = new SqlConnection(CONNSTRING))
    using (SqlCommand cmd = new SqlCommand(SPName, db))
    {
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.Add("@TotalRows", SqlDbType.Int).Direction = ParameterDirection.Output;
        db.Open();
        using (reader = cmd.ExecuteReader())
        {
            totalRows = (int)cmd.Parameters["@TotalRows"].Value; // throws an exception

            int rowsProcessed = 0;
            while (reader.Read())
            {
                rowsProcessed++;
                // write export records to file

                // keep a log of rowsProcessed
            }
        }
        // if i retrieve cmd.Parameters["@TotalRows"].Value here then it works ok, but of course by this stage its too late
    }
}
catch (Exception ex)
{
    // handle the exception
}

有人可以提出解决方案吗?我不想在SP中运行两个查询 - 对于初学者来说因为效率低,但主要是因为这都是在事务中完成的,因此查询可能不会给出相同的结果。

2 个答案:

答案 0 :(得分:1)

如果它是一个大查询,当您开始使用其结果时,查询本身仍在运行(如果客户端上没有足够的空间来缓冲所有的结果) 1

存储过程仍在执行此查询,因此尚未继续将rowcount分配给输出参数。

可能在结果集中包含COUNT(*) OVER ()列,但要注意此经常会产生较差的查询计划,尤其是在处理大型查询时。

另外,正如Ezequiel在评论中建议的那样,你可以先将结果转储到某个形式的临时表中,这样你就可以获得计数 - 但要再次注意,因为这是一个大问题,你&# 39;为了能够获得这个数量而消耗大量资源。

1 每个SELECT查询在技术上都被游标使用。默认情况下,SQL Server使用" firehose"尝试尽快将结果发送到客户端的游标。但是,如果客户端缓冲区已满,那么根据定义,游标必须暂停检索结果,直到客户端实际使用它们中的一些来释放缓冲区空间。

答案 1 :(得分:-1)

可能无法解决您的问题,但请尝试更改:

SELECT @TotalRows = @@ROWCOUNT

SET @TotalRows = @@ROWCOUNT