1

I wrote my own SQL client because I was tired of SSMS. I want to capture the affected row counts just like SSMS but SqlCommand.StatementCompleted is raised multiple times when ExecuteReader finishes (or EndExecuteReader is called). Even though the statements in the batch complete with a regular interval, it seems like the DONE_IN_PROC messages are queued up on the client, and then dumped all at once, instead of being raised continuously. No InfoMessage events through-out the execution from SqlConnection, either.

Update above in italics.

Say, you have a SQL statement that updates rows in a loop, or something like that:

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

SSMS properly shows "(10 rows affected)" every X number of seconds or w/e. There are no records coming from the reader, since this is a UPDATE only statement, so can't use SqlDataReader to count the rows.

Is that possible, using the SqlClient library?

The simplified code in question is like this:

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
            }));
          }
        }
      }
    }
  }
}

A similar question is found here, though the description is less clear.

Community
  • 1
  • 1
Mr. TA
  • 5,230
  • 1
  • 28
  • 35

3 Answers3

0

Why dont you move the sql into a stored procedure. Execute the stored procedure using a command object with a parameters collection including an output parameter. Within the stored procedure have a total count variable to sum each of the individual update rows affected. Return this as an output parameter.

  • 1
    Your suggestion misplaces the requirement I'm trying to satisfy. I'm trying to make the SQL client that I wrote work a little like SSMS: report the affected row counts as multi-statement batch executes. I can obviously do something similar by tweaking the SQL, but that defeats the point of having a SQL client: it's supposed to do things like this by itself. – Mr. TA Jan 16 '15 at 22:50
0

When you update the db using ExecuteNonQuery method, it returns number of rows affected. This method is used when you use insert, update or delete sql command. Hope this is what you are looking for.

Bhaskar
  • 1,028
  • 12
  • 16
  • That won't work. That method only returns one record count. I need to capture 0 or more counts from multiple statements in a batch. Reread my question, one of the most frequent scenarios is a very long job split into a loop. There is no way to split a SQL batch containing a loop into multiple `ExecuteNonQuery` calls. – Mr. TA Jan 20 '15 at 20:58
  • Have you tried the answer given in [this link](http://stackoverflow.com/questions/1880471/capture-stored-procedure-print-output-in-net)? – Bhaskar Jan 21 '15 at 02:08
  • In SSMS, each statement is executed one by one that's why it can tell the affected rows by each statement. I couldn't find a function to do the same in C#. I think you have to create loop and execute one statement at a time (without closing the connection, if possible). Am I right? @Mr.TA – Bhaskar Jan 23 '15 at 07:31
  • nope, that's impossible, which is why I included the example of a loop. You can split SQL into multiple executions if it's static 3 UPDATEs, for example, but not if it's a loop. – Mr. TA Jan 23 '15 at 16:57
  • Hey your problem solved? I found [this](https://msdn.microsoft.com/en-us/library/cfa084cz(v=vs.110).aspx). It can execute multiple statements, but I don't know if it works for the "loop" case. – Bhaskar Feb 04 '15 at 05:06
0

There are two events that you want to handle. The SqlConnection InfoMessage and the SqlCommand StatementCompleted.

Sorry for the VB code but you should be able to convert it.

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
Nicholas
  • 768
  • 6
  • 16
  • Please see updated question. `StatementCompleted` does get raised for each statement, but only when `ExecuteReader` finishes. Basically, I get a slew of `StatementCompleted` events all at once. Meanwhile, the batch executes and I don't know if it's running successfully or not - until it's done. – Mr. TA Jan 22 '15 at 18:56
  • @mr-ta My code is from an asp.net application where everything is writing to the same stream and it does produce the messages in the correct order. I suspect you might need to use async/await but I'm not familiar with that. – Nicholas Jan 23 '15 at 01:07
  • @mr-ta One other thing, are you using print or raiserror in your sql code? I think print is delayed until after a result set or batch is returned. Try using raiserror('msg', 0, 0); in your sql code and see what happens. – Nicholas Jan 23 '15 at 01:19
  • The order is not the problem, the `StatementCompleted` events are raised in the correct order. It's the timing that is off. Instead of being raised as statements are actually completed, they all get raised at the end. This prevents me from knowing if things are working as expected. Some of the batch updating loops i run can go on for hours, i need to know if I am on the right track sooner than that. – Mr. TA Jan 23 '15 at 16:55
  • @mr-ta Instead of using a tread have you looked at using async? This might help http://codereview.stackexchange.com/a/22916 – Nicholas Jan 23 '15 at 23:54
  • For what it's worth, I tried the Begin/End pair and that didn't work. My project is in .NET 4; upgrading becomes a whole new effort now. I'll make a test project and try it out first. – Mr. TA Jan 24 '15 at 11:36
  • I marked this as answer because it seems the timing of the events is completely random. I couldn't notice any pattern as to version of SQL server, local vs. remote, etc. – Mr. TA Apr 24 '15 at 10:05