none
楽観的排他制御についての疑問があります。 RRS feed

  • 質問

  • 排他制御(悲観的ロック・楽観的ロック)について

    楽観的排他制御についての疑問:

    A、B二台のクライアントPCがあります。
    この二台のPCが、あるデータ(レコード)を表示します。
    それぞれ、自己のPC内のデータテーブルを基に表示がされているはずです。

    Aがこのレコードを削除します。
    Bは、それ以前に作成したデータテーブルを元にしています。Bはこのレコードに修正を加え
    UPLoadします。この時点で、サーバーにはこのデータは存在しません(削除されていますから)。

    Bは、変更は正常に行われたと信じて、終了します。
    しかし、レコードは削除されていますから、この変更自体は存在しないわけで、Bの期待は裏切られます。

    私のサーバーでは、存在しないデータの書き込み変更は当然受け付けず、なんのエラーコメントも出しません。

    楽観的排他制御では、このような場合にどのように対処するのでしょうか?
    私のDB(サーバー)設定で、対処すべきなのでしょうか?


    • 編集済み yksaila 2012年12月4日 2:54
    2012年12月4日 2:51

回答

  • 楽観的排他制御では、変更する際に自分が変更前に取得したレコードと変わっていないことを期待して変更を行います。
    例えばあるテーブルTが、ID, 列A, 列B, 列Cから構成されているとします。
    変更のためにあるレコードを取得したとします。
    select 列A, 列B, 列C from テーブルT where ID = 10
    この時の列A, 列B, 列Cの値をそれぞれ、a, b, cとします

    このユーザーが列Bの値をb0に変えました。update文は以下のようになります。
    update  テーブルT set 列B = b0 where ID = 10 and 列A = a and 列B = b and 列C = c

    もし、このユーザーがこのレコードを取得してから他のユーザーがこのレコードを変更していなければ、列A, 列B, 列Cの値はそれぞれ、a, b, cのままですから、上のupdate文は成功します。しかし、他のユーザーがこのレコードを変更していれば、where句で指定されたレコードは無いのでupdate文で変更されたレコードの件数は0件になります。
    この0件をもって、更新に失敗したことがわかります。レコード自体が削除された場合も0件になりますから、同じく更新に失敗したことがわかります。

    さて、今回の例は変更される可能性がある列が3つしかなかったのでSQL文が短かったのですが、もし、30個も列があればかなり長いSQL文になります。これを解消するために、SQL ServerにはTIMESTAMP型という便利な列を作成することができます。TIMESTAMPはシーケンス値であり、レコードが更新される度にカウントアップされます。つまり、もしテーブルTにTIMESTAMP型の列TMSTPがあれば、以下のように簡単に書けます。
    update  テーブルT set 列B = b0 where ID = 10 and TMSTP = レコード取得時にしまっておいたTMSTP列の値

    楽観的排他制御の考え方はシンプルです。自分が取得したあるレコードにおける取得した全ての列が変更されていないことを期待して、update文を発行するというだけのものです。ですから、サーバー側の設定ではありません。


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


    2012年12月4日 3:15
    モデレータ
  • TIMESTAMP値は8バイトのbyte配列ですので、
    Public TMSTP_Byte As Byte()
    と、宣言してみて下さい。

    >(長引くようでしたら、新規スレッドに移した方がいいですか?)

    ご質問のタイトルから外れていないので、とりあえずこのままで良いと思います。

    #予期しないエラーで返信の書き込みができていませんでした。

    #ご存じだと思いますが、TMSTPは私が適当に付けた名前です。私が適当に書いてしまったので反省してます。「タイムスタンプ」とか日本語の列名の方がわかりやすいと思います。
    ついでにROWVERSION(SQL Server 2005から追加)の話が出ましたが、マイクロソフト的にはこれを推しているようなんですが、なぜかSQL Server Management Studioでは相変わらず使うことができません(SQL Server 2012対応版では試していません)。もちろん、DDLでは使用できます(CREATE TABLE文など)。
    ちなみにROWVERSION型は、SQL Server Management StudioではTIMESTAMP型と表示されますし、ここからテーブル作成のスクリプトを書き出すと、TIMESTAMP型でCREATE TABLE文が作成されてしまいます。というわけで、SQL Server Management Studio上ではROWVERSION型は無いことになっているようです・・・。
    tsequal関数もSQL Server 2005から削除されたことですし、やはりROWVERSIONを使うべきなのかなぁと思うこともあるのですが、SQL Server Management Studioの挙動に合わせて、私はやはりTIMESTAMP型を使ってしまいます。ADO.NET的にも単なるBYTE配列での扱いになってしまうため、この辺りは全体的に統一が取れていない気がします。
    TIMESTAMPとROWVERSIONについて、何か他に情報をお持ちの方がいらっしゃいましたら、教えていただけると幸いです。

     


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

    • 回答としてマーク yksaila 2012年12月5日 5:55
    2012年12月5日 4:48
    モデレータ

すべての返信

  • 楽観的排他制御では、変更する際に自分が変更前に取得したレコードと変わっていないことを期待して変更を行います。
    例えばあるテーブルTが、ID, 列A, 列B, 列Cから構成されているとします。
    変更のためにあるレコードを取得したとします。
    select 列A, 列B, 列C from テーブルT where ID = 10
    この時の列A, 列B, 列Cの値をそれぞれ、a, b, cとします

    このユーザーが列Bの値をb0に変えました。update文は以下のようになります。
    update  テーブルT set 列B = b0 where ID = 10 and 列A = a and 列B = b and 列C = c

    もし、このユーザーがこのレコードを取得してから他のユーザーがこのレコードを変更していなければ、列A, 列B, 列Cの値はそれぞれ、a, b, cのままですから、上のupdate文は成功します。しかし、他のユーザーがこのレコードを変更していれば、where句で指定されたレコードは無いのでupdate文で変更されたレコードの件数は0件になります。
    この0件をもって、更新に失敗したことがわかります。レコード自体が削除された場合も0件になりますから、同じく更新に失敗したことがわかります。

    さて、今回の例は変更される可能性がある列が3つしかなかったのでSQL文が短かったのですが、もし、30個も列があればかなり長いSQL文になります。これを解消するために、SQL ServerにはTIMESTAMP型という便利な列を作成することができます。TIMESTAMPはシーケンス値であり、レコードが更新される度にカウントアップされます。つまり、もしテーブルTにTIMESTAMP型の列TMSTPがあれば、以下のように簡単に書けます。
    update  テーブルT set 列B = b0 where ID = 10 and TMSTP = レコード取得時にしまっておいたTMSTP列の値

    楽観的排他制御の考え方はシンプルです。自分が取得したあるレコードにおける取得した全ての列が変更されていないことを期待して、update文を発行するというだけのものです。ですから、サーバー側の設定ではありません。


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


    2012年12月4日 3:15
    モデレータ
  • 楽観的排他制御の仕組みはtrapemiyaさんが回答されていますので、割愛させて頂いて、

    1.DBサーバー側が準備する事は、
      1-1. レコードに変更があることが分かる仕組みづくり。
        (trapemiyaさんが触れられている、TIMESTAMP型等が便利...。)

    2.プログラム側で必要なことは、
      2-1. 取得時点とレコード変わっていないことをチェック。
      2-2. 更新処理

    となります。

    とりあえずは、今回の問題は、
    DBの設定ではなく、プログラム側の処理となるでしょう。

    但し、DBアクセスに関するプログラムを「共通化する」かどうかは、
    システムの作り方次第ですね。

    今回の場合、2-1のチェックが抜けているのではないでしょうか?
    チェックした結果、レコードが無いわけですから、それに対して
    処理が必要です。
    どういった処理をするかはそのプログラムの仕様次第です。

    2012年12月4日 3:40
  • trapemiyaさんへ

    ありがとうございます。 良く分かりました。 なるほど、この方法の方がシンプルで綺麗かもしれません。

    早速、サーバーの設定を変更してやってみようと思います。 

    再度、確認と質問です。

    1.UpDate時--直前にSelectで(レコード取得時)に、TMSTP列の値を取得しておくのですね。 

    データテーブル使用の場合は、その時点でTMSTP列の値も取得ですね。

     (分かり切ったことを書いてすいません。 無視してください。)

    2.update文で変更されたレコードの件数=0件は、どうやって確認しますか?

     これが、わかればコメントも出せます。

    3.どのPCで、どのフォームで使用中かは分からなくても良いのですね。

    "失敗" と、コメント表示さえでれば、再挑戦するだけですね。

    YKsaila

            

    2012年12月4日 3:42
  • 1.についてはその通りです。

    2.については、SQL文を発行したメソッドの戻り値を見ます。

    SqlCommand.ExecuteNonQuery メソッド
    http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlcommand.executenonquery(v=vs.80).aspx

    TableAdapterやSqlDataAdapterを使用している場合でも同様です。戻り値が処理したレコード数になっています。

    3.については、大抵はユーザーに対して再挑戦を促しますが、場合によっては上書きも可能なようにユーザーに選択させることもあります。


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

    2012年12月4日 3:51
    モデレータ
  • trapemiyaさんへ

    うまくいきません。 挿入コードが間違っていますか?

    以下、コードです。

    Dim SQLst as string
    SQLst---Update文が、ここにきます。

    Dim sql As New System.Data.SqlClient.SqlCommand(SQLst, Con)
    sql.Parameters.AddWithValue(----)--更新列数だけ続きます。
    sql.ExecuteNonQuery()

    上記コードで正常に更新しています(一件のみの更新です)。

    更新件数を確認しようと、上記”sql.ExecuteNonQuery()”の代わりに
    下記二行を入れました。

    Dim n As Integer
    n = sql.ExecuteNonQuery()

    これでも、正常に更新されています。

    しかし;
    nの箇所で止めて調べると、0と表示します。 当然、1のはずなのですが?

    どこが間違いなのでしょうか?

    YKsaila

    2012年12月4日 12:06
  • SQL文が間違っている可能性が高いです。そして1行も更新されなかったために0が返ってきているかと。
    2012年12月4日 13:22
  • trapemiyaさんへ

    失礼しました。

    止める位置が一行早かったようです。

    ちゃんと、1になりました!

    YKsaila

    2012年12月4日 13:43
  • 佐佑理さんへ

    コードを止める位置が一行早かっただけでした。 私のミスでした。

    以下のように、遊びで実験用に、コードを入れてみました。

    正しく表示しています。

    Where以降の条件式を二通り書いてみて実験;

     SQLst &= "WHERE Sales_ID = @Sales_ID "

    (A):sql.Parameters.AddWithValue("@Sales_ID", 9999999) '--存在しないSales_ID

    (B):sql.Parameters.AddWithValue("@Sales_ID", CInt(Me.TX_Sales_ID.Text))  ' 存在するSales_ID

            Dim n As Integer
            n = sql.ExecuteNonQuery()
            If n = 0 Then
                MessageBox.Show("更新できませんでした!")  '(A)
                Exit Sub
            Else
                MessageBox.Show("更新できます!")            '(B)
            End If

    YKsaila


    • 編集済み yksaila 2012年12月4日 14:16
    2012年12月4日 14:14
  • trapemiyaさんへ

    前回の書き込みの(1).(レコード取得時に、TMSTP列の値を取得しておく)ですが、やってみると意外と難しいです。

    修正(Update)時に、そのレコードのTimeStamp値を取得するために、下記のようにやってみたのですが、どうでしょうか? (*)の行でエラーがでます。

    それと、この方法でよろしいでしょうか?

    'TimeStamp--Moduleで、定義。 Public にする。---Updateのコード内には、TMSTPがないから。
     Public TMSTP_Byte As Byte
       
    'フォームを開くごとにTimeStampを取得する(表示するレコードに応じて)。
    ’dtは、データテーブル。 nは、dtの列インデックス。 TMSTPは、サーバー内のTimeStmp列(Binary値)で、dt内に存在。
    TMSTP_Byte = CByte(dt.Rows(n).Item("TMSTP"))  ’----(*)

    以上を準備し、下記のコードを実行。
     ------略
     SQLst &= "WHERE (Client_ID = @Client_ID) and (TMSTP = @TMSTP) "
     sql.Parameters.AddWithValue("@Client_ID", NZint(Me.TX_Client_ID.Text))
     sql.Parameters.AddWithValue("@TMSTP", TMSTP_Byte)
                'sql.ExecuteNonQuery()

                Dim n As Integer
                n = sql.ExecuteNonQuery()
                If n = 0 Then
                    MessageBox.Show("更新できませんでした!")
                    Exit Sub
                ’Else
                '   MessageBox.Show("更新できます!")
                End If

    (*)の行でエラー表示します。
    エラー表示---型 'Byte()' から型 'Byte' への変換は無効です。

    上記、どのように修正したら良いのでしょうか?

    (長引くようでしたら、新規スレッドに移した方がいいですか?)

    YKsaila

    2012年12月5日 3:17
  • rowversion(trapemiyaさんはtimestampと呼んでいる)はSQL Server上はbinary(8)で、.NETではByte配列です。

    エラーメッセージぐらい読んで問題を理解してください → 型 'Byte()' から型 'Byte' への変換は無効です。

    2012年12月5日 4:12
  • TIMESTAMP値は8バイトのbyte配列ですので、
    Public TMSTP_Byte As Byte()
    と、宣言してみて下さい。

    >(長引くようでしたら、新規スレッドに移した方がいいですか?)

    ご質問のタイトルから外れていないので、とりあえずこのままで良いと思います。

    #予期しないエラーで返信の書き込みができていませんでした。

    #ご存じだと思いますが、TMSTPは私が適当に付けた名前です。私が適当に書いてしまったので反省してます。「タイムスタンプ」とか日本語の列名の方がわかりやすいと思います。
    ついでにROWVERSION(SQL Server 2005から追加)の話が出ましたが、マイクロソフト的にはこれを推しているようなんですが、なぜかSQL Server Management Studioでは相変わらず使うことができません(SQL Server 2012対応版では試していません)。もちろん、DDLでは使用できます(CREATE TABLE文など)。
    ちなみにROWVERSION型は、SQL Server Management StudioではTIMESTAMP型と表示されますし、ここからテーブル作成のスクリプトを書き出すと、TIMESTAMP型でCREATE TABLE文が作成されてしまいます。というわけで、SQL Server Management Studio上ではROWVERSION型は無いことになっているようです・・・。
    tsequal関数もSQL Server 2005から削除されたことですし、やはりROWVERSIONを使うべきなのかなぁと思うこともあるのですが、SQL Server Management Studioの挙動に合わせて、私はやはりTIMESTAMP型を使ってしまいます。ADO.NET的にも単なるBYTE配列での扱いになってしまうため、この辺りは全体的に統一が取れていない気がします。
    TIMESTAMPとROWVERSIONについて、何か他に情報をお持ちの方がいらっしゃいましたら、教えていただけると幸いです。

     


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

    • 回答としてマーク yksaila 2012年12月5日 5:55
    2012年12月5日 4:48
    モデレータ
  • trapemiyaさんへ

    使用サーバーは、SQL2003 R2 です。

    このサーバーのテーブルデザインで、新規に列を追加し、列名を"TMSTP"としました。 データ型に、TimeStampがありましたので、これを選択しました。 サーバーのこのテーブルを開くと、列は<Binary>となっています。

    質問です;

    この<Binary>は、列TMSTPはサーバー上では、二進数で表示されているということですか?

    .NETでByte配列ということは、データテーブルでの列(TMSTP)が、二進数なのでしょうか?

    この二者は、データ型が異なるのですね。

    でしたら、この両者の型を相互に変換してやれば、うまくいくと考えたのですが?

    Public TMSTP_Byte As Byte() としましたが、やはり上記の(*)の箇所でエラーコメントが出ます。

    (エラーコメント: 型 'Byte' の値を 'Byte の 1 次元配列' に変換できません。)

    データテーブル内に来ているはずの”TMSTP”の値を、なんとかしてTMSTP_Byteに持ってこれればよいのですが?

    別の方法も考えました;

    フォーム内にTMSTPを表示するテキストボックスを設け、ここにTMSTPを表示して使用する。

    データテーブル内のTMSTPの値が二進数なら、これを十進数に変換し、UPDate時にはこれをまた二進数に戻して

    これをWhere以下の条件式で使うことも考えました。

    しかし最終的にはサーバー上のデータ型が<Binary>なので、これにもどしてやらないと有効に使えないですね?

    要するに、サーバー上の<Binary>と データテーブルのByteの相互の型変換ができれば良い、ということでしょうか?

    この型変換の方法が知りたいのですが?

    YKsaila

    2012年12月5日 6:35
  • tramemiyaさんへの返信に、関連事項を記入しました。

    ご教示をお願い出来れば、ありがたいです。

    よろしく、お願いします。

    YKsaila

    2012年12月5日 6:36
  • TIMESTAMP値は8バイトですから、.NETではバイト配列で受け取る必要があります。つまり、
    TMSTP_Byte = CByte(dt.Rows(n).Item("TMSTP"))  ’----(*)

    TMSTP_Byte = CType(dt.Rows(n).Item("TMSTP"), byte())
    に変える必要があります。
    そのため、TMSTP_Byteの定義をbyte()に変えました。
    SQL Server上と.NET上ではデータ型は違いますが、どちらも8バイトで同じ値です。.NET上ではByte配列に入れ、そのまま8バイトの値として取り扱って下さい。

    ちなみに、SQL2003 R2 というのはありません。おそらく、Windows Server 2003 R2のことでしょうか?
    SQL Serverのバージョンには2003はありません。


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

    2012年12月5日 6:53
    モデレータ
  • trapemiyaさんへ

    ありがとうございます。

    TMSTP_Byte = CType(dt.Rows(n).Item("TMSTP"), byte()) として、成功しました!

    Binary と Byte() についても少し勉強しました。 違いを十分には、理解していなかったようです。

    教わってみて、はじめて 「あっ、そうか。 こうすれば、いいんだ。」 と納得しました。 意外と気づかないものですね、初心者だと。

    サーバーは、おっしゃるとおり、Windows SerVer 2003 R2 です。 書き間違えました。

    基本は理解し、土台もできましたので、今までの排他処理をこの方式に作り替えます。

    以前からTimeSamp方式が気にはなっていたのですが、良く分からなかったので、従来の自分の出来る排他処理でコードを書いていました。 全体の10%(フォームが4個)くらいが出来ている状態ですから、まだ良かったです! 完成してからの排他処理の変更は大変ですから。 

    今から、この変更作業開始です。 2,3日は、かかるでしょう(合間を見ての作業ですから)。

    うまくいくと、嬉しいです。

    今後も、よろしくお願いします。

    YKsaila

    2012年12月5日 12:16
  • 最後に一つ注意点です。それは、TIMESTAMP型(ROWVERSION型)を使う方式が万能ではないということです。しかし、大抵の場合はTIMESTAMP型を使う方法でうまくいきます。
    良くないケースを挙げます。
    例えば、100列あるテーブルがあるとします。このうち、ある画面Aでは1番目と2番目の列しか扱いません。つまり、1番目と2番目の列しか更新する可能性がありません。この画面Aが開いている状態で他のユーザーが99番目の列を変更しました。この後、画面Aで更新処理を行えば、当然、TIMESTAMP型方式では同時実行制御エラーになり、更新に失敗します。画面Aでは1番目と2番目の列しか更新対象ではないので、99番目の列が他のユーザーに変更されても一向に構わないのにです。この場合、このユーザーが画面Aを開きなおすと、1番目と2番目の列は全く変わっていないことに気付くことになります。
    これは明らかにおかしな動作です。
    よって、この場合は、TIMESTAMP型方式を使わない方がうまく行きます。つまり、私が先の例で挙げたような、以下のようなwhere句でupdateを行うことになります。
    where @id = id and @一番目の列 = 先に取得した一番目の列の値 and @二番目の列 = 先に取得した二番目の列の値

    楽観的排他制御の基本は、自分がこれから更新しようとしている全ての列のみが、先に取得した値と変わっていないことを期待して更新を行うことです。TIMESTAMP型を使う方法は、それを横着して、自分が取得してからそのレコードが誰にも変更されていないことを期待して更新を行うようにしてしまっていることです。いや、大抵の場合は横着ではなく、効率的な方法です。今回の例で挙げたようなケースの場合は横着になるでしょう。


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

    2012年12月5日 15:28
    モデレータ
  • trapemiyaさんへ

    ありがとうございます。 よく、理解しました。 参考になりました。

    更新列が少ない場合は、例示していただいた方式で行うことにします。

    なお、通常は下記のコメントを出すようにしました。

    1、更新の場合:

    Dim n As Integer
    n = sql.ExecuteNonQuery()
    If n = 0 Then
       MessageBox.Show("更新できません!  他のPCで使用されました、再試行してください( 他データへ移行し、戻れば再試行できます )。")
       Exit Sub
    End If

    2.削除の場合:
    Dim n As Integer
    n = sql.ExecuteNonQuery()
    If n = 0 Then
      MessageBox.Show("削除できません! 他のPCで使用されたか、または既に削除されています。")
    End If

    TimeStamp方式は、速い者勝ちなんですね(先にUpdateしたほうを優先、アクセス順ではない!)。

    私が以前に採用していた方法は、後からアクセスする者を待機させます(アクセス順というか、アクセスさせない!)。

    なぜ、楽観的排他制御というかの意味も理解できました。

    Yksaila

    2012年12月6日 1:31
  • TimeStamp方式は、速い者勝ちなんですね(先にUpdateしたほうを優先、アクセス順ではない!)。

    私が以前に採用していた方法は、後からアクセスする者を待機させます(アクセス順というか、アクセスさせない!)。

    なぜ、楽観的排他制御というかの意味も理解できました。

    用語が違うので指摘を。

    TimeStampは楽観的排他制御を実現するための手段の1つであり方式というほどではありません。
    楽観的排他制御が早い者勝ちです。その反対、後勝ちの場合は排他制御をせず常に上書きすれば実現できます。
    Yksailaさんが以前に採用していた方法というのは悲観的排他制御の部類に入ります。

    2012年12月6日 1:45
  • 佐佑理さんへ

    >後勝ちの場合は排他制御をせず常に上書きすれば実現。

    排他制御をしなくても良い、とのお考えですか?

    常に上書きの場合ですが、下記のような状況では問題が起きますね。

    あるレコード(データ行)の特定の列で、

    Aさんは、aならばXを入力します。

    Bさんは、aならばYを入力しますが、もしa以外のデータが入力されているなら、そのままにしておきたいと考えています。

    さて、Aさん、Bさんは各自のPCの前で入力します。 同じデータにアクセスし、先にAさんが入力します。 この後に、Bさんが入力です。

    Bさんの入力後の値は、Y に、なっています。 しかし、Bさんの意図とは異なる結果となっています。 Bさんの意図は、Xのはずです。

    この事例からも分かるように、複数のPCでの修正・削除・登録操作では、同一データ(レコード)への同時アクセスを禁止するか、またはデータが変更になっていることをクライアントに知らせることが重要なのではないでしょうか?

    この対処をしないと、データが膨大になればなるほど、異常の発見が困難になります。 また、異常が発生していることが使用者に認識されていないということ自体が、もっと恐ろしいことなのでしょう!

    YKsaila

    2012年12月7日 2:01
  • 早い者勝ちに対して反対の概念を説明しただけです。

    あらゆる環境において後勝ちが適用できるなどとは言っていません。

    2012年12月7日 4:03
  • 佐佑理さんへ

    そうでしたか、了解しました。

    YKsaila


    • 編集済み yksaila 2012年12月7日 4:49
    2012年12月7日 4:49
  • ここから先の記事は、主として下記のスレッドでの記事・回答が参考になります。

    「サブテーブルでのTimestamp値取得は、どうすればよいですか?

    上記二行、12/18書き込み

    12/08 追加書き込み

    12/08 に新規書き込みをしました。 そちらの方が、分かりやすいです。

    以下は、周辺状況の理解に役立ててください。 多分、不要でしょうが。

    12/08 追加書き込み、終わり。

    TimeStamp(ここでは、TMSTP)に関する質問です(長文になりました、すいません):フォーム内のDataGridviewについてです。

    売上伝票(F_I_Sales)があり、その中に売上詳細(売上細目)があります。
    一枚の売上伝票には、複数の売上品目が書かれており、それが売上詳細です。


    フォーム(F_I_Sales)内に DataGridViewを配置し、これを売上詳細の表示に充てています。
    F_I_Salesに対応するテーブルは T_Salesです、DataGridView(売上詳細)に対応するテーブルは T_SalesDetailsです。


    修正・変更時には、DataGridView行の追加・削除が発生します。


    DataGridViewには、これに対応したボタンをF_I_Sales内に配置しています(行番号が狂いますので、番号を揃える機能も追加)。
    実験使用している中で不都合が発生するので、行の追加・削除・行番号揃では、
    まず現データをDB(サーバーのテーブル)に書き込む →こののちDB内で削除等を実施 → 再度データテーブルに呼び込む。
    という、操作をしています。

    売上詳細(DataGridView)のDBサーバーへの書き込みについてですが;
    もちろん、DataGridView(または、データテーブル)内のみでの行追加・削除等の方法も考えられたのですが、
    (実は、この方法に詳しくない、というのも理由の一つです)
    こうすると、データ書き込み時に、UpdateとInsert(行追加)が混在します。
    これを解決するために、行追加時には即DBサーバーに書き込み、以降は全てUpdateで済ませるようにしています。

    Update/Insertが混在するときに、一旦DBの全データを削除し、その直後にDataGridViewのデータを全てDB(サーバー)にInsertすることも
    考えたのですが、この方法は採用すべきでない、実態に合わせようとして上記のようにしました。
    <また、他に保存法が無い状態で削除するのは、どうかな(危険?)、とも思ったのです。>

    さて、本題です。
    こうしますと、DataGridView内のTimeStamp値は、当初の値でなくなることも発生します。

    Form_Load時にDataGridViewの各行のTimestamp値(当初の値)を記憶させておきます。 
    行の追加・削除・行番号揃をしないときは、修正ボタンは正常に使用できます。

    しかし、行の追加・削除・行番号揃をしますとエラーがでたり、不都合が出ます。Updateが発生するからです。
    なので、行の追加・削除・行番号揃時には、TimeStamp値は持ってこないようにしています。
    これを持ってくれば、当然自前のTimeStamp値ですから書き込み可能なのは当たり前で、コード自体が無意味になります(1=1 というのと同じですから)。

    行の追加・削除・行番号揃時ですが;
    DataGridviewの各行の当初のTimeStamp値をForm_Load時に取得することは出来ています。
    しかし、これを保存し”修正ボタン”(行の追加・削除・行番号揃時)で使用する方法が分かりません。Publicでは、保存できませんでした。

    また、これができても、行の追加・削除時で、Form_Load時に保存したTimeStamp値の初期値をそこに正しく復旧出来るのか、大いに不安になって苦しんでいます。
    方法が難しいです(または、良くわかっていない、経験不足?)。


    当初は、T_SalesDetailsは、T_Salesにユニーク(unique)に従属していると判断し、T_SalesのみでTimestamp値を使用しました。
    簡単で楽でした。通常は、これでいいのだと思います。無駄なことをする必要はないですから。

    しかし、もしT_SalesDetailsに、他のフォーム(テーブル)からアクセスするのであれば、このT_SalesDetailsにもTimeStamp値を使用しようと考えたのです。

    内容からしてあり得ないかもしれませんが、状況によっては上記のことも考えておくべき、と思ったのです。
    フォーム内に配値したDataGridView(実際は、これに対応するテーブル)に、複数のフォームからアクセスする可能性があるときは、何らかの対応措置が必要です。

    このようなときは、別方法で排他制御したほうがよいでしょうか? 例えば、私の以前の方式で。
    滅多にないような場合かもしれませんが、あり得ないことでもなさそうに思えたのです。
    他に方法があるでしょうか? それとも、不要?

    当面は、DataGridViewつまり対応するテーブルにはTimeStampは使用せず、この状況が発生したときに別方法で考えるのが現実的でしょうか?


    YKsaila



    • 編集済み yksaila 2012年12月19日 1:11
    2012年12月7日 10:37
  • フォーム内にあるサブフォーム(DataGridView)への排他制御について、です。

    丸一日考えて、下記のような結論に達しました。 排他制御は、やはり複雑で大変です。 でも、面白い!

    (私は、この問題は嫌いではありません、疲れますが。)

    他の方法もあるでしょうが、私見では下記の方法も悪くない気がしています。 いかがでしょうか?


    DataGridViewでの、TimeStamp値(仕様書?)

    DataGridViewでの、行追加・削除・行揃え対策:下記の①②③は、全てServerに対する行為。

    A:Form_Load時に、DataGridViewの元データであるテーブル(Server)から、<TimeStamp初期値>を取得しておく。


    1.行追加
      ①Insert(行追加) -- Server Table に追加(行番号=現在行の最大番号+1)。 

     ②Update -- 現書き込みデータをDB(Server)に書き込む。Update文のWhere条件に <TimeStamp初期値>を追加しておく。
                  他のPCからの書き込みがこのUpdate(行追加)前にあれば、TimeStamp値が異なるので、このUpdate実行失敗。
           コメント表示して、Exit。 
           正常にUpdateされれば、この時点で新規TimeStamp値がServer上で更新される。 
            
      ③Select -- DataGridViewでの、追加行表示(②でUpdateされたデータも含めて)
           ここで、更新された新TimeStamp値取得(Form_Load時のTimeStamp初期値ではない)。
           なお、追加行にデータ書き込みしなくてもTimeStamp値は発行されます。 実験して確認済み。

    2.行削除
     ①Update -- 上記1の②と同じ(但し、Update(行追加)前 → Update(行削除)前 と読み変える)。

     ②Delete(行削除) 
           
      ③Select -- DataGridViewでの表示(削除行を除いて表示)
           ここで、更新された新TimeStamp値取得(Form_Load時のTimeStamp初期値ではない)。

    3.行番号揃え
     ①Update -- 上記1の②と同じ(但し、Update(行追加)前 → Update(行番号揃え)前 と読み変える)。

     ②Update(行番号揃え)-- ここでは、行番号(LineNo)のみの処理。再度、TimeStamp値更新。
           
      ③Select -- DataGridViewでの表示(行番号が順揃えになった状態で表示)
           ここで、再度更新されたTimeStamp値取得(Form_Load時のTimeStamp初期値ではない)。

    以上で、DataGridViewでの、<行追加・削除・行揃え>が、終了。


    フォーム全体の ”Update” 実行 ;
     フォームを開いている時間中に、DataGridView (の対応するDBテーブル)へ、他のPCから(同じフォームだけでなく、全く別種のフォームからも含めて)
     の書き込みがあっても、上記で対応しています(1の②、2の①、3の① で)。

    この方法でいければ、万全だと思います。


    問題点:Aで取得したTimeStamp初期値(複数あります、For--Next で取得)を、上記( 1の②、2の①、3の① )のUpdate文で使いたいのですが、
        持ち込み方が分かりません。

        Form_Load時に取得したDataGridViewのTimeStamp初期値(複数個)を上記Update文のWhere条件に書ければ、全てが解決します。

    上記問題点への、ご回答(ヒント?)をいただければ、幸いです。

    YKsaila

     


    • 編集済み yksaila 2012年12月19日 1:11
    2012年12月8日 2:05
  • yksailaさんへ

    モデレータの一人として書き込ませていただきます。
    まず、申し訳ないですが、「trapemiyaさんへ」と個別に質問を投げかけることは控えていただけませんでしょうか? 個別に呼びかけることによって回答者へ負担を強いる可能性がありますし、他の人が回答しにくくなります。もちろん、回答者の回答に対する返答であれば、この限りではありません。ポイントとしては、常に多くの回答者がyksailaさんの味方だと考えて質問されると良いと思います。
    もちろん、私もその一人として、できるだけ回答させていただきます。

    また、できるだけスレッドが長くならないようにご配慮下さい。質問に区切りが付いたら、いくら関連した質問であっても新しいスレッドを起こすようにして下さい。長いスレッドは後から見た人に読まれない可能性が高く、それだけ的確な回答が得られる可能性を減らしてしまいます。新しいスレッドは多くの回答者の目に留まり、それだけ早く的確な回答が得られる可能性が高まります。また、情報共有という面からも、長いスレッドは読みにくく、検索してこのスレッドに辿り着いた人にとって、わかりにくいものになります。

    ご理解いただけたら、新しいスレッドで、この問題について再度ご質問願えませんでしょうか? お手数をおかけしますが、よろしくお願いいたします。


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


    2012年12月8日 14:49
    モデレータ
  • > フォーム内にあるサブフォーム(DataGridView)への排他制御について、です。

    > ・・・中略・・・

    > DataGridViewでの、TimeStamp値(仕様書?)

    DataGridView に timestamp 列を設けて楽観的同時実行制御に利用するなどということはあり得ません。timestamp 列は DB のテーブルに設けるのです。このあたり、考え違いがあるような気がします。

    terapemiya さんのお勧めに従って新しいスレッドを立てて質問するなら、上記のことを良く考えて質問するようにしてください。

    2012年12月9日 2:48
  • 了解しました。

    礼儀として、そうすべきと誤解していました。

    以後、そのようにします。

    YKsaila

    2012年12月14日 1:20
  • SurferOnWwwさんへ

    全くの誤解です! または、私の書き方が悪かったか、です。

    DataGridView に timestamp 列は設けていません、当然です。関連コードのwhere条件内にあるのです。

    >DataGridView に timestamp 列を設けて楽観的同時実行制御に利用するなどということはあり得ません。timestamp 列>は DB のテーブルに設けるのです。このあたり、考え違いがあるような気がします。

    Timestamp列は、DBのテーブルに設けています。

    DataGridViewに設けるなんて、本来の目的に反しています!!!

    なぜSurferOnWwwさんが、そう思われたかが残念です。 そんなに、理解は悪くないつもりです。

    きっと、書き方が悪かったのですね。

    YKsaila

    2012年12月14日 1:34