Como posso obter contas de linha individuais como o SSMS?

8

Eu tenho um programa cliente c # que está executando procedimentos armazenados via ExectueNonQuery, incluindo a captura da PRINTsaída e Error com eventos do InfoMessage. Funciona bem, mas notei algo estranho.

Quando executo um procedimento armazenado do SSMS, ele exibe contagens de linhas para cada instrução SQL individual que é executada na guia Mensagens (como se viesse do InfoMessages). No entanto, meu programa nunca vê essas mensagens, apesar de capturar todas as mesmas saídas. Em vez disso, ele retorna as linhas afetadas no resultado da função ExecuteNonQuery que é a soma de todas as contas de linha individuais (o que é meio inútil).

Por exemplo, este procedimento:

use [tempdb]
go

SELECT  * 
INTO    MyCols
FROM    sys.columns
go

CREATE PROC foo As

    UPDATE  MyCols
    SET     name = name + N''
-- SSMS shows (662 row(s) affected)

    UPDATE  MyCols
    SET     name = name + N''
    WHERE   name like '%x%'
-- SSMS shows (59 row(s) affected)

PRINT 'bar'
-- both SSMS and ExecuteNonQuery get this

-- ExecuteNonQuery returns 721 rows affected
GO

Quando o processo fooé executado, o SSMS exibe contagens de linhas de 662 e 59, mas ExecuteNonQueryretorna apenas o total de 721.

Então, como posso obter as mesmas informações que o SSMS está recebendo?


Só para esclarecer aqui: não estou interessado em como alterar os procedimentos armazenados para adicionar PRINT @@ROWCOUNTs após cada instrução SQL. Eu sei como fazer isso e não é uma opção na maioria das vezes por vários motivos.

Estou perguntando como fazer o que o SSMS está fazendo aqui. Eu posso alterar o código do cliente tudo o que eu quero neste momento (por enquanto, de qualquer maneira) e gostaria de fazê-lo corretamente.

RBarryYoung
fonte

Respostas:

6

O SqlCommand.StatementCompletedevento será acionado após cada instrução em um lote, e uma das propriedades do evento (bem, praticamente a única propriedade) é o número de linhas afetadas pela instrução que disparou o evento.

Algumas notas:

  • A exigência de obter esta informação é que você fez não especificar SET NOCOUNT ON;, ou, inversamente, você se especificar SET NOCOUNT OFF;.
  • Todos os eventos são disparados na conclusão de cada um Execute___(), não durante a execução.
  • O StatementCompletedEventArgs.RecordCountinclui contagens de linha de SELECTdeclarações, enquanto o SqlDataReader.RecordsAffected propriedade somente relatórios contagens de linha de DML ( INSERT, UPDATE, DELETE, etc).
  • O StatementCompletedevento não inclui a instrução SQL individual do lote que disparou o evento. No entanto, o manipulador de evento é enviado o sendercomo um parâmetro de entrada e esta é a SqlCommanddo lote de consulta, e você pode ver que lote lançando senderpara SqlCommande, em seguida, olhando para a CommandTextpropriedade (isso é mostrado no exemplo abaixo).

A documentação é muito esparsa, por isso trabalhei em um exemplo que mostra esse evento disparando para ambos ExecuteNonQuerye ExecuteScalar, assim como para consultas ad hoc e procedimentos armazenados (ou seja, SqlCommand.CommandTypede Textvs StoredProcedure):

using System;
using System.Data;
using System.Data.SqlClient;

namespace StatementCompletedFiring
{
    class Program
    {
        static void Main(string[] args)
        {
            using (SqlConnection _Connection =
                          new SqlConnection("Integrated Security = True;"))
            {
                using (SqlCommand _Command = new SqlCommand(@"
SET NOCOUNT OFF; --  ensures that the 'StatementCompleted' event fires

EXEC('
CREATE PROCEDURE #TestProc
AS
SELECT * FROM sys.objects;

SELECT * FROM sys.tables;
');

SELECT * FROM sys.objects;
", _Connection))
                {

                    _Command.StatementCompleted += _Command_StatementCompleted;

                    try
                    {
                        _Connection.Open();

                        _Command.ExecuteNonQuery();

                        _Command.CommandText = @"
SELECT 123 AS [Bob];

WAITFOR DELAY '00:00:05.000'; --5 second pause to shows when the events fire

SELECT 2 AS [Sally]
UNION ALL
SELECT 5;
";
                        Console.WriteLine("\n\t");
                        Console.WriteLine(_Command.ExecuteScalar().ToString());
                        Console.WriteLine("\n");


                        _Command.CommandType = CommandType.StoredProcedure;
                        _Command.CommandText = "#TestProc";
                        _Command.ExecuteNonQuery();
                    }
                    catch (Exception _Exception)
                    {
                        throw new Exception(_Exception.Message);
                    }
                }
            }
        }

        static void _Command_StatementCompleted(object sender,
                                                StatementCompletedEventArgs e)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Write("\nQuery Batch: ");
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine(((SqlCommand)sender).CommandText);

            Console.ForegroundColor = ConsoleColor.Red;
            Console.Write("Row(s) affected: ");
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine(e.RecordCount.ToString() + "\n");

            Console.ResetColor();
        }
    }
}

RESULTADO:

Lote de consulta:
DESLIGAR NOCOUNT; - garante que o evento 'StatementCompleted' seja acionado

EXEC ('CREATE PROCEDURE #TestProc AS SELECT * FROM sys.objects;

SELECT * FROM sys.tables; ');

SELECT * FROM sys.objects;

Linha (s) afetada (s): 453

Lote de consulta:
SELECT 123 AS [Bob];

WAITFOR ATLAY '00: 00: 05.000 '; --5 segundos de pausa

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Linha (s) afetada (s): 1

Lote de consulta:
SELECT 123 AS [Bob];

WAITFOR ATLAY '00: 00: 05.000 '; --5 segundos de pausa

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Linha (s) afetada (s): 2

123

Lote de consulta: #TestProc Linhas
afetadas: 453

Lote de consulta: #TestProc Linhas
afetadas: 17

Solomon Rutzky
fonte
11
Eu tentei isso e funciona para mim. Estranhamente, as declarações StatementCompletions e InfoMessages de PRINT nos procedimentos armazenados não parecem ser sincronizadas umas com as outras (eu recebo um monte de StatementCompletions, depois um monte de saídas de instrução PRINT, embora devam ser intercaladas). acho que é um truque SSMS para outro dia ...
RBarryYoung
11
@RBarryYoung Sim, esse comportamento, acredito que é de se esperar, mesmo que irritante. Tem a ver com a ordem dos itens no TDS (Tabular Data Stream): msdn.microsoft.com/en-us/library/dd304523.aspx . Eu sei que todas as mensagens PRINTe RAISERROR(..., 10, 1)vêm após os conjuntos de resultados. Estou tentando encontrar a mensagem solicitada nessa documentação, mas até agora não a encontrei.
Solomon Rutzky
O mistério para mim é como o SSMS classifica isso corretamente.
usar o seguinte código
11
@RBarryYoung Talvez essa deva ser uma pergunta separada, pois essa era apenas a contagem de linhas de consultas individuais? É uma boa pergunta, e eu descobri isso :). Vou publicá-lo como uma pergunta se tiver a chance de fazê-lo antes de você chegar a ele.
Solomon Rutzky
11
@RBarryYoung Yikes. Desculpe ouvir sobre isso. Espero que você esteja se recuperando. Vou tentar chegar lá dentro dos próximos dias. Só tenho uma ou duas variações para testar, depois de postar a última mensagem. Vou postar o link aqui.
Solomon Rutzky
-1

O resultado da execução da consulta simplesmente não fará o que você deseja aqui. Mas você ainda pode chegar lá, depende apenas do motivo pelo qual deseja usar as informações.

Você pode adicionar esta linha após cada inserção "PRINT @@ ROWCOUNT" e deve obter o número de linhas afetadas pela operação anterior como parte da saída (onde você obtém "barra".

Como alternativa, você pode adicionar um parâmetro "OUTPUT" ao seu procedimento armazenado para manter os resultados e capturá-lo quando você executa a consulta de execução.

EDITAR:

Consegui modificar o exemplo que Jonathan Kehasias reuniu para incluir a manipulação de eventos concluídos da instrução. Basta adicionar essas duas linhas.

#Add handler for StatementCompleted
$statementhandler = {param($sender, [System.Data.StatementCompletedEventArgs]$event) Write-Host $event.RecordCount };

#Attach handler...
$cmd.add_StatementCompleted($statementhandler)
Jonathan Fite
fonte
Não consigo modificar esses procedimentos. Posso modificar o código do cliente, inclusive usando algo diferente de ExecuteNonQuery.
RBarryYoung
Você pode tentar anexar um manipulador de eventos ao evento infomessage sqlcommand. Este artigo mostra como fazer isso usando o PowerShell. sqlskills.com/blogs/jonathan/…
Jonathan Fite
Se você tivesse lido minha pergunta, teria visto que eu já estou fazendo isso. É não lá.
RBarryYoung
11
Esta pergunta na área de C # diz que adicionar um ouvinte ao evento SQLCommand.StatementCompleted deu a eles o que estavam procurando. stackoverflow.com/questions/27993049/…
Jonathan Fite