none
GridViewのRowUpdatedでPRIMARY KEY違反をハンドルする方法

    質問

  • 環境:asp.net 4 (VB)、SQL Server2008

    更新可能なGridViewで1行の更新処理をかけたときに、PRIMARY KEY違反をハンドルしたいのですがなかなか良い方法がわかりません。

    Protected Sub Gridview1_RowUpdated(sender As Object, e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) Handles Gridview1.RowUpdated
        If Not e.Exception Is Nothing Then
            If e.Exception.ToString.IndexOf("PRIMARY KEY 違反") >= 0 Then
                Label1.Text = "重複エラーが発生してますよ"
                e.ExceptionHandled = True
            End If
        End If
    End Sub

    こんな感じで取り敢えず作ってみたものの、これだと、Exceptionの内容の中に「PRIMARY KEY 違反」という文字列があるか見ているだけなので信頼できません(asp.netのバージョンやDBの種類でExceptionの文言が変わってくるかも?)信頼性のある方法でPRIMARY KEY違反をハンドルする方法がありましたら教えてください。そもそもこういったことは「RowUpdated」のタイミングでやるのではなく「RowUpdating」のイベントで事前にSELECT COUNT(*) ....などしてチェックするのが王道なのでしょうか?


    PS.更新処理でPRIMARY KEY違反が発生するようなDB設計はいかがなものかという突っ込みな無しで^^;


    2012年8月10日 8:34

回答

  • > InnerExceptionの設定は保障されているわけではないので、確かに
    > そのような不確実なものを当てにしてはいけませんね・・・。

    InnerException があると、それにしか必要な情報が入ってない場合が
    ありますので、以下のようにしたほうがいいかもしれませんね。

    public static Exception GetInnerException(Exception ex)
    {
        if (ex.InnerException == null)
        {
            return ex;
        }
        return GetInnerException(ex.InnerException);
    }
        
    protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
    {
        if (e.Exception == null && e.AffectedRows == 1)
        {
            // Update に成功
        }
        else
        {
            if (e.Exception != null)
            {
                Exception ex = GetInnerException(e.Exception);
                
                if (ex is SqlException)
                {
                    if (((SqlException)ex).Number == 2627)
                    {
                        // PK 制約違反
                        Label3.Text = ex.Message;
                        e.ExceptionHandled = true;
                    }
                }
            }
            e.KeepInEditMode = true;
        }
    }
    

    2012年8月14日 13:22

すべての返信

  • 試していませんが、

    If e.Exception IsNot Nothing Then
        If e.Exception.InnerException IsNot Nothing Then
            Dim inner As Exception = e.Exception.InnerException
     
                 If TypeOf inner Is System.Data.Common.DbException Then

    この時、inner をDbException にキャストし、そのErrorCodeプロパティで判断できると思います。PRIMARY KEY違反のエラーコードは2627かなぁ?

    (参考)
    SQLServer エラー一覧
    http://dbhikaku.web.fc2.com/sqlserver_error/sqlserver_error2001_3000.html


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年8月10日 13:56
    モデレータ
  • ちょっと試してみました。

    SQL Server 2008 利用だそうですので、e.Exception を SqlException にキャストして、
    その Number プロパティが PK 制約違反のエラーコードと一致しているかをチェックす
    るのがよさそうです。

    以下のような感じです。

    protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
    {
        if (e.Exception == null && e.AffectedRows == 1)
        {
            // Update に成功
        }
        else
        {
            if (e.Exception != null)
            {
                // e.Exception.InnerException は null になる
                Exception innerEx = e.Exception.InnerException;
                    
                if (e.Exception is SqlException)
                {
                    if (((SqlException)e.Exception).Number == 2627)
                    {
                        // PK 制約違反
                        Label3.Text = e.Exception.Message;
                        e.ExceptionHandled = true;
                    }
                }
            }
            e.KeepInEditMode = true;
        }
    }

    言語は C# です。読めなければ以下のページにある変換サービスで VB.NET に
    変換してみてください。

    Convert C# to VB.NET
    http://www.developerfusion.com/tools/convert/csharp-to-vb/

    なお、上のコードの中にも書きましたが、e.Exception.InnerException は null
    になります。

    ちなみに SQL Server のエラーコードは sys.messages から取得できます。日本
    語の一覧が見たければ SQL Server Management Studio などで以下のクエリを走
    らせると結果ウィンドウに表示されます。

    SELECT message_id, language_id, severity, is_event_logged, text
      FROM sys.messages
      WHERE language_id = 1041
    
    PK 制約違反のエラーコード(message_id)は 2627 で間違いないようです。

    2012年8月11日 7:23
  • こんにちは、投稿させて頂きます。

    ストアドをお使いであれば、そちら側で対応してみてはいかがでしょうか?

    CATCH句でエラーコードを返すようにすれば、様々なDB Errorに対応できる思います。


    USE [testdb]
    GO
    
    
    
    BEGIN TRY
    
    	INSERT INTO [testdb].[dbo].[test1]
    			   ([ID]
    			   ,[Name])
    		 VALUES
    			   (
    			   1
    			   ,'Yamada'
    			   )
    	INSERT INTO [testdb].[dbo].[test1]
    			   ([ID]
    			   ,[Name])
    		 VALUES
    			   (
    			   1
    			   ,'Kataoka'
    			   )
    
    END TRY
    BEGIN CATCH
    	SELECT
    		ERROR_NUMBER() AS ErrorNumber
    		,ERROR_SEVERITY() AS ErrorSeverity
    		,ERROR_STATE() AS ErrorState
    		,ERROR_PROCEDURE() AS ErrorProcedure
    		,ERROR_LINE() AS ErrorLine
    		,ERROR_MESSAGE() AS ErrorMessage;
    	
    	--print 'PRIMARY KEY違反'
    
    END CATCH
    
    GO


    • 編集済み cbqb22 2012年8月12日 12:28 文言修正
    2012年8月12日 12:27
  • > ストアドをお使いであれば、そちら側で対応してみてはいかがでしょうか?

    ストアドの CATCH ブロックによってエラーをトラップして、どのようなメリ
    ットがあるのでしょうか?

    UPDATE の際の PK 制約違反例外を捕捉した場合のみ、エラーメッセージを表
    示し、ExceptionHandled プロパティを true に設定して例外は処置されたと
    見なして処置を継続する(その他の例外は処置せず、サーバーエラーとして終
    了)という操作を行う、ということが今回の要件と理解しています。

    その理解が正しければ、ストアドでエラーをトラップしてしまうと、以下の
    (1), (2) ような対応がアプリ側で追加で必要になると思います。

    (1) ObjectDataSource、データ操作のためのクラス(要自作)、GridView を
      組み合わせた 3 層構造とする。(SqlDataSource + GridView という 2
      層構造では対応できないので)

    (2) ストアドの CATCH ブロックによってエラーがトラップされたら、呼び出し
      元のアプリケーション(上記で言うと「データ操作のためのクラス」)で
      例外をスローする。

      (注)例外をスローしないで、メソッドの戻り値などで処置しようとする
         と、GridView に DataBind が起こって、せっかくユーザーが入力
         したデータがすべて消えてしまいます。

    (3) GridView.RowUpdated イベントのハンドラで (2) でスローした例外を処置
      する。

    自分は、どう考えてもメリットはなさそうに思いますが・・・

    2012年8月13日 3:45
  • SurferOnWwwさんのおっしゃる通り、ストアド例外処理とすると2層構造では実現できませんね。

    とうふちくわさんの質問内容からはDAOクラスがなさそうなので、素直にC#側での処理がよさそうですね。

     

     

    ///ここに記載するのは混乱を招くかもしれませんので適切ではないかもしれませんが///

    実は以前、sqlexceptionを補足できないケースに遭遇したことがあったもので・・・・。それがPrimary Key違反の補足であったかはわかりません。

    (ネットで補足がスルーされる事例がないか調べてみましたが、見つかりませんでした。)

    後学のためにどなたかご存じであればご教授頂けると嬉しいです。




    • 編集済み cbqb22 2012年8月13日 4:44 訂正
    2012年8月13日 4:43
  • なお、上のコードの中にも書きましたが、e.Exception.InnerException は null
    になります。

    InnerExceptionはnullでしたか・・・。InnerExceptionの設定は保障されているわけではないので、確かにそのような不確実なものを当てにしてはいけませんね・・・。あくまで追加の情報として入っていれば利用するぐらいで考えなければなりません。完全に私の考え方のミスです。フォローありがとうございました。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    2012年8月14日 2:43
    モデレータ
  • > InnerExceptionの設定は保障されているわけではないので、確かに
    > そのような不確実なものを当てにしてはいけませんね・・・。

    InnerException があると、それにしか必要な情報が入ってない場合が
    ありますので、以下のようにしたほうがいいかもしれませんね。

    public static Exception GetInnerException(Exception ex)
    {
        if (ex.InnerException == null)
        {
            return ex;
        }
        return GetInnerException(ex.InnerException);
    }
        
    protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
    {
        if (e.Exception == null && e.AffectedRows == 1)
        {
            // Update に成功
        }
        else
        {
            if (e.Exception != null)
            {
                Exception ex = GetInnerException(e.Exception);
                
                if (ex is SqlException)
                {
                    if (((SqlException)ex).Number == 2627)
                    {
                        // PK 制約違反
                        Label3.Text = ex.Message;
                        e.ExceptionHandled = true;
                    }
                }
            }
            e.KeepInEditMode = true;
        }
    }
    

    2012年8月14日 13:22
  • trapemiyaさん、cbqb22さん、SurferOnWwwさんご回答ありがとうございます。おかげさまでSurferOnWwwさんに書いて頂いたコードで意図した動作が出来るようになりました。

    ちなみにSurferOnWwwさんに書いて頂いたコードの中で私の理解が足りないために不明な点がございました。

    public static Exception GetInnerException(Exception ex)
    {
        if (ex.InnerException == null)
        {
            return ex;
        }
        return GetInnerException(ex.InnerException);
    }

    7行目の

    return GetInnerException(ex.InnerException);

    ですが、引数「ex」の「InnerException」を自分自身(GetInnerException)のFunctionをかませてreturnされていますが、これは「ex.InnerException」の中にさらにInnerExceptionが存在する場合があるので、InnerExceptionがnullになるまで「GetInnerException」のFunctionをかませ続けるという意味合いでしょうか?つまり

        if (ex.InnerException == null)
        {
            return ex;
        } esle {
            return ex.InnerException;
        }

    では処理的に不足があるということでしょうか?

    また、cbqb22さんからの複数種類DBに汎用的に対応する方法のアドバイスありがとうございました。






    2012年8月16日 7:25
  • > つまり
    >
    >    if (ex.InnerException == null)
    >    {
    >        return ex;
    >    } esle {
    >        return ex.InnerException;
    >    }
    >
    > では処理的に不足があるということでしょうか?

    場合によっては InnerException が何重かネストしているケー
    スもあるので、その場合に一番最初に発生した例外を取得する
    には上記の方法では不足です。

    どこまで奥深いところまで InnerException が存在するか分ら
    ない場合は、自分で自分を呼ぶ「再帰」という方法を使うのが
    便利です。

    ただし、今回の場合はそこまでやる必要はなく、せいぜい一つ
    下まで見れば十分かもしれません。

    何にせよ、サーバーコントロールの中でどのように例外がスロ
    ーされているか見えないのが問題だと思います。

    2012年8月16日 13:23
  • SurferOnWwwさん ご返信ありがとうございます。

    場合によっては InnerException が何重かネストしているケースもあるので、その場合に一番最初に発生した例外を取得するには上記の方法では不足です。

    どこまで奥深いところまで InnerException が存在するか分らない場合は、自分で自分を呼ぶ「再帰」という方法を使うのが便利です。

    勉強になりました。ありがとうございます。

    2012年8月17日 7:07