Wie kann ich einzelne Zeilenzahlen wie SSMS erhalten?

8

Ich habe ein Client-C # -Programm, das gespeicherte Prozeduren über ausführt ExectueNonQuery, einschließlich des Abfangens der PRINTund der Fehlerausgabe mit InfoMessage-Ereignissen. Es funktioniert gut, aber ich habe etwas Seltsames bemerkt.

Wenn ich eine gespeicherte Prozedur über SSMS ausführe, werden Zeilenanzahl für jede einzelne SQL-Anweisung angezeigt, die auf der Registerkarte Nachrichten ausgeführt wird (als ob sie von den InfoMessages stammen würde). Mein Programm sieht diese Nachrichten jedoch nie, obwohl es alle anderen Ausgaben abfängt. Stattdessen werden nur die Zeilen zurückgegeben, die im Ergebnis der ExecuteNonQuery-Funktion betroffen sind. Dies ist die Summe aller einzelnen Zeilenzählungen (was irgendwie nutzlos ist).

Zum Beispiel dieses Verfahren:

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

Wenn der fooProzess ausgeführt wird, zeigt SSMS Zeilenanzahl von 662 und 59 an, gibt jedoch ExecuteNonQuerynur die Summe von 721 zurück.

Wie kann ich die gleichen Informationen erhalten, die SSMS erhält?


Um hier klar zu sein: Ich bin nicht daran interessiert, wie die gespeicherten Prozeduren geändert werden, um PRINT @@ROWCOUNTnach jeder SQL-Anweisung s hinzuzufügen . Ich weiß, wie man das macht und es ist aus verschiedenen Gründen meistens keine Option.

Ich frage, wie ich das machen soll, was SSMS hier macht. Ich kann den Client-Code an dieser Stelle nach Belieben ändern (jedenfalls momentan) und möchte es richtig machen.

RBarryYoung
quelle

Antworten:

6

Das SqlCommand.StatementCompletedEreignis wird nach jeder Anweisung in einem Stapel ausgelöst, und eine der Eigenschaften des Ereignisses (also so ziemlich die einzige Eigenschaft) ist die Anzahl der Zeilen, die von der Anweisung betroffen sind, die das Ereignis ausgelöst hat.

Einige Notizen:

  • Eine Anforderung , diese Informationen zu bekommen , ist , dass Sie haben nicht angeben SET NOCOUNT ON;, oder umgekehrt, Sie haben angeben SET NOCOUNT OFF;.
  • Alle Ereignisse werden nach Abschluss jedes Ereignisses ausgelöst Execute___(), nicht während der Ausführung.
  • Die StatementCompletedEventArgs.RecordCountenthält Zeilenanzahl von SELECTAnweisungen, während die SqlDataReader.RecordsAffected Eigenschaft nur Zeilenanzahl von DML - Anweisungen Berichte ( INSERT, UPDATE, DELETE, usw.).
  • Das StatementCompletedEreignis enthält nicht die einzelne SQL-Anweisung aus dem Stapel, der das Ereignis ausgelöst hat. Der Ereignishandler wird jedoch senderals Eingabeparameter gesendet, und dies ist der SqlCommandAbfrage-Batch. Sie können diesen Batch anzeigen sender, SqlCommandindem Sie ihn in die CommandTextEigenschaft umwandeln und dann betrachten (dies wird im folgenden Beispiel gezeigt).

Die Dokumentation ist sehr spärlich auf diese so habe ich ein Beispiel aufgearbeitet , das zeigt dieses Ereignis für beide Brennen ExecuteNonQueryund ExecuteScalarsowie sowohl für Ad - hoc - Abfragen und Stored Procedures (dh SqlCommand.CommandTypevon 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();
        }
    }
}

AUSGABE:

Abfragestapel:
SET NOCOUNT OFF; - stellt sicher, dass das Ereignis 'StatementCompleted' ausgelöst wird

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

SELECT * FROM sys.tables; ');

SELECT * FROM sys.objects;

Betroffene Zeile (n): 453

Abfragestapel:
SELECT 123 AS [Bob];

WAITFOR DELAY '00: 00: 05.000 '; - 5 Sekunden Pause

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Betroffene Zeile (n): 1

Abfragestapel:
SELECT 123 AS [Bob];

WAITFOR DELAY '00: 00: 05.000 '; - 5 Sekunden Pause

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Betroffene Zeile (n): 2

123

Abfragestapel: #TestProc Betroffene
Zeile (n): 453

Abfragestapel: #TestProc Betroffene
Zeile (n): 17

Solomon Rutzky
quelle
1
Ich habe es versucht und es funktioniert für mich. Seltsamerweise scheinen die StatementCompletions und die InfoMessages von PRINT-Anweisungen in den gespeicherten Prozeduren nicht miteinander synchronisiert zu sein (ich erhalte eine Reihe von StatementCompletions, dann eine Reihe von PRINT-Anweisungsausgaben, obwohl sie verschachtelt sein sollen) Ich denke, das ist ein SSMS-Trick für einen anderen Tag ...
RBarryYoung
1
@RBarryYoung Ja, dieses Verhalten ist meiner Meinung nach zu erwarten, auch wenn es nervt. Dies hängt mit der Reihenfolge der Elemente im TDS (Tabular Data Stream) zusammen: msdn.microsoft.com/en-us/library/dd304523.aspx . Ich weiß, dass die PRINTund RAISERROR(..., 10, 1)-Nachrichten alle nach den Ergebnismengen kommen. Ich versuche, die Reihenfolge der Nachrichten in dieser Dokumentation zu finden, bin aber bisher nicht darauf gestoßen.
Solomon Rutzky
Das Rätsel für mich ist, wie SSMS es richtig aussortiert.
RBarryYoung
1
@RBarryYoung Vielleicht sollte dies eine separate Frage sein, da es sich bei dieser Frage nur um Zeilenzählungen aus einzelnen Abfragen handelte? Es ist eine gute Frage, und ich habe es herausgefunden :). Ich werde es als Frage posten, wenn ich die Gelegenheit dazu bekomme, bevor Sie dazu kommen.
Solomon Rutzky
1
@RBarryYoung Yikes. Tut mir leid, das zu hören. Ich hoffe, Sie sind auf dem Weg der Besserung. Ich werde versuchen, es in den nächsten Tagen zu erreichen. Ich habe nur noch ein oder zwei Variationen zum Testen, an die ich gedacht habe, nachdem ich diese letzte Nachricht gepostet habe. Ich werde den Link dazu hier posten.
Solomon Rutzky
-1

Das Executenonquery-Ergebnis wird hier einfach nicht das tun, was Sie wollen. Aber Sie können immer noch dorthin gelangen, es hängt nur davon ab, wofür Sie die Informationen verwenden möchten.

Sie können diese Zeile nach jedem Einfügen von "PRINT @@ ROWCOUNT" hinzufügen und Sie sollten die Anzahl der Zeilen, die von der vorherigen Operation betroffen sind, als Teil der Ausgabe erhalten (wobei Sie "bar" erhalten.

Alternativ können Sie Ihrer gespeicherten Prozedur einen "OUTPUT" -Parameter hinzufügen, um die Ergebnisse zu speichern, und diesen dann einfach erfassen, wenn Sie die executenonquery ausführen.

BEARBEITEN:

Es ist mir gelungen, das von Jonathan Kehasias zusammengestellte Beispiel so zu ändern, dass es die Behandlung mit abgeschlossenen Anweisungen enthält. Fügen Sie einfach diese beiden Zeilen hinzu.

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

#Attach handler...
$cmd.add_StatementCompleted($statementhandler)
Jonathan Fite
quelle
Ich kann diese Verfahren nicht ändern. Ich kann den Client-Code ändern, einschließlich der Verwendung von etwas anderem als ExecuteNonQuery.
RBarryYoung
Sie können versuchen, einen Ereignishandler an das Ereignis sqlcommand infomessage anzuhängen. Dieser Artikel zeigt, wie es mit Powershell geht. sqlskills.com/blogs/jonathan/…
Jonathan Fite
Wenn Sie meine Frage gelesen hätten, hätten Sie gesehen, dass ich das bereits tue. Es ist nicht da drin.
RBarryYoung
1
Diese Frage im C # -Bereich besagt, dass das Hinzufügen eines Listeners zum SQLCommand.StatementCompleted-Ereignis ihnen das gab, wonach sie suchten. stackoverflow.com/questions/27993049/…
Jonathan Fite