Получить количество записей из нескольких операторов

Я написал свой собственный SQL-клиент, потому что устал от SSMS. Я хочу зафиксировать затронутые счетчики строк так же, как SSMS, но SqlCommand.StatementCompleted поднимается несколько раз, когда ExecuteReader завершается (или вызывается EndExecuteReader). Несмотря на то, что операторы в пакете выполняются с регулярным интервалом, кажется, что сообщения DONE_IN_PROC ставятся в очередь на клиенте, а затем выгружаются все сразу, вместо того, чтобы непрерывно подниматься. Нет событий InfoMessage на протяжении всего цикла. исполнение от SqlConnection тоже.

Обновление выше, выделенное курсивом.

Скажем, у вас есть оператор SQL, который обновляет строки в цикле, или что-то в этом роде:

WHILE 1=1 
BEGIN 
  UPDATE tbl SET .... WHERE Id BETWEEN i AND i+10; 
  IF @@ROWCOUNT =0 BREAK; 
  SET i = i + 10; 
END

SSMS правильно показывает «(затронуто 10 строк)» каждые X секунд или w/e. От модуля чтения нет записей, так как это только оператор UPDATE, поэтому нельзя использовать SqlDataReader для подсчета строк.

Возможно ли это, используя библиотеку SqlClient?

Рассматриваемый упрощенный код выглядит следующим образом:

class ExecuteWorker
{
  public void Start(string query)
  {
    this._query = query;
    this._thread.Start(this.Work);
  }

  void Work()
  {
    using(var conn = new SqlConnection(this._connStr))
    {
      conn.Open();
      using(var command = conn.CreateCommand())
      {
        command.CommandText = this._query;
        using(var reader = command.ExecuteReader())
        {
          while(reader.Read())
          {
            this._control.BeginInvoke(new Action(()=>{
              // update UI
            }));
          }
        }
      }
    }
  }
}

Аналогичный вопрос можно найти здесь, хотя описание менее понятное.


person Mr. TA    schedule 16.01.2015    source источник
comment
Какой код вы используете для выполнения оператора SQL?   -  person theMayer    schedule 17.01.2015
comment
@theMayer хлеб и масло SqlClient: SqlConnection, SqlCommand, ExecuteReader.   -  person Mr. TA    schedule 17.01.2015
comment
Это выполнимо, но я хотел бы увидеть полную реализацию того, что вы делаете.   -  person theMayer    schedule 18.01.2015
comment
Пожалуйста, смотрите обновленный ответ, дайте мне знать, если нужны какие-либо дополнительные подробности.   -  person Mr. TA    schedule 20.01.2015


Ответы (3)


Есть два события, которые вы хотите обработать. SqlConnection InfoMessage и SqlCommand StatementCompleted.

Извините за код VB, но вы сможете его преобразовать.

Private Sub OnInfoMessage(sender As Object, args As SqlClient.SqlInfoMessageEventArgs)
    For Each sqlEvent As System.Data.SqlClient.SqlError In args.Errors
        Dim msg As String = String.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}", sqlEvent.Number, sqlEvent.Class, sqlEvent.State, sqlEvent.LineNumber, Environment.NewLine)
        'Write msg to output
    Next
End Sub

Private Sub OnStatementCompleted(sender As Object, args As StatementCompletedEventArgs)
    Dim msg As String = String.Format("({0} row(s) affected)", args.RecordCount)
    'Write msg to output
End Sub

Public Sub Work()
    Using dbc As New SqlConnection(Me.ConnectionString),
          dbcmd As New SqlCommand()

        dbcmd.Connection = dbc
        dbcmd.CommandTimeout = Convert.ToInt32(timeout) ' you will want to set this otherwise it will timeout after 30 seconds
        'set other dbcmd properties

        AddHandler dbc.InfoMessage, New SqlInfoMessageEventHandler(AddressOf OnInfoMessage)
        AddHandler dbcmd.StatementCompleted, AddressOf OnStatementCompleted

        dbc.Open()

        'dbcmd.ExecuteNonQuery() or Using dataReader As SqlDataReader = dbcmd.ExecuteReader()

    End Using
End Sub
person Nicholas    schedule 21.01.2015
comment
Пожалуйста, смотрите обновленный вопрос. StatementCompleted вызывается для каждого оператора, но только после завершения ExecuteReader. По сути, я получаю множество StatementCompleted событий одновременно. Тем временем пакет выполняется, и я не знаю, успешно он работает или нет, пока он не будет выполнен. - person Mr. TA; 22.01.2015
comment
@mr-ta Мой код взят из приложения asp.net, где все записывается в один и тот же поток, и сообщения выводятся в правильном порядке. Я подозреваю, что вам может понадобиться использовать async/await, но я не знаком с этим. - person Nicholas; 23.01.2015
comment
@ mr-ta Еще одна вещь: вы используете печать или рейзеррор в своем коде sql? Я думаю, что печать откладывается до тех пор, пока не будет возвращен набор результатов или пакет. Попробуйте использовать raiserror('msg', 0, 0); в вашем коде sql и посмотрите, что произойдет. - person Nicholas; 23.01.2015
comment
Порядок не проблема, события StatementCompleted вызываются в правильном порядке. Это время, которое выключено. Вместо того, чтобы подниматься по мере фактического завершения операторов, они все поднимаются в конце. Это мешает мне узнать, работают ли все так, как ожидалось. Некоторые из циклов пакетного обновления, которые я запускаю, могут продолжаться часами, мне нужно знать, нахожусь ли я на правильном пути раньше. - person Mr. TA; 23.01.2015
comment
@ mr-ta Вместо использования протектора вы рассматривали использование асинхронности? Это может помочь codereview.stackexchange.com/a/22916 - person Nicholas; 24.01.2015
comment
Что бы это ни стоило, я попробовал пару «Начало/Конец», и это не сработало. Мой проект находится в .NET 4; обновление теперь становится совершенно новым усилием. Я сделаю тестовый проект и попробую сначала. - person Mr. TA; 24.01.2015
comment
Я отметил это как ответ, потому что время событий кажется совершенно случайным. Я не мог заметить какой-либо закономерности в отношении версии SQL-сервера, локального или удаленного и т. д. - person Mr. TA; 24.04.2015

Почему бы вам не переместить sql в хранимую процедуру. Выполните хранимую процедуру, используя объект команды с набором параметров, включая выходной параметр. В хранимой процедуре есть переменная общего количества для суммирования каждой из затронутых отдельных строк обновления. Верните это как выходной параметр.

person Brian McEnroy    schedule 16.01.2015
comment
Ваше предложение не соответствует требованию, которое я пытаюсь удовлетворить. Я пытаюсь заставить клиент SQL, который я написал, работать немного как SSMS: сообщать количество затронутых строк при выполнении пакета из нескольких операторов. Очевидно, что я могу сделать что-то подобное, изменив SQL, но это сводит на нет смысл наличия SQL-клиента: он должен делать такие вещи сам по себе. - person Mr. TA; 17.01.2015

При обновлении базы данных с помощью ExecuteNonQuery, он возвращает количество затронутых строк. Этот метод используется, когда вы используете команду вставки, обновления или удаления sql. Надеюсь, это то, что вы ищете.

person Bhaskar    schedule 20.01.2015
comment
Это не сработает. Этот метод возвращает только одно количество записей. Мне нужно захватить 0 или более счетчиков из нескольких операторов в пакете. Перечитайте мой вопрос, один из самых частых сценариев — это очень длинная работа, разбитая на цикл. Невозможно разделить пакет SQL, содержащий цикл, на несколько вызовов ExecuteNonQuery. - person Mr. TA; 20.01.2015
comment
Пробовали ли вы ответить на эту ссылку? - person Bhaskar; 21.01.2015
comment
В SSMS каждый оператор выполняется один за другим, поэтому он может указать затронутые строки для каждого оператора. Я не мог найти функцию, чтобы сделать то же самое в С#. Я думаю, вам нужно создать цикл и выполнять по одному оператору за раз (без закрытия соединения, если это возможно). Я прав? @Мистер Т.А. - person Bhaskar; 23.01.2015
comment
нет, это невозможно, поэтому я включил пример цикла. Вы можете разделить SQL на несколько исполнений, например, если это статические 3 ОБНОВЛЕНИЯ, но не если это цикл. - person Mr. TA; 23.01.2015
comment
Эй, твоя проблема решена? Я нашел это. Он может выполнять несколько операторов, но я не знаю, работает ли он для цикла. - person Bhaskar; 04.02.2015