Search code examples
c#sql-server-2008raiserror

SQL Server and how to handle the type of error without conflict


I use SQL Server 2008 and ASP.NET C# and I have a stored procedure that returns some calculated rows.

I want to check the parameters values before doing the process and I want to return a custom error message if the parameters values are wrong.

In the UI side, I must return a different text and use a different window according to the error type. Example:

  • Errors when the parameters values are wrong.
  • Non handled errors.

I currently use this kind of SP in the database, but I tried to use the state argument and even the severity argument to identify the type of error. But I have some conflicts with non handled error returning the same state number than my parameter error, and so the wrong window comes up. If I use the severity level, I guess the SP could return an error with the same severity number as well in some cases.

I give you a quick example to have a better view:

CREATE PROCEDURE dbo.GetData
    @Date1 date,
    @Date2 date
AS
BEGIN

    -- check the parameters
    IF @Date2 < @Date1
    BEGIN
        RAISERROR(N'Date 2 cannot be less than Date 1', 16, 2); -- <= Here State 2
        return
    END

    -- process here...
    DECLARE @Table1 TABLE ( name nvarchar(50) NOT NULL)

    -- Supposing you have to insert a row with a NULL value
    INSERT INTO @Table1 VALUES (NULL);
    -- Thus, this query returns this error with the state 2 as well!
    --Msg 515, Level 16, State 2, Procedure GetData, Line 21
    --Cannot insert the value NULL into column 'name', table '@Table1'; column does not allow nulls. INSERT fails.

    SELECT 'Record 1';
    SELECT 'Record 2';
END

From c#:

List<string> data = new List<string>();

protected void Button1_Click(object sender, EventArgs e)
{
    string errorMessage = string.Empty;
    bool isErrorFromChecking = false;

    if (GetValues(ConfigurationManager.ConnectionStrings["TestConnectionString"].ConnectionString,
                new DateTime(2011, 01, 01), new DateTime(2011, 02, 01),
                ref isErrorFromChecking, ref errorMessage))
    {
        Label1.Text = data[0].ToString();
        return;
    }

    if (isErrorFromChecking)
    {
        Label1.Text = errorMessage;
        return;
    }
    Label1.Text = string.Format("Internal Error: {0}.", errorMessage);
}

private bool GetValues(string connectionString, DateTime date1, DateTime date2,
            ref bool isErrorFromChecking, ref string errorMessage)
{
    data = new List<string>();
    try
    {
        using (SqlConnection sqlCon = new SqlConnection(connectionString))
        {
            sqlCon.Open();
            SqlCommand sqlCmd = new SqlCommand();
            sqlCmd.Connection = sqlCon;
            sqlCmd.CommandType = CommandType.StoredProcedure;
            sqlCmd.CommandText = "dbo.GetData";

            sqlCmd.Parameters.AddWithValue("Date1", date1);
            sqlCmd.Parameters.AddWithValue("Date2", date2);

            SqlDataReader reader = sqlCmd.ExecuteReader();
            while (reader.Read())
            {
                data.Add(reader[0].ToString());
            }
            reader.Close();
            sqlCon.Close();
        }
    }
    catch (SqlException ex)
    {
        if (ex.State == 2)
        {
            isErrorFromChecking = true;
            errorMessage = ex.Message;
            return false;
        }
        isErrorFromChecking = false;
        errorMessage = ex.Message;
        return false;
    }
    catch (Exception ex)
    {
        isErrorFromChecking = false;
        errorMessage = ex.Message;
        return false;
    }

    return true;
}

In the code above, the dates are correct but the program does not return the message “Internal Error: …” although the SP had an error.

I have some ideas, but I just want to know your point of view and the best way to do it.

Thanks.


Solution

  • Simply split the logic in to two. By this I mean have the 'parameter check' logic in one Stored Procedure and the rest in another SP. In the 'check' SP use output parameters to get any codes or error text you want returned.

    The second SP can also call SP first to ensure it's not getting sent bad data.

    The C# should then call the 'check' SP first and see what is returned.

    Alternative is to use an output parameter for the error condition and return an empty dataset.

    CREATE PROCEDURE spTest (@param1 int,   @ErrorText varchar(50) OUTPUT )
    AS
    BEGIN
        SET @ErrorText = NULL
        IF @Param1 = 1 
        BEGIN
            SET @ErrorText = 'I really cant except 1 as a parameter'
            SELECT NULL as QueryPlaceholderCol
        END 
        ELSE
        BEGIN
            SELECT 3.141 AS QueryPlaceholderCol
        END
    END
    

    To test in T-SQL:

    DECLARE @ReturnedError VARCHAR(50)
    EXEC spTest 1,@ReturnedError OUTPUT
    SELECT CASE WHEN @ReturnedError IS NULL THEN 'Worked Fine' ELSE @ReturnedError END AS Outputs
    EXEC spTest 1423,@ReturnedError OUTPUT
    SELECT CASE WHEN @ReturnedError IS NULL THEN 'Worked Fine' ELSE @ReturnedError END AS Outputs