DOBON.NET DOBON.NETプログラミング掲示板過去ログ

UpdateCommand同時実行違反について

環境/言語:[VB.NET2003]
分類:[.NET]

失礼致します。

開発環境OS=XPProSP2
言語=VB.NET2003
です。

フォームfrmTestで使う関数FUNCTIONを想定します。
FUNCTIONを実行すると、ローカルにあるMicrosoftAccessのデータベース(MDB)にあるテーブル[売上明細]のフィールド[フラグ](int型)を、0から1に書き換えます。ここでは、コードを簡略化するために、レコードが1レコードだけ、確実に入っていると想定します。

テーブル[売上明細]のフィールド構成は以下の通りです。

売上明細

明細ID
商品コード
商品名
単価
数量
フラグ(int型)

以下、コード内容です。

---------------------------------------------------------------------

Imports System.Data.OleDb

Public Class frmTest
Inherits System.Windows.Forms.Form

Public Function FUNCTION

Dim strA As String
Dim cnL As OleDb.OleDbConnection
Dim daL As OleDb.OleDbDataAdapter
Dim dsL As New DataSet
Dim dtL As DataTable
Dim drL As DataRow()
Dim Olecb As OleDb.OleDbCommandBuilder

Try

strA = "SELECT * FROM [売上明細]"

cnL = New OleDb.OleDbConnection(データベース接続文字列)
cnL.Open()

daL = New OleDb.OleDbDataAdapter(strA, cnL)
daL.Fill(dsL, "売上明細")
dtL = dsL.Tables("売上明細")
drL = dtL.Select()
Olecb = New OleDb.OleDbCommandBuilder(daL)
drL(0).BeginEdit()

drL(0)("フラグ") = 1'ここで、変更。

drL(0).EndEdit()
daL.Update(dsL, "売上明細")

MsgBox("更新しました。")
Return True

Catch ex As Exception

MessageBox.Show(ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False

End Try

End Function

End Class
------------------------------------------------------------------

上記のコードを実行すると、[売上明細]には確かに1レコード、入っているのですが、daL.Update(dsL, "売上明細")の行で、「同時実行違反:updateCommandによって0件処理されました」エラーが出てしまいます。cnL.Open()を外しても、drL(0).BeginEditとdrL(o).EndEditを外しても、結果は同じでした。

drL(0)("フラグ") = 1のコードをコメントアウトすると、エラーは起きずに、MsgBox("更新しました。")まで流れます。

どなたか原因がお分かりになられましたら、お忙しい中、誠に恐縮でございますが、ご指導くださいませ。

よろしくお願い致します。
■No28087に返信(ERIさんの記事)
> 言語=VB.NET2003
とりあえず手元の環境(VS2008)では、提示されたコードで更新できたので、
バージョン依存の問題でもあるのでしょうかね…。


> drL(0).EndEdit()
今回のケースで BeginEdit/EndEdit は不要だと思います。
本題とは関係ありませんけれども。


> どなたか原因がお分かりになられましたら
原因は分かりませんが、とりあえずエラーの理由について:


OleDbDataAdapter は内部で、下記のような UPDATE 文を利用しています。
(NULL を許容する列がある場合は、もう少し長い SQL になります)

--------------
 UPDATE 売上明細 SET
  明細ID = new_明細ID
  , 商品コード = new_商品コード
   :
   :
  , フラグ = new_フラグ
 WHERE
  明細ID = old_明細ID AND
   :
   :
  フラグ = old_フラグ
--------------

old_フラグ に入る値は「drL(0)("FLG", DataRowVersion.Original)」で、
new_フラグ に入る値は「drL(0)("FLG", DataRowVersion.Current)」です。


そしてこの時、WHERE にヒットする行が 0 件だった場合には、
別のユーザーがその行を削除または編集したのだと判断されて、
> 同時実行違反:updateCommandによって0件処理されました
のエラーを発生させる仕組みになっています。


ですから、何らかの理由でこの「WHERE」の判定に問題が生じているという事になりますね。

まずはエラーの内訳を調べるため、daL.Update(dsL, "売上明細") の前に

Debug.WriteLine(Olecb.ConflictOption)
daL.UpdateCommand = Olecb.GetUpdateCommand
Debug.WriteLine(daL.UpdateCommand.CommandText)
For Each p As OleDb.OleDbParameter In daL.UpdateCommand.Parameters
 Debug.WriteLine(String.Format("[{0}]{1}({2}/NULL判定用={3}),{4}({5}) => {6}({7})", _
  p.SourceVersion, p.ParameterName, p.SourceColumn, p.SourceColumnNullMapping, _
  p.DbType, p.OleDbType, p.Value, TypeName(p.Value)))
Next

を実行して、パラメータ(OleDbType)や SQL の内容に不備が無いかを
チェックしてみてください。そして問題があるようならば、
UpdateCommand の内容を自作の物に差し替えてみてください。
(InsertCommand や DeleteCommand についても、同じことが言えます)


内容によっては、Olecb.ConflictOption = ConflictOption.OverwriteChanges を
設定することうまくいく可能性があります。というのも、この指定にすると
主キー列だけを使って検索されるため、UpdateCommand が
 WHERE 明細ID = old_明細ID
のように一気に単純化されるからです。
魔界の仮面弁士さま^^

お忙しい中、詳しいアドバイスを頂きまして、
誠にありがとうございます^^。
早速取り組んでみます^^。
また、結果をご報告させて頂きます^^。

ERI


■No28088に返信(魔界の仮面弁士さんの記事)
> ■No28087に返信(ERIさんの記事)
>>言語=VB.NET2003
> とりあえず手元の環境(VS2008)では、提示されたコードで更新できたので、
> バージョン依存の問題でもあるのでしょうかね…。
>
>
>>drL(0).EndEdit()
> 今回のケースで BeginEdit/EndEdit は不要だと思います。
> 本題とは関係ありませんけれども。
>
>
>>どなたか原因がお分かりになられましたら
> 原因は分かりませんが、とりあえずエラーの理由について:
>
>
> OleDbDataAdapter は内部で、下記のような UPDATE 文を利用しています。
> (NULL を許容する列がある場合は、もう少し長い SQL になります)
>
> --------------
>  UPDATE 売上明細 SET
>   明細ID = new_明細ID
>   , 商品コード = new_商品コード
>    :
>    :
>   , フラグ = new_フラグ
>  WHERE
>   明細ID = old_明細ID AND
>    :
>    :
>   フラグ = old_フラグ
> --------------
>
> old_フラグ に入る値は「drL(0)("FLG", DataRowVersion.Original)」で、
> new_フラグ に入る値は「drL(0)("FLG", DataRowVersion.Current)」です。
>
>
> そしてこの時、WHERE にヒットする行が 0 件だった場合には、
> 別のユーザーがその行を削除または編集したのだと判断されて、
>>同時実行違反:updateCommandによって0件処理されました
> のエラーを発生させる仕組みになっています。
>
>
> ですから、何らかの理由でこの「WHERE」の判定に問題が生じているという事になりますね。
>
> まずはエラーの内訳を調べるため、daL.Update(dsL, "売上明細") の前に
>
> Debug.WriteLine(Olecb.ConflictOption)
> daL.UpdateCommand = Olecb.GetUpdateCommand
> Debug.WriteLine(daL.UpdateCommand.CommandText)
> For Each p As OleDb.OleDbParameter In daL.UpdateCommand.Parameters
>  Debug.WriteLine(String.Format("[{0}]{1}({2}/NULL判定用={3}),{4}({5}) => {6}({7})", _
>   p.SourceVersion, p.ParameterName, p.SourceColumn, p.SourceColumnNullMapping, _
>   p.DbType, p.OleDbType, p.Value, TypeName(p.Value)))
> Next
>
> を実行して、パラメータ(OleDbType)や SQL の内容に不備が無いかを
> チェックしてみてください。そして問題があるようならば、
> UpdateCommand の内容を自作の物に差し替えてみてください。
> (InsertCommand や DeleteCommand についても、同じことが言えます)
>
>
> 内容によっては、Olecb.ConflictOption = ConflictOption.OverwriteChanges を
> 設定することうまくいく可能性があります。というのも、この指定にすると
> 主キー列だけを使って検索されるため、UpdateCommand が
>  WHERE 明細ID = old_明細ID
> のように一気に単純化されるからです。
■No28102に返信(ERIさんの記事)
> 魔界の仮面弁士さま^^

エラーの原因が分かりました。

ここで記載させて頂いた例では、分かりやすくする為に、売上明細の主キーは[明細ID]としていましたが、
実際に現在開発しているプログラムでは、
[No]というフィールド名でした。
SQL文で取得するテーブルのフィールド名に[No]というフィールド名が
含まれていると、そのフィールドが、主キーであってもなくても、
このエラーが起きてしまうようです。
[明細No]や[ID]のように、
他のフィールド名に変更すると、エラーが起きなくなりました。

こちらの具体例でも[No]とすべきでした。
申し訳ありません。

フィールド名[No]を変更しないまま、エラーを解決できる方法があればよいのですが。

また、2003では、
Olecb.ConflictOptionという記述は、無効になってしまうようです。

引き続き、検討してみます。
ありがとうございます。
■No28107に返信(ERIさんの記事)
> [No]というフィールド名でした。
テーブルや列名に予約語を使わない方が良いですよ。

たとえば、SQL の内容が
 『SELECT NO FROM TABLE1』
の場合、元データが何であっても「0」という数値になっていますし、
 『SELECT YES FROM TABLE1』
なら、すべて「-1」となります。(JET には「Yes/No 型」という物があります)

エスケープすれば予約語も使えますが…ミドルウェアによっては
SQL が自動補正される関係上、これだけでは対処できない事もあります。
 『SELECT TBL.NO FROM TABLE1』
 『SELECT [NO] FROM [TABLE1]』
 『SELECT `NO` FROM `TBL`』


> フィールド名[No]を変更しないまま、エラーを解決できる方法があればよいのですが。
今更遅いのかもしれませんが、そもそも設計段階で
予約語を使わないようにしておいた方が良かったですね。


とりあえず、SELECT 売上明細.NO AS 明細NO なビューを作っておき、
それを使って更新してみるのは如何でしょう。(未確認)

魔界の仮面弁士さま

ありがとうございます。
まずは、フィールド名を変更する方向で、
対応していきたいと思います。
ありがとうございました。


■No28110に返信(魔界の仮面弁士さんの記事)
> ■No28107に返信(ERIさんの記事)
>>[No]というフィールド名でした。
> テーブルや列名に予約語を使わない方が良いですよ。
>
> たとえば、SQL の内容が
>  『SELECT NO FROM TABLE1』
> の場合、元データが何であっても「0」という数値になっていますし、
>  『SELECT YES FROM TABLE1』
> なら、すべて「-1」となります。(JET には「Yes/No 型」という物があります)
>
> エスケープすれば予約語も使えますが…ミドルウェアによっては
> SQL が自動補正される関係上、これだけでは対処できない事もあります。
>  『SELECT TBL.NO FROM TABLE1』
>  『SELECT [NO] FROM [TABLE1]』
>  『SELECT `NO` FROM `TBL`』
>
>
>>フィールド名[No]を変更しないまま、エラーを解決できる方法があればよいのですが。
> 今更遅いのかもしれませんが、そもそも設計段階で
> 予約語を使わないようにしておいた方が良かったですね。
>
>
> とりあえず、SELECT 売上明細.NO AS 明細NO なビューを作っておき、
> それを使って更新してみるのは如何でしょう。(未確認)
■No28111に返信(ERIさんの記事)
>
> 魔界の仮面弁士さま
>
> ありがとうございます。
> まずは、フィールド名を変更する方向で、
> 対応していきたいと思います。
> ありがとうございました。
>
>
> ■No28110に返信(魔界の仮面弁士さんの記事)
>>■No28107に返信(ERIさんの記事)
> >>[No]というフィールド名でした。
>>テーブルや列名に予約語を使わない方が良いですよ。
>>
>>たとえば、SQL の内容が
>> 『SELECT NO FROM TABLE1』
>>の場合、元データが何であっても「0」という数値になっていますし、
>> 『SELECT YES FROM TABLE1』
>>なら、すべて「-1」となります。(JET には「Yes/No 型」という物があります)
>>
>>エスケープすれば予約語も使えますが…ミドルウェアによっては
>>SQL が自動補正される関係上、これだけでは対処できない事もあります。
>> 『SELECT TBL.NO FROM TABLE1』
>> 『SELECT [NO] FROM [TABLE1]』
>> 『SELECT `NO` FROM `TBL`』
>>
>>
> >>フィールド名[No]を変更しないまま、エラーを解決できる方法があればよいのですが。
>>今更遅いのかもしれませんが、そもそも設計段階で
>>予約語を使わないようにしておいた方が良かったですね。
>>
>>
>>とりあえず、SELECT 売上明細.NO AS 明細NO なビューを作っておき、
>>それを使って更新してみるのは如何でしょう。(未確認)
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板