1

私はいくつかのストアドプロシージャをチェーンしていて、エラー処理を正しく機能させる上でいくつかの問題に遭遇しました。これらのストアドプロシージャの一部は長時間実行されるため、ストアドプロシージャ内SqlInfoMessageEventHandlerなどのコードを使用RAISERROR('whatever',10,1) WITH NOWAITして、操作の進行状況をユーザーに報告しました。これを行うには、cmd.ExecuteNonQuery()を使用できず、代わりにcmd.ExecuteReader()を使用する必要があることを読みました。私はこれを試しましたが、実際にそうです。ExecuteNonQueryからのメッセージが表示されません。

しかし、私が見つけた問題は、ExecuteReaderを使用するときに、ストアドプロシージャがエラーをスローした場合、それらが無視されることです。これは、tryブロックからこのストアドプロシージャを呼び出す.NETアプリケーションを意味し、ストアドプロシージャでエラー(SELECT 1/0など)が発生すると、実行はcatchブロックに入ることがなく、代わりにトランザクションをコミットします。この例は次のとおりです。

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TempTstTbl]') AND type in (N'U'))
DROP TABLE [dbo].[TempTstTbl]

CREATE TABLE [dbo].[TempTstTbl] (
    Step INT,
    Val  VARCHAR(50)
)

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Child]') AND type in (N'P', N'PC'))

DROP PROCEDURE [dbo].[Child]
GO 
CREATE PROCEDURE [dbo].[Child]
AS
BEGIN
BEGIN TRY
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (1, 'FROM CHILD BEFORE FAULT')
    SELECT 1/0
    --RAISERROR ('This should really fail', 16, 2)
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (2, 'FROM CHILD AFTER FAULT')
END TRY
BEGIN CATCH
    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;

    SELECT 
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE();

    -- Use RAISERROR inside the CATCH block to return error
    -- information about the original error that caused
    -- execution to jump to the CATCH block.
    RAISERROR (@ErrorMessage, -- Message text.
               @ErrorSeverity, -- Severity.
               @ErrorState -- State.
               );
END CATCH;
END
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Parent]') AND type in (N'P', N'PC'))

DROP PROCEDURE [dbo].[Parent]
GO
CREATE PROCEDURE [dbo].[Parent]
AS
BEGIN
BEGIN TRY
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (1, 'FROM PARENT BEFORE CHILD')
    Exec [dbo].[Child]
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (2, 'FROM PARENT AFTER CHILD')
END TRY
BEGIN CATCH
    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;

    SELECT 
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE();

    -- Use RAISERROR inside the CATCH block to return error
    -- information about the original error that caused
    -- execution to jump to the CATCH block.
    RAISERROR (@ErrorMessage, -- Message text.
               @ErrorSeverity, -- Severity.
               @ErrorState -- State.
               );
END CATCH;
END
GO

EXEC [dbo].[Parent]
SELECT * FROM [dbo].[TempTstTbl]

次のような.NETコードを使用します。

private void button4_Click(object sender, EventArgs e)
{
    using (SqlConnection conn = new SqlConnection(@"Data Source=XPDEVVM\XPDEV;Initial Catalog=MyTest;Integrated Security=SSPI;"))
    {
        conn.Open();
        using (SqlTransaction trans = conn.BeginTransaction())
        {
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.Transaction = trans;
                cmd.CommandText = "[cfg].[Parent]";
                cmd.CommandType = CommandType.StoredProcedure;

                try
                {
                    -- cmd.ExecuteReader(); -- Using this instead of ExecuteNonQuery means the divide by 0 error in the stored proc is ignored, and everything is committed :(
                    cmd.ExecuteNonQuery();
                    trans.Commit();
                }
                catch (Exception ex)
                {
                    trans.Rollback();
                }
            }
        }
    }
}

ストアドプロシージャから進行状況メッセージを取得する方法について誰かが考えていますが、ストアドプロシージャでエラーが発生した場合でも、.NET例外をキャッチできますか?

4

1 に答える 1

1

この問題は、DataReaderの動作方法に起因しているようです。私は最初に、別のSO質問に対するこの回答のNelsonRothermelコメントでヒントに出くわしました。

次に、このスレッドで説明されているこの問題の詳細をさらに読みます。ここで、Richard McKinnonは、問題に対処する方法として次の例を示しています(私の強調)。

重大度>10(私は11を使用)で同じことを試し、SELECT行をSPに追加します(NorthWind DBを使用してチェックしました)。データを取得するSELECTがある場合、例外が発生することはありません。

[元のメッセージがクリップされました]

この場合、ExecuteReader()は、結果の最初のセットであるSELECTステートメントの処理を開始します。次の結果セットであるRAISERRORを取得するには、NextResult()を呼び出す必要があります

したがって、エラーを確認するために、以前のコードを次のように変更します。

SqlDataReader dr = command.ExecuteReader(); dr.NextResult(); dr.Close();

コードにdr.NextResult()を追加しようとしましたが、問題は修正されたようです。

これにより、ストアドプロシージャから情報メッセージを取得できますが、ストアドプロシージャで発生したエラーも検出できます。

于 2012-09-16T15:08:48.990 に答える