QA@IT

VB.NET+ODP.NETでトランザクション実行時のエラーメッセージ

14852 PV

Windows7 Professional
VB2008+Oracle11gR2(サーバー・クライアントともに32bit)

お世話になります。

フォームアプリケーションから複数のデータ更新を行う際にトランザクションを
設定しているのですが、エラーをCatchした際のメッセージが

「オブジェクトの現在の状態に問題があるため、操作は有効ではありません。」

というものになり、実際に発生したエラーが何であったのかを知ることが出来ません。

Public Sub TEST

Dim Tran as Oracletransaction=Nothing
Try
 Using CON As New OracleConnection(接続文字列)
  CON.Open()
  Tran=CON.BeginTransaction
   Using CMD As New OracleCommand("", CON)

 更新処理
  Tran.Commit

   End Using
 End Using

Catch ex As OracleException
      Tran.Rollback
      MessageBox.Show(ex.ToString)
Catch ex As Exception
      Tran.Rollback
      MessageBox.Show(ex.ToString)
End Sub

ここで何らかのエラーが発生したときのメッセージが
「オブジェクトの現在の状態に問題があるため、操作は有効ではありません。」ですと
原因究明が出来ませんので、都度トランザクションに関する部分をコメントアウトして
エラーを再現させています。

どのように記述すればトランザクションをかけたまま元のエラーを知ることが出来るのでしょうか。

2014.03.11 編集しました

以下、SurferOnWww様がご提示下さったリンク先の引用です。

① try-catch ブロックは、基本的に、例外を業務エラーなどに変換したい場合に使う。
try-catch ブロックは、例外が発生しうる『1 行』のみを囲む。
一般例外(Exception クラス)ではなく、特定の例外(SqlException など)のみを捕捉する。
catch した後には、必ず後処理(業務エラーへの変換など)を記述する。
② try-finally ブロックは、基本的に、リソースを確実に解放したい場合に使う。
try-finally ブロックは、アプリケーション全体を囲む。
一般例外(Exception クラス)すべてに対して有効になるように記述する。
catch ブロックは書かない。

これを受けてこのように書きなおしてみました。

Public Sub TEST

 Using CON As New OracleConnection(接続文字列)
   Using CMD As New OracleCommand("", CON)

 更新処理

   CON.Open
   Dim TRAN As OracleTransaction = Nothing
   TRAN = CON.BeginTransaction
   CMD.Transaction = TRAN
   Try
     CMD.ExecuteNonQuery()
   Catch ex As OracleException
     MessageBox.Show(ex.ToString)
     If Not TRAN Is Nothing Then
        TRAN.Rollback()
     End If
   End Try

   Tran.Commit

   End Using
 End Using

この場合ですとCatchのなかでRollbackが実行されることを確認しました。

【疑問1】
この理解で正しいでしょうか?

【疑問2】

try-catch ブロックは、例外が発生しうる『1 行』のみを囲む。

とありますが、更新部分で複数のテーブルに対して処理を行う場合は
UsingでCMD1、CMD2、CMD3等と宣言し、下記のように記述するのはいけないのでしょうか?
やはりCMD1、CMD2、CMD3それぞれにTry-Catchを記述するものなのでしょうか?

   CON.Open
   Dim TRAN As OracleTransaction = Nothing
   TRAN = CON.BeginTransaction
   CMD1.Transaction = TRAN
   CMD2.Transaction = TRAN
   CMD3.Transaction = TRAN
   Try
     CMD1.ExecuteNonQuery()
     CMD2.ExecuteNonQuery()
     CMD3.ExecuteNonQuery()
   Catch ex As OracleException
     MessageBox.Show(ex.ToString)
     If Not TRAN Is Nothing Then
        TRAN.Rollback()
     End If
   End Try

【疑問3】
DB接続時以外の一般エラー
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Sub
これをどこへ書けば正しいのか分からなくなってしまいました。

一般例外(Exception クラス)すべてに対して有効になるように記述する。

とあるもののtry-finally ブロックは

catch ブロックは書かない。

ともあるからです。

また、Using~End Usingで記述するとFainallyは不要だと理解していたのですが、
これも誤った解釈だったのということでしょうか。

  • Shu様 有難うございます。
    Using CMD...の下にCMD.Transaction=TRANを加筆しましたが同じエラーメッセージでした。
    SurferOnWww 様 有難うございます。まだまだ勉強不足です(^^;
    -
  • Tran.RollbackとMessageBox表示の順序を入れ替えることで解決しました。今後とも宜しくお願い致します。 -
  • あとはせめてcatchブロックでは TranがNothingではない場合のみRollbackするようにした方がいいですね。例えば接続文字列を間違えた場合落ちるんじゃないですか?(落ちずに何事もなく続行するなら多分どこかで例外を握りつぶしてるのでそれはそれで大きな問題) -
  • 「Tran.RollbackとMessageBox表示の順序を入れ替えることで解決しました」ということは Rollback できていないということでは? 紹介した記事を読んでますか? そうは思えないのですが。 -
  • flied_onion様 今回もお世話になります。接続文字列はdll内に記述しており問題ないのですが、If Not Tran Is Nothing Then...のような処理が望ましいということでしょうか? -
  • SurferOnWww様 ご指摘有難うございます。ご教示下さったリンク先、拝読致しましたがまだ理解に至っておりません。再確認しましたがRollback出来ておりました。http://dobon.net/vb/dotnet/beginner/exceptionhandling.html等を参考にさせて頂きましたが、私の解釈が誤っていたようです。 -
  • 以前の順序では、メッセージ表示前に、Tran.Rollback で例外がスローされ終わってしまったのですよね。それでも Rollback できていたということは、想像ですが、using ブロックを抜ける時に Dispose メソッドで Rollback されたのでは? とすると、問題は、接続が閉じられているのに catch ブロックで Rollback をかけたことでは? 確認ください。 -
  • 接続文字列が正しかろうが、サーバーが停止していたりネットワークが不通だったり、tnsnames.oraが間違っていれば落ちます。tryブロックを大きめにとらざるを得ないのであればいろいろな問題が起こることを想定しなければなりません。提示してくれているIf文はそれで大丈夫です。 -
  • CommitしていないわけですからRollbackそのものはどこかで発生します。SurferOnWwwさんが指摘されているのはTran.Rollbackメソッドがエラーなく呼べているかで、そのエラーが提示された順序の入れ替えで正常化するとは思えない点です。確認にはTran.Rollbackの次の行でメッセージボックスやDebug.Printなどしてその処理が通過するかを見る方がわかりやすいかも。 -
  • SurferOnWww様
    flied_onion様
    遅くなり申し訳ございませんでした。まとめての返信で失礼致します。ステップ実行にて意図通りに実行されたRollbackではなかったことを確認しました。当初の目的である本来のエラーメッセージをログに残すことには成功しましたが、このままで良い訳では無いことも理解出来ました。より精進したいと思います。
    -
  • ご教示頂いた内容を整理するために編集してみました。ご教示頂ければ幸いです。宜しくお願い致します。 -
  • 「編集」した質問内容に対するレスを書きました。下の「回答」を見てください。 -
  • SurferOnWww様 大変丁寧なご回答を頂き本当に有難うございます。拝読する中で漸く業務エラーと異常事態の区別が付きました(遅!)。これまでtry-catchはどこにでも書くもの、くらいに思っていましたので目から鱗でした。ご教示下さり本当に有難うございました。今後とも宜しくお願い致します。 -

回答

CMD に対し トランザクションを割り当てていないから
ではないでしょうか?

CMD.Transaction = Tran

とかでどうでしょう?

編集 履歴 (0)

先に紹介した msdn blog の記事に書いてあった方針に合意して、その通りに実装するという前提で回答します。(質問者さんや質問者さんの所属する組織の設計方針/ルール/カンパニーポリシーなどが、それとは違うという場合は話が別ですよ)

【疑問1】
この理解で正しいでしょうか?

OracleException を catch して、そのままにしてしまうという点が違うと思います。

先に紹介した記事の Part 1 の[.NET アプリケーションにおける例外と業務エラーの違い]というセクションをよく読んでください。

OracleException の中には「異常事態」(データベース接続エラーやネットワークエラーで、処理が正しく遂行できない事態)もあるはずですが、そのような例外までキャッチしてユーザーに通知しても、ユーザーとしては何ともできません。また、処理は続けないで終了すべきです。

catch すべきは「業務エラー」の方だけです。記事の例では「業務エラー」を、重複ユーザ ID の登録を例にとって説明していますが、それなら想定内の事態としてユーザーに再入力を求めるなどして処置を継続できます。

その具体的な対応例は Part 2 の[try-catch と try-finally を併用する場合のコード例]のセクションを見てください。

また、「異常事態」で発生した例外の処置については、Part 1 の[例外の処理方法]のセクションを見てください。Windows Forms アプリの場合はそのセクションの「② Windows フォームの場合」のところです。

【疑問2】
更新部分で複数のテーブルに対して処理を行う場合は
UsingでCMD1、CMD2、CMD3等と宣言し、下記のように記述するのはいけないのでしょうか?
やはりCMD1、CMD2、CMD3それぞれにTry-Catchを記述するものなのでしょうか?

何を「業務エラー」として catch するかによると思います。

想定しうる「業務エラー」が、例えば CMD1 でしか起こりえないなら CMD1 だけで良いし、CMD1、CMD2、CMD3 全てで起こりうるなら 3 つまとめて囲えば良いし、いずれでも起こらないなら try/catch そのものが不要です。

なお、【疑問1】に対する答えでも書きましたが、catch したままではダメです。catch したものが「業務エラー」でなければ再スローしてください。

【疑問3】
DB接続時以外の一般エラー
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Sub
これをどこへ書けば正しいのか分からなくなってしまいました。

Exception の中には想定できる「業務エラー」は含まれておらず「異常事態」になるのでは?

であれば、上記の【疑問1】への回答を理解できれば、Exception を catch する必要はない(というより、catch してはいけない)ということが分かりますよね。

上にも書きましたが、「異常事態」で発生した例外の処置については、Part 1 の[例外の処理方法]のセクションを見てください。

また、Using~End Usingで記述するとFainallyは不要だと理解していたのですが、
これも誤った解釈だったのということでしょうか。

確実にコネクションリークを防ぐという目的には、どちらか一方でいいです。詳しくは Part 2 の[IDisposable インタフェースと using ブロックによる try-finally の記述]のセクションを読んでください。

なお、記事では SqlCommand の方は Dispose していませんが、Dispose したほうがいいのは確かなようです。詳しくは以下のページを見てください。まぁ、ほとんど影響はないのかもしれませんが。

SqlCommand の Dispose は呼ぶべきか?
http://surferonwww.info/BlogEngine/post/2013/04/23/whether-to-call-dispose-method-for-sqlcommand-object.aspx

編集 履歴 (0)

Shu さんの指摘のほか、これが原因かどうかは分かりませんが、using, try, catch の使い方が普通じゃないです。

例外処置の書き方の基本については以下の記事が参考になると思いますので、目を通されることをお勧めします。

特に、Part 2 の[トランザクション処理を伴うときの try ブロックの記述]セクションに注目してください。

.NETの例外処理 Part.1
http://blogs.msdn.com/b/nakama/archive/2008/12/29/net-part-1.aspx

.NETの例外処理 Part.2
http://blogs.msdn.com/b/nakama/archive/2009/01/02/net-part-2.aspx

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。