Как я могу получить отдельные учетные записи, такие как SSMS?

8

У меня есть клиентская программа на c #, которая выполняет хранимые процедуры через ExectueNonQuery, включая PRINTперехват и вывод ошибок с событиями InfoMessage. Работает нормально, но я заметил кое-что странное.

Когда я выполняю хранимую процедуру из SSMS, она отображает количество строк для каждого отдельного оператора SQL, который выполняется на вкладке «Сообщения» (как будто он поступает из InfoMessages). Тем не менее, моя программа никогда не видит эти сообщения, хотя она получает все те же другие выходные данные. Вместо этого он просто возвращает строки, затронутые в результате выполнения функции ExecuteNonQuery, который представляет собой сумму всех отдельных рядов (что является бесполезным).

Например, эта процедура:

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

Когда процедура fooзапущена, SSMS отображает количество строк 662 и 59, но ExecuteNonQueryвозвращает только 721.

Итак, как я могу получить ту же информацию, что и SSMS?


Просто чтобы прояснить это: меня не интересует, как изменить хранимые процедуры для добавления PRINT @@ROWCOUNTs после каждого оператора SQL. Я знаю, как это сделать, и это не вариант в большинстве случаев по разным причинам.

Я спрашиваю, как сделать то, что SSMS делает здесь. Я могу изменить код клиента все, что я хочу на данный момент (на данный момент, во всяком случае), и я хотел бы сделать это правильно.

RBarryYoung
источник

Ответы:

6

SqlCommand.StatementCompletedСобытие будет срабатывать после каждого оператора в пакете, и один из свойств события (ну, почти единственное свойство) есть число строк , затронутых в заявлении , что обожженное событие.

Некоторые заметки:

  • Требование о получении этой информации является то , что вы ничего не указать SET NOCOUNT ON;, или , наоборот, вы же указать SET NOCOUNT OFF;.
  • Все события запускаются при завершении каждого Execute___(), а не во время выполнения.
  • StatementCompletedEventArgs.RecordCountВключает в себя число строк из SELECTутверждений, в то время как SqlDataReader.RecordsAffected свойство сообщает только число строк из DML отчетности ( INSERT, UPDATE, DELETEи т.д.).
  • StatementCompletedСобытие вовсе не включать отдельные инструкции SQL из партии , что обожженного событие. Тем не менее, обработчик событий отправляется senderкак входной параметр, и это SqlCommandпакет запроса, и вы можете увидеть этот пакет, приведя его senderк свойству, SqlCommandа затем просмотрев его CommandText(это показано в примере ниже).

Документация очень скудны на это , так что я работал на пример , который показывает это событие обжиг для обоих ExecuteNonQueryи ExecuteScalar, а также как для специальных запросов и хранимых процедур (т.е. SqlCommand.CommandTypeиз Textпротив 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();
        }
    }
}

ВЫВОД:

Пакет запроса:
ВЫКЛЮЧИТЕ NOCOUNT; - обеспечивает запуск события StatementCompleted

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

SELECT * FROM sys.tables; «);

SELECT * FROM sys.objects;

Пострадали строки: 453

Пакет запросов:
ВЫБЕРИТЕ 123 AS [Bob];

ЗАДЕРЖКА ОЖИДАНИЯ '00: 00: 05.000 '; - 5 секундная пауза

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Пострадали строки: 1

Пакет запросов:
ВЫБЕРИТЕ 123 AS [Bob];

ЗАДЕРЖКА ОЖИДАНИЯ '00: 00: 05.000 '; - 5 секундная пауза

SELECT 2 AS [Sally] UNION ALL SELECT 5;

Пострадали строки: 2

123

Пакет запросов: #TestProc Количество
затронутых строк : 453

Пакет запросов: #TestProc Количество
затронутых строк : 17

Соломон Руцкий
источник
1
Я попробовал это, и это работает для меня. Как ни странно, операторы StatementCompletions и InfoMessages from PRINT в хранимых процедурах, похоже, не синхронизируются друг с другом (я получаю кучу StatementCompletions, а затем кучу выходных данных оператора PRINT, хотя предполагается, что они чередуются) думаю, это трюк
SSMS
1
@RBarryYoung Да, такое поведение, я полагаю, следует ожидать, даже если оно раздражает. Это связано с порядком элементов в TDS (поток табличных данных): msdn.microsoft.com/en-us/library/dd304523.aspx . Я знаю, что все сообщения PRINTи RAISERROR(..., 10, 1)приходят после результирующих наборов. Я пытаюсь найти порядок сообщений в этой документации, но до сих пор не сталкивался с ним.
Соломон Руцкий
Для меня загадка в том, как SSMS правильно это решает.
RBarryYoung
1
@RBarryYoung Возможно, это должен быть отдельный вопрос, так как этот был просто о количестве строк из отдельных запросов? Это хороший вопрос, и я понял это :). Я опубликую это как вопрос, если у меня будет возможность сделать это прежде, чем вы доберетесь до него.
Соломон Руцки
1
@RBarryYoung Yikes. Жаль слышать об этом. Надеюсь, ты поправляешься. Я постараюсь добраться до него в ближайшие дни. У меня просто осталось один или два варианта для проверки, о которых я подумал после того, как опубликовал последнее сообщение. Я выложу ссылку на него здесь.
Соломон Руцки
-1

Результат выполнения запроса просто не будет делать то, что вы хотите здесь. Но вы все равно можете туда добраться, все зависит от того, для чего вы хотите использовать информацию.

Вы можете добавить эту строку после каждой вставки «PRINT @@ ROWCOUNT», и вы должны получить количество строк, затронутых предыдущей операцией, как часть вывода (где вы получаете «bar»).

В качестве альтернативы, вы можете добавить параметр «OUTPUT» в вашу хранимую процедуру, чтобы сохранить результаты, а затем просто перехватить их при запуске executetenonquery.

РЕДАКТИРОВАТЬ:

Мне удалось изменить пример, который Джонатан Кехасиас собрал, чтобы включить обработку событий оператора. Просто добавьте эти две строки.

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

#Attach handler...
$cmd.add_StatementCompleted($statementhandler)
Джонатан Файт
источник
Я не могу изменить эти процедуры. Я могу изменить код клиента, в том числе с помощью чего-то другого, кроме ExecuteNonQuery.
RBarryYoung
Вы можете попробовать подключить обработчик событий к событию sqlcommand infomessage. В этой статье показано, как это сделать с помощью powershell. sqlskills.com/blogs/jonathan/…
Джонатан
Если бы вы прочитали мой вопрос, вы бы увидели, что я уже делаю это. Это не там.
RBarryYoung
1
Этот вопрос в области C # говорит, что добавление слушателя к событию SQLCommand.StatementCompleted дало им то, что они искали. stackoverflow.com/questions/27993049/…
Джонатан Файт