Comment puis-je obtenir des comptes individuels comme SSMS?


8

J'ai un programme client c # qui exécute des procédures stockées via ExectueNonQuery, y compris la capture de la PRINTsortie et de l'erreur avec les événements InfoMessage. Cela fonctionne bien, mais j'ai remarqué quelque chose d'étrange.

Lorsque j'exécute une procédure stockée à partir de SSMS, elle affiche les nombres de lignes pour chaque instruction SQL individuelle qui est exécutée dans l'onglet Messages (comme si elle provenait des InfoMessages). Cependant, mon programme ne voit jamais ces messages, bien qu'il intercepte tous les mêmes résultats. Au lieu de cela, il renvoie simplement les lignes affectées dans le résultat de la fonction ExecuteNonQuery qui est la somme de tous les décomptes de lignes individuels (ce qui est en quelque sorte inutile).

Par exemple, cette procédure:

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

Lorsque le fooproc est exécuté, SSMS affiche les nombres de lignes de 662 et 59, mais ExecuteNonQueryne renvoie que le total de 721.

Alors, comment puis-je obtenir les mêmes informations que SSMS obtient?


Juste pour être clair ici: je ne suis pas intéressé par la façon de changer les procédures stockées pour ajouter des PRINT @@ROWCOUNTs après chaque instruction SQL. Je sais comment faire et ce n'est pas une option la plupart du temps pour diverses raisons.

Je demande comment faire ce que SSMS fait ici. Je peux changer le code client tout ce que je veux à ce stade (pour le moment en tout cas) et je voudrais le faire correctement.

Réponses:


6

L' SqlCommand.StatementCompletedévénement se déclenchera après chaque instruction d'un lot, et l'une des propriétés de l'événement (enfin, à peu près la seule propriété) est le nombre de lignes affectées par l'instruction qui a déclenché l'événement.

Quelques notes:

  • Une exigence d'obtenir cette information est que vous ne pas spécifié SET NOCOUNT ON;, ou à l' inverse, vous avez spécifié SET NOCOUNT OFF;.
  • Tous les événements se déclenchent à la fin de chacun Execute___(), pas pendant l'exécution.
  • Le StatementCompletedEventArgs.RecordCountinclut nombre de lignes de SELECTcomptes, alors que la SqlDataReader.RecordsAffected propriété ne rapporte que nombre de lignes de DML ( INSERT, UPDATE, DELETE, etc.).
  • L' StatementCompletedévénement n'inclut pas l'instruction SQL individuelle du lot qui a déclenché l'événement. Cependant, le gestionnaire d'événements est envoyé le sendercomme paramètre d'entrée et c'est SqlCommanddu lot de requête, et vous pouvez voir ce lot par coulée senderà SqlCommandet à la recherche puis à la CommandTextpropriété (ce qui est indiqué dans l'exemple ci - dessous).

La documentation est très clairsemée à ce sujet, j'ai donc élaboré un exemple qui montre que cet événement se déclenche pour les deux ExecuteNonQueryet ExecuteScalar, ainsi que pour les requêtes ad hoc et les procédures stockées (c'est SqlCommand.CommandType-à- dire de 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();
        }
    }
}

PRODUCTION:

Lot de requêtes:
OFF NOCOUNT OFF; - garantit que l'événement 'StatementCompleted' se déclenche

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

SELECT * FROM sys.tables; ');

SELECT * FROM sys.objects;

Ligne (s) affectée (s): 453

Lot de requêtes:
SELECT 123 AS [Bob];

ATTENDRE LE RETARD '00: 00: 05.000 '; --5 seconde pause

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Ligne (s) affectée (s): 1

Lot de requêtes:
SELECT 123 AS [Bob];

ATTENDRE LE RETARD '00: 00: 05.000 '; --5 seconde pause

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Ligne (s) affectée (s): 2

123

Lot de requêtes: #TestProc
Ligne (s) affectée (s): 453

Lot de requêtes: #TestProc
Ligne (s) affectée (s): 17


1
J'ai essayé cela et cela fonctionne pour moi. Curieusement, les instructions StatementCompletions et InfoMessages from PRINT dans les procédures stockées ne semblent pas être synchronisées les unes par rapport aux autres (j'obtiens un tas d'instructions StatementCompletions, puis un tas de sorties d'instructions PRINT, bien qu'elles soient supposées être entrelacées).
Je

1
@RBarryYoung Oui, je pense que ce comportement est normal, même s'il est ennuyeux. Cela a à voir avec l'ordre des éléments dans le TDS (Tabular Data Stream): msdn.microsoft.com/en-us/library/dd304523.aspx . Je sais que les messages PRINTet RAISERROR(..., 10, 1)viennent tous après les jeux de résultats. J'essaie de trouver l'ordre des messages dans cette documentation mais jusqu'à présent je ne l'ai pas rencontré.
Solomon Rutzky

Le mystère pour moi est de savoir comment SSMS trie correctement.
RBarryYoung

1
@RBarryYoung Cela devrait peut-être être une question distincte, car celle-ci concernait uniquement le nombre de lignes des requêtes individuelles? C'est une bonne question, et je l'ai compris :). Je le posterai sous forme de question si j'en ai l'occasion avant d'y arriver.
Solomon Rutzky

1
@RBarryYoung Yikes. Désolé, d'entendre celà. J'espère que vous êtes sur la bonne voie. J'essaierai d'y arriver dans les prochains jours. Il ne me reste qu'une ou deux variantes à tester auxquelles j'ai pensé après avoir posté ce dernier message. Je vais poster le lien ici.
Solomon Rutzky

-1

Le résultat de la requête exécutée ne fera tout simplement pas ce que vous voulez ici. Mais vous pouvez toujours y arriver, cela dépend simplement de la raison pour laquelle vous souhaitez utiliser les informations.

Vous pouvez ajouter cette ligne après chaque insertion "PRINT @@ ROWCOUNT" et vous devriez obtenir le nombre de lignes affectées par l'opération précédente dans le cadre de la sortie (où vous obtenez "bar".

Alternativement, vous pouvez ajouter un paramètre "OUTPUT" à votre procédure stockée pour conserver les résultats, puis simplement capturer cela lorsque vous exécutez la requête exécutée.

ÉDITER:

J'ai réussi à modifier l'exemple que Jonathan Kehasias a rassemblé pour inclure la gestion des événements de déclaration terminée . Ajoutez simplement ces deux lignes.

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

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

Je ne peux pas modifier ces procédures. Je peux modifier le code client, y compris en utilisant autre chose que ExecuteNonQuery.
RBarryYoung du

Vous pouvez essayer d'attacher un gestionnaire d'événements à l'événement d'infomessage sqlcommand. Cet article explique comment procéder à l'aide de PowerShell. sqlskills.com/blogs/jonathan/…
Jonathan Fite

Si vous aviez lu ma question, vous auriez vu que je le fais déjà. Ce n'est pas là.
RBarryYoung

1
Cette question dans la zone C # indique que l'ajout d'un écouteur à l'événement SQLCommand.StatementCompleted leur a donné ce qu'ils recherchaient. stackoverflow.com/questions/27993049/…
Jonathan Fite
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.