0

Little bit of background of my Application.

I am working on a File Watcher windows service using C# that looks for .bak files in a particular folder and then use it to restore the database that file belongs to.

The Restored Database has a Stored Procedure that calls 10 different stored procedure. It's the File Watcher's functionality to execute the stored procedure after the Restore is done.

The Stored Procedure is [1_IMPORT_DATA_AND_PROCESS_ALL] which calls 10 different stored procedure within itself.

This is the Method which is executing the Stored Procedure after the restore is complete.

// Trigger Stored Procedure after restore. 
private void triggerSP(String connectionStr)
{
    // This doesn't open the Connection. conn.Open() has to be explicitly called.
    
    SqlConnection conn = new SqlConnection(connectionStr);
    try
    {
    
    conn.Open();
    conn.FireInfoMessageEventOnUserErrors = true;
    // Capture messages returned by SQL Server.
    conn.InfoMessage += delegate (object sender, SqlInfoMessageEventArgs e)
    {
        
        message += " -> " + e.Message + " -> ";
    };
    //conn.InfoMessage += new SqlInfoMessageEventHandler(cn_InfoMessage);
    
    //.create a command object identifying the stored procedure.
    SqlCommand cmd = new SqlCommand("[dbo].[1_IMPORT_DATA_AND_PROCESS_ALL]", conn);
    cmd.CommandTimeout = 0;
    // 2. set the command object so it knows to execute a stored procedure.
    cmd.CommandType = CommandType.StoredProcedure;

    // Add a check here as well.
    // execute the command.
    SqlDataReader rdr = cmd.ExecuteReader();


    string[] info = new string[] { "Message: \n" + message };
    WriteToFile(info);

    // Since we are not using - using block we have to explicitly call Close() to close the connection.
    conn.Close();
    }
    catch (SqlException SqlEx){
    string[] error = new string[3] ;

    string msg1 = "Errors Count:" + SqlEx.Errors.Count;
    string msg2 = null;

    foreach (SqlError myError in SqlEx.Errors)
        msg2 += myError.Number + " - " + myError.Message + "/" ;

    conn.InfoMessage += delegate (object sender, SqlInfoMessageEventArgs e)
    {
        message += "\n" + e.Message;
    };

    error[0] = msg1;
    error[1] = msg2;
    error[2] = message;

    WriteToFile(error);
    }

    finally
    {
    //call this if exception occurs or not
    //in this example, dispose the WebClient
    conn.Close();
    }

}

Problem

I am only getting back the Message outputs from the very first stored procedure i.e [1_IMPORT_DATA_AND_PROCESS_ALL] and not from the stored procedure which are being called from within [1_IMPORT_DATA_AND_PROCESS_ALL] as shown bellow.

enter image description here

As soon as the 1st Sp calls another SP my codes stops reading the Messages.

I want to capture all the Messages that are being printed, something Like this (image below), which are the actual messages that are being printed when I execute the SP in SSMS.

enter image description here

This particular line is fetching the Messages from the SP

conn.InfoMessage += delegate (object sender, SqlInfoMessageEventArgs e)
    {
        message += " -> " + e.Message + " -> ";
    };

So far I have referred to everything form this question and it's derivatives.

I can't change the Stored Procedure now, I can only make changes to my C# Application.

Hope my question is clear.

Community
  • 1
  • 1

1 Answers1

0

Good day,

Note! This message is not marked as "community wiki" and as such it written by a specific person under his name and this is not a shared article. If you have comments then please use the comments instead of changing the content, which the OP meant to provide (for example extra learning points in the content). Thanks!

In the following script I present an example of handling nested Stored Procedure error.The basic Idea is to use TRY/CATCH in order to prevent raising the error and stop the transaction, and the OUTPUT is used to return the error information to the upper level SP

This is only a basic example...

CREATE or ALTER PROCEDURE L1 (
    @InputInt int,
    @ErrMessage NVARCHAR(MAX) OUTPUT,
    @ErrNum INT OUTPUT
)AS
    SELECT @@NESTLEVEL AS 'Inner Level'; -- this information present the level of the SP during the execution. It is not needed for the solution but for the sake of the learning and understanding of nested SP
    Select 'Start L1'

    BEGIN TRY  
        -- When the ionput is 0 we Generate a divide-by-zero error.  
        SELECT 1/@InputInt;  
    END TRY  
    BEGIN CATCH
        SET @ErrMessage = ERROR_MESSAGE()
        SELECT @ErrMessage
    END CATCH;

    SET @ErrNum = @@ERROR
    IF (@ErrNum > 0) Begin
       SELECT 'L1 error Number: ' + CONVERT(NVARCHAR(10), @ErrNum)
       Return
    END
    ELSE
       select 'L1 OK'
GO

CREATE or ALTER PROCEDURE L2 (
    @InputInt int
) AS   
    Declare @ErrMessage NVARCHAR(MAX) = '', @ErrNum INT = 0
    SELECT @@NESTLEVEL AS 'Outer Level';
    BEGIN TRY
        EXEC L1 @InputInt, @ErrMessage, @ErrNum;
    END TRY
    BEGIN CATCH
        SELECT 'There was error!'
        select @@ERROR
    END CATCH
GO

EXECUTE L2 1 -- OK
GO

EXECUTE L2 0; --Raise error in the nested stored procedures 
GO
Ronen Ariely
  • 2,336
  • 12
  • 21
  • Sorry @RonenAriely, I forgot to mention, I can only make changes to my C# Application and not the stored procedure. –  Mar 25 '19 at 22:59
  • Hi, If the SP does not include a procedure to handle errors, then the errors will not "move" up in the levels from the nested SP to the client app (the C#). In this case **you cannot present something that does not exists**. There are no errors to display from the nested SP if you do not handle them. You should contact the person who control the SP's code and (1) check if there is a procedure to handle errors - in this case we need to see the code of the SP, or (2) ask him to do his part and edit the code according to the requirements. – Ronen Ariely Mar 25 '19 at 23:04
  • By the way, in this case, you should remove the tag "tsql" from the question, since now you gave new information which say that it is not related to "tsql" since you do not control the "tsql" of the SP... This become a pure C# question, but the answer stays the same as my previous comment "you cannot present something that does not exists" :-) – Ronen Ariely Mar 25 '19 at 23:09
  • In all the SPs I have PRINT statement after every transaction. I was hoping to capture all of that since it will appear in the Message. I have also provided the screen shot for the desired Message to capture from SQL Server to my C# Application. –  Mar 26 '19 at 03:19
  • Hi @ThePashupatee , There is no option to have areal discussion in stackoverflow. It is the worse active forums interface probably online. We can only post original question and answers and you keep changing the question which make the answers irrelevant. In order to explain better, I need to use images, code, etc'. If it was a discussion on MSDN forums for example we could have a discussion where we response to each other and we could track the discussion history and who response what and to whom. I will try to give it another shot and explain the issue in this poor comments interface... – Ronen Ariely Mar 26 '19 at 07:07
  • Imagine my explanation in image... you have 3 levels `App1->SP2->SP1`. App1 call SP2 which call SP1. App1 does not have any information regarding SP1 directly which is the issue.It only "speak" with SP2... The request came to SP1, and SP1 has error -> SP1 sendthe error to SP2 back since SP2 speak with SP1, but SP1 cannot send the information to God or to anyone who does not speak/listen to it. It does not send the information to APP1. **`I HATE stackoverflow`** I need to split the comment... – Ronen Ariely Mar 26 '19 at 07:19
  • **`Continue...`** The information reach SP2 from SP1 but if SP2 does not handle the information, then SP2 also will not send it to the APP1 - you need to handle the errors in SP2. Since SP2 had error from executing SP1 it send information to the APP1, but this is *NOT* the information from SP1, but a new information related to the error in SP2. **Using REST architecture, You need to handle the errors in each level, if you have nested requests** A call B which call C which call D... responses return from D to C to B to A. If B did not get the information from D then how can B provide it to A – Ronen Ariely Mar 26 '19 at 07:25
  • In addition am error in SP1 which is not handle well might stop the transaction,which mean that SP2 will get error and if SP2 does not handle the error it will raise an error from SP2 and stop the transaction... errors should handled if they appear – Ronen Ariely Mar 26 '19 at 07:30
  • Hey @RonenAriely, I understood the explanation, and so far what I can make out of it is there is no way I can fetch PRINT messages/or any messages from nested SPs unless they are handled in the Parent SP. My App won't be able to see the messages unless they are being handled by the SP it is calling. –  Mar 26 '19 at 13:01
  • In short... exactly **`:-)`** . I think that you can close the thread. The answer I gave you can be marked as answer for the original question, and the explanation in the comments brings the rest of the information (to clarify, you can get errors from the SP that you call, but these will not be the errors from the nested SP and their message will not necessarily will be the same). – Ronen Ariely Mar 26 '19 at 13:37
  • Ops...I cannot edit the comment above...it should be "basically yes" and not "exactly" since you do not have to handle the error in the SP which you call in order to have the information in the APP. You can bypass the the "middle" SP in different ways, but this mean that the information will not come from the the SP which you call. For example you can handle the error in the nested SP and insert the error to table. Next the application can read the information from the table (and there are more option). But in any case the solution must involve handling the error in the Stored Procedures level – Ronen Ariely Mar 26 '19 at 13:49
  • I will give it more time. Unfortunately I cannot mark your answer as required answer. My question was clear and I have only edited it once. From the question it was clear that I am looking for C# solution since I have only provided and linked C# codes and references. –  Mar 26 '19 at 13:54
  • This one change which you added "`I can't change the Stored Procedure now, I can only make changes to my C# Application.`"" made all the change (and removing the tag `tsql` which is what brought me to this discussion (I only checked `tsql` questions). Anyhow, That's ok :-) . I think that I don't have anything more to add, so I will drop from the discussion. **Good luck** – Ronen Ariely Mar 26 '19 at 14:35
  • 1
    Thanks for your Input. And sorry, I confused you in the beginning. –  Mar 26 '19 at 14:38
  • It's OK :-) **Good luck** with solving your needs :-) – Ronen Ariely Mar 26 '19 at 14:39