Come posso ottenere singoli conteggi di riga come SSMS?


8

Ho un programma client c # che sta eseguendo procedure memorizzate tramite ExectueNonQuery, incluso l' PRINToutput di Error ed Error con eventi InfoMessage. Funziona bene, ma ho notato qualcosa di strano.

Quando eseguo una procedura memorizzata da SSMS, vengono visualizzati i conteggi delle righe per ogni singola istruzione SQL eseguita nella scheda Messaggi (come se provenisse dagli InfoMessage). Comunque il mio programma non vede mai questi messaggi, sebbene catturi tutti gli stessi altri output. Al contrario, restituisce solo le righe interessate dal risultato della funzione ExecuteNonQuery che è la somma di tutti i singoli conteggi delle righe (che è piuttosto inutile).

Ad esempio, questa procedura:

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 fooviene eseguito il proc, SSMS visualizza i conteggi delle righe di 662 e 59, ma ExecuteNonQueryrestituisce solo il totale di 721.

Quindi, come posso ottenere le stesse informazioni che SSMS sta ricevendo?


Giusto per essere chiari qui: non sono interessato a come modificare le stored procedure per aggiungere PRINT @@ROWCOUNTs dopo ogni istruzione SQL. So come farlo e non è un'opzione per la maggior parte del tempo per una serie di motivi.

Sto chiedendo come fare ciò che SSMS sta facendo qui. Posso cambiare il codice client tutto quello che voglio a questo punto (per ora, comunque) e vorrei farlo nel modo giusto.

Risposte:


6

L' SqlCommand.StatementCompletedevento verrà generato dopo ogni istruzione in un batch e una delle proprietà dell'evento (beh, praticamente l'unica proprietà) è il numero di righe interessate dall'istruzione che ha generato l'evento.

Alcune note:

  • Un requisito per ottenere queste informazioni è che non è stato specificato SET NOCOUNT ON;o, al contrario, è stato specificato SET NOCOUNT OFF;.
  • Tutti gli eventi si attivano al completamento di ciascuno Execute___(), non durante l'esecuzione.
  • Il StatementCompletedEventArgs.RecordCountinclude conteggi delle righe da SELECTdichiarazioni, mentre la SqlDataReader.RecordsAffected proprietà riporta solo conteggi delle righe da DML dichiarazioni ( INSERT, UPDATE, DELETE, ecc).
  • L' StatementCompletedevento non include la singola istruzione SQL dal batch che ha generato l'evento. Tuttavia, il gestore di eventi viene inviato il sendercome parametro di input e questo è il SqlCommanddel batch di query, e si può vedere che lotto per colata sendera SqlCommande poi guardando la CommandTextproprietà (questo è mostrato nell'esempio qui sotto).

La documentazione è molto scarsa su questo, quindi ho elaborato un esempio che mostra l'attivazione di questo evento per entrambi ExecuteNonQuerye ExecuteScalar, nonché per le query ad hoc e le procedure memorizzate (cioè SqlCommand.CommandTypedi 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();
        }
    }
}

PRODUZIONE:

Batch query:
SET NOCOUNT OFF; - assicura che l'evento "StatementCompleted" venga generato

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

SELEZIONA * DA sys.tables; ');

SELEZIONA * DA sys.objects;

Fila interessata: 453

Query Batch:
SELECT 123 AS [Bob];

RITARDO ATTESA '00: 00: 05.000 '; - 5 secondi di pausa

SELEZIONA 2 COME [Sally] UNION ALL SELECT 5;

Righe interessate: 1

Query Batch:
SELECT 123 AS [Bob];

RITARDO ATTESA '00: 00: 05.000 '; - 5 secondi di pausa

SELEZIONA 2 COME [Sally] UNION ALL SELECT 5;

Righe interessate: 2

123

Lotto query: #TestProc Righe
interessate: 453

Lotto query: #TestProc Righe
interessate: 17


1
Ho provato questo e funziona per me. Stranamente, le istruzioni StatementCompletions e InfoMessages dalle istruzioni PRINT nelle procedure memorizzate non sembrano essere sincronizzate l'una con l'altra (ottengo un sacco di StatementCompletions, quindi un sacco di output dell'istruzione PRINT, anche se si suppone che siano intercalati) Ma io suppongo che sia un trucco SSMS per un altro giorno ...
RBarryYoung

1
@RBarryYoung Sì, quel comportamento, credo sia prevedibile, anche se fastidioso. Ha a che fare con l'ordine degli elementi nel TDS (Tabular Data Stream): msdn.microsoft.com/en-us/library/dd304523.aspx . So che i messaggi PRINTe RAISERROR(..., 10, 1)vengono tutti dopo il set di risultati. Sto cercando di trovare il messaggio che ordina in quella documentazione ma finora non l'ho trovato.
Solomon Rutzky,

Il mistero per me è come SSMS lo risolve correttamente.
RBarryYoung,

1
@RBarryYoung Forse questa dovrebbe essere una domanda separata poiché questa riguardava solo il conteggio delle righe delle singole query? È una buona domanda e l'ho capito :). Lo posterò come una domanda se avrò la possibilità di farlo prima che tu ci arrivi.
Solomon Rutzky,

1
@RBarryYoung Yikes. Mi dispiace sentirlo. Spero che tu sia in via di guarigione. Proverò ad arrivarci entro i prossimi giorni. Mi restano solo una o due varianti da testare a cui ho pensato dopo aver pubblicato l'ultimo messaggio. Pubblicherò il link qui.
Solomon Rutzky,

-1

Il risultato di execenonquery non farà proprio quello che vuoi qui. Ma puoi ancora arrivarci, dipende solo da cosa vuoi usare le informazioni.

È possibile aggiungere questa riga dopo ogni inserimento "STAMPA @@ ROWCOUNT" e si dovrebbe ottenere il numero di righe interessate dall'operazione precedente come parte dell'output (dove si ottiene la "barra".

In alternativa, è possibile aggiungere un parametro "OUTPUT" alla procedura memorizzata per conservare i risultati e quindi catturarlo quando si esegue la query di esecuzione.

MODIFICARE:

Sono riuscito a modificare l'esempio che Jonathan Kehasias ha messo insieme per includere la gestione degli eventi completata da istruzioni. Aggiungi queste due righe.

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

#Attach handler...
$cmd.add_StatementCompleted($statementhandler)

Non posso modificare queste procedure. Posso modificare il codice client, incluso l'utilizzo di qualcosa di diverso da ExecuteNonQuery.
RBarryYoung,

Potresti provare ad associare un gestore eventi all'evento infomessage sqlcommand. Questo articolo mostra come farlo usando PowerShell. sqlskills.com/blogs/jonathan/…
Jonathan Fite,

Se avessi letto la mia domanda, avresti visto che lo sto già facendo. E ' non è in là.
RBarryYoung,

1
Questa domanda nell'area C # afferma che l'aggiunta di un listener all'evento SQLCommand.StatementCompleted ha dato loro quello che stavano cercando. stackoverflow.com/questions/27993049/…
Jonathan Fite,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.