3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。(jehupc.exblog.jpから移転中)

(ADO.Net)DataSetを使ったトランザクション制御

(VB.Net)ADO.NetでOLEDBのトランザクションで、SQL直書き(コマンドオブジェクトにSLQ文をセットする方法)でのトランザクションの方法をメモりました。

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

まず、複数のクライアントが同時にDBにアクセスし不整合になるのを防ぐため、排他制御の仕組みをとらないといけません。

Visual Studio で DataSet を使うとこの仕組みもどうやら簡単に出来るようです。

Visual Studio のサーバーエクスプローラからテーブルを DataSet のデザイナにドラッグすると勝手に DataTable と TableAdpter を作成してくれますが、この時点で勝手に更新時(Update句)に楽観的ロックがかかるようになってます。

下記図のようにテーブルアダプタの詳細を見ると「オプティミスティック同時実行制御」にチェックが入ってると排他制御がかかります。

ただ排他制御といっても楽観的ロックなので、更新時に条件として自身の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 句をみるとこうなっています。

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