none
データとデータベースの妥当性検査をどの時点で行うべきか? RRS feed

  • 質問

  • 以下の様なテストコードを作ってみました。
    DBに接続してトランザクションを開始する前にデータの妥当性検査を行い
    かつ、トランザクションの中でも妥当性検査を行っています。
    このコメント
            'ここを実行している時点で別のタスクから社員番号=20003が追加・更新・削除されたら排他制御(データの一意性)が?
    の時点でデータベースの中身が変わっている事も考えられます。
    妥当性検査はトランザクションの前・中それとも両方で行うべきでしょうか?

    また、MessageBox.Show("社員テーブルに社員番号=20003がありません", "追加通知 2")の時点で停止させておいて、
    Microsoft Accessアプリでテーブルの内容を変更したらテーブルは変更できてしまいました。
    Microsoft Accessアプリが排除されるものと思っていましたが??
    で、MessageBox.Showを通過したら例外処理でロールバックされます。
    排他制御を正しく行ったプログラムでないと複数のデータベースを更新するプログラムにおいては
    致命的なデータエラーを作り出す可能性もあります。
    やはり排他制御を検証するのはややこしいです。(@_@)・・・

    データの妥当性検査を行いつつ、かつトランザクションを適応していく方法が他にも有るのでしょうか?

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim i As Integer
            Dim db_dataAdapter As OleDb.OleDbDataAdapter        'データアダプタの定義
            Dim db_dataSet As DataSet = New DataSet("t_社員")   'データセットの定義
            Dim db_dataTable As DataTable                       'データテーブルの定義
            Dim db_dataView As DataView                         'データビューの定義
            Dim db_CommandBuilder As OleDb.OleDbCommandBuilder  'データベースコマンドビルダの定義
            Dim db_dataRow As DataRow                           'データローの定義

            Dim db_transaction As OleDb.OleDbTransaction
            Dim db_command As OleDb.OleDbCommand
            Dim db_cn As OleDb.OleDbConnection = New OleDb.OleDbConnection( _
                "Provider=Microsoft.Jet.OLEDB.4.0;" & _
                "Data Source=C:\test.mdb;")


            'データセットに取得する
            db_dataAdapter = New OleDb.OleDbDataAdapter("SELECT * FROM 社員", db_cn)    'DB内の社員テーブルに接続
            db_dataAdapter.Fill(db_dataSet, "t_社員")                                   'データセットに全てのデータを取得

            'コマンドビルダの作成
            'db_CommandBuilder = New OleDb.OleDbCommandBuilder(db_dataAdapter)

            'DataTableオブジェクトに取得してから結果を出力する
            db_dataTable = db_dataSet.Tables("t_社員")                          'データテーブルにデータセットの内容を取得
            If db_dataTable.Rows.Count = 0 Then                                 ' 取得レコード数のチェック
                MessageBox.Show("社員テーブルにレコードがありません", "通知")   'レコードがなかった時の処理
                Exit Sub
            Else
                For Each db_dataSetTableRow As DataRow In db_dataTable.Rows     'すべての行を参照
                    For i = 0 To db_dataTable.Columns.Count() - 1               'すべての列を参照
                        Debug.Write(db_dataSetTableRow.Item(i) & ControlChars.Tab)
                    Next
                    Debug.WriteLine("")
                Next
            End If

            'データセットから目的のデータを探し無かったら追加する

            ' 主キーの設定
            db_dataTable.PrimaryKey = New DataColumn() {db_dataTable.Columns("社員番号")}
            db_dataRow = db_dataTable.Rows.Find("20003")                '社員番号20003を検索(主キー)

            If db_dataRow IsNot Nothing Then
                MessageBox.Show("社員テーブルに社員番号=20003があります", "追加不可通知 1")   'レコードが存在する時の処理
                Exit Sub
            Else
                MessageBox.Show("社員テーブルに社員番号=20003がありません", "追加通知 1")     'レコードが存在しない時の処理
            End If

            'ここを実行している時点で別のタスクから社員番号=20003が追加・更新・削除されたら排他制御(データの一意性)が?

            Dim s_syainbangou As String

            'DBに接続してトランザクションを開始する
            db_command = db_cn.CreateCommand()
            db_cn.Open()

            db_transaction = db_cn.BeginTransaction()

            'トランザクションを適用する
            db_command.Connection = db_cn
            db_command.Transaction = db_transaction

            '社員テーブルから指定の社員の社員番号を参照する
            db_command.CommandText = "SELECT 社員番号 FROM 社員 WHERE 社員番号 = 20003"
            s_syainbangou = db_command.ExecuteScalar
            MessageBox.Show(s_syainbangou, "社員番号は")
            If s_syainbangou IsNot Nothing Then
                MessageBox.Show("社員テーブルに社員番号=20003があります", "追加不可通知 2")   'レコードが存在する時の処理
                db_cn.Close()
                Exit Sub
            Else
                MessageBox.Show("社員テーブルに社員番号=20003がありません", "追加通知 2")     'レコードが存在しない時の処理
            End If

            db_command.CommandText = "INSERT INTO 社員 VALUES(20003, '新たな 方', '33')"
            'db_command.CommandText = "UPDATE 社員 SET  氏名 = '名前 変更' , 所属= '44'  WHERE 社員番号 = 30020"
            'db_command.CommandText = "DELETE FROM 社員 WHERE 社員番号 = 30010"

            'DBを更新し、トランザクションを確定または中止する
            Try
                db_command.ExecuteNonQuery()
                db_transaction.Commit()
                MessageBox.Show("参照・追加・更新・削除を終了しました。", "通知")
            Catch ex As Exception
                db_transaction.Rollback()
                MessageBox.Show(ex.Message, "エラー")
                MessageBox.Show("ロールバックしました。", "通知")
            End Try
            db_cn.Close()

        End Sub
        'using (SqlTransaction tran = con.Begin.Transaction())
        '{
        '    try {
        '        //SQLによるデータベース更新処理
        '       :
        '        //コミット
        '        tran.Commit();
        '    }
        '    catch {
        '        //ロールバック
        '        tran.Rollback();
        '    }
        '}

        '    using (TransactionScope tran = new TransactionScope()) {
        '    //SQLによるデータベース更新処理
        '   :
        '    //コミット
        '    tran.Complete();
        '}

     

    2010年4月28日 5:49

回答

  • 考え方として、DataSetは楽観的同時実行制御です。つまり、データベースの一部をクライアントのパソコンにコピーし、そこで操作を行い、この操作の結果をデータベースに書き戻します。この書き戻す時に他の誰かによって値が変えられていたら仕方ない、そこでエラーを通知するというものです。
    したがって、トランザクションはデータベースへ書き戻す部分のみです。自分が操作している間に他の誰かがデータベースの値をいじることを認めているのが楽観的同時実行制御です。
    結果として、書かれているコードの流れで良いと思いますよ。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答の候補に設定 山本春海 2010年4月30日 4:20
    • 回答としてマーク 若葉 2010年4月30日 6:29
    2010年4月28日 6:34
    モデレータ
  • 前にもレスしましたように、楽観的同時実行制御を実装してはいかがですか?

    DataSet/DataTable に取ってきて、中身を見てどうするか決めて、実行するとい
    うようなケースに見えますが、その間ロックしてしまうのも問題がありそうな感
    じがします。

    以下のページの「楽観的同時実行制御の勧め」が参考になると思います(ちょっ
    と日本語が変ですが)。

    DB 設計者のための明解 ADO.NET 第 1 回
    http://msdn.microsoft.com/ja-jp/events/dd231281.aspx

    • 回答の候補に設定 山本春海 2010年4月30日 4:21
    • 回答としてマーク 若葉 2010年4月30日 6:29
    2010年4月29日 7:35

すべての返信

  • 考え方として、DataSetは楽観的同時実行制御です。つまり、データベースの一部をクライアントのパソコンにコピーし、そこで操作を行い、この操作の結果をデータベースに書き戻します。この書き戻す時に他の誰かによって値が変えられていたら仕方ない、そこでエラーを通知するというものです。
    したがって、トランザクションはデータベースへ書き戻す部分のみです。自分が操作している間に他の誰かがデータベースの値をいじることを認めているのが楽観的同時実行制御です。
    結果として、書かれているコードの流れで良いと思いますよ。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答の候補に設定 山本春海 2010年4月30日 4:20
    • 回答としてマーク 若葉 2010年4月30日 6:29
    2010年4月28日 6:34
    モデレータ
  • 前にもレスしましたように、楽観的同時実行制御を実装してはいかがですか?

    DataSet/DataTable に取ってきて、中身を見てどうするか決めて、実行するとい
    うようなケースに見えますが、その間ロックしてしまうのも問題がありそうな感
    じがします。

    以下のページの「楽観的同時実行制御の勧め」が参考になると思います(ちょっ
    と日本語が変ですが)。

    DB 設計者のための明解 ADO.NET 第 1 回
    http://msdn.microsoft.com/ja-jp/events/dd231281.aspx

    • 回答の候補に設定 山本春海 2010年4月30日 4:21
    • 回答としてマーク 若葉 2010年4月30日 6:29
    2010年4月29日 7:35
  • trapemiyaさん、SurferOnWwwさん

    ご返事ありがとうございます。

    > DB 設計者のための明解 ADO.NET 第 1 回
    > http://msdn.microsoft.com/ja-jp/events/dd231281.aspx

    ここの内容が良くまとめられていますね。
    初心者には抽象的に書かれていると感じるかもしれませんが
    私にはかなり良く説明されていると感じました。
    でも、まだまだ、私は全体が理解できたとは言い難いでしょうね。「戦いはまだまだ続く!!」

    2010年4月30日 6:29