「ほっ」と。キャンペーン
(ADO.Net)DataSetを使ったトランザクション制御
(VB.Net)ADO.NetでOLEDBのトランザクションで、SQL直書き(コマンドオブジェクトにSLQ文をセットする方法)でのトランザクションの方法をメモりました。

今回は DataSet で作られた TableAdapter を使ってトランザクションを行う方法です。

まず、複数のクライアントが同時にDBにアクセスし不整合になるのを防ぐため、排他制御の仕組みをとらないといけません。
Visual Studio で DataSet を使うとこの仕組みもどうやら簡単に出来るようです。
Visual Studio のサーバーエクスプローラからテーブルを DataSet のデザイナにドラッグすると勝手に DataTable と TableAdpter を作成してくれますが、この時点で勝手に更新時(Update句)に楽観的ロックがかかるようになってます。
下記図のようにテーブルアダプタの詳細を見ると「オプティミスティック同時実行制御」にチェックが入ってると排他制御がかかります。
e0091163_9213963.jpg

ただ排他制御といっても楽観的ロックなので、更新時に条件として自身のDataSetがデータを取得した時から各フィールドが変更されていなかどうかをチェックしているだけです。
たとえば、上記図のようなテーブル(test2)だと、VisualStudio が作成する排他制御の入った Update 句はこうなります。

UPDATE test2
SET id = @id, name = @name, value = @value
WHERE (id = @Original_id) AND (@IsNull_name = 1) AND (name IS NULL) AND (@IsNull_value = 1) AND (value IS NULL)
OR
(id = @Original_id) AND (name = @Original_name) AND (@IsNull_value = 1) AND (value IS NULL)
OR
(id = @Original_id) AND (@IsNull_name = 1) AND (name IS NULL) AND (value = @Original_value)
OR
(id = @Original_id) AND (name = @Original_name) AND (value = @Original_value)

フィールド数が多いとかなりオーバヘッドがかかりそうなクエリです。

また、テーブルに timestamp 型のフィールド(SQL Serverの場合、timestamp型は行の挿入または更新時に自動的に値が更新されるもの)があると、これを更新時の比較条件に勝手します。
上記 test2 のテーブルにタイムスタンプ型の列 "updatecheck" を追加して、Visual Studio が生成した Update 句をみるとこうなっています。
e0091163_922206.jpg


UPDATE test
SET id = @id, name = @name, value = @value
WHERE (id = @Original_id) AND (@IsNull_updatecheck = 1) AND (updatecheck IS NULL)
OR
(id = @Original_id) AND (updatecheck = @Original_updatecheck)

条件が timestamp の列だけを比較して、他のユーザが先に更新していないかチェックするようになっています。
フィールド数が多いテーブルはこちらのタイムスタンプ型のチェックのほうが、前フィールドチェック型に比べてオーバヘッドは低くなりそうですね。

この辺の詳細はMSDN:オプティミスティック同時実行制御 (ADO.NET)で詳しく説明されてます。

さて、後は排他制御失敗時にロールバックするようにトランザクションをうまくからめるだけです。
トランザクションを開始して、各テーブルを更新。その時、排他制御に引っ掛かったら DBConcurrencyException が発生するので、そこでロールバックを行う。問題なければコミット。という流れになります。
その具体的なコードは下記の通りです。
コネクションとトランザクションのオブジェクトを TableAdpter オブジェクトに紐づけるという点がミソですね。
'テーブルアダプタのインスタンス生成
Dim adpTest As New RhythmSalesProDataSetTableAdapters.testTableAdapter()
Dim adpTest2 As New RhythmSalesProDataSetTableAdapters.test2TableAdapter()
 
Dim cn As SqlClient.SqlConnection = adpTest.Connection
'まずコネクションを開く
cn.Open()
' トランザクション開始
Dim trn As SqlClient.SqlTransaction = cn.BeginTransaction
 
Try
Dim tblTest As RhythmSalesProDataSet.testDataTable = adpTest.GetDataByID(1)
For Each row As DataRow In tblTest.Rows
row.Delete()
Next
'コネクションとトランザクションの指定
adpTest.Connection = cn
adpTest.Transaction = trn
'DB更新
adpTest.Update(tblTest)
 
Dim tblTest2 As RhythmSalesProDataSet.test2DataTable = adpTest2.GetDataByID(1)
For Each row As DataRow In tblTest.Rows
row.Delete()
Next
'コネクションとトランザクションの指定
adpTest2.Connection = cn
adpTest2.Transaction = trn
'DB更新
adpTest2.Update(tblTest2)
'コミット
trn.Commit()
Catch ex As DBConcurrencyException
' 同時実行違反の処理をここでする。。エラーメッセージとか。。。
'ロールバック
trn.Rollback()
Catch ex As Exception
'ロールバック
trn.Rollback()
Throw
Finally
cn.Close()
End Try

[PR]
by jehoshaphat | 2009-05-15 08:03 | .Net開発 | Trackback | Comments(0)
トラックバックURL : http://jehupc.exblog.jp/tb/10243687
トラックバックする(会員専用) [ヘルプ]
※このブログはトラックバック承認制を適用しています。 ブログの持ち主が承認するまでトラックバックは表示されません。


<< 長い間お世話になったの初代PC... Chrome、IE8の流れを受... >>