none
LINQtoSQLでInvalidOperationException RRS feed

  • 質問

  • LINQtoSQLで楽観的同時実行制御を使って競合を処理しようとしています。

     

          for (int i = 0; i != retry; i++)
          {
            // 変更内容をログに出力
            logChanges();
    
            // 変更内容をDBへ送信
            try
            {
    
              datacontext.SubmitChanges(ConflictMode.ContinueOnConflict);
    
              // 競合検出しなかったので成功し、ループを抜ける。
              isSuccess = true;
              break;
            }
            catch (ChangeConflictException e)
            {
              // 同時実行競合が検出された場合はログに記録する。
              foreach (ObjectChangeConflict occ in providb.ChangeConflicts)
              {
                occ.Resolve(RefreshMode.KeepChanges);
                Log.Warn("Some records are not updated due to optimistic conflict found, retring...");
    
              }
            }
          }
    

    これである条件下でSubmitChanges()がInvalidOperationExceptionをthrowしてきます。

     

    分かった範囲では

    0)対象のテーブルにはtimestamp列を設定してあります。

    1)プログラム側で複数のレコードをselectしてdatacontextの変更行う。(レコードA,B,Cを変更したとします。)

    2) 上記のSubmitChanges()の前に別のアプリから1)のレコードの一部の更新を行い、競合を起こす。

        (レコードA,BをUPDATE/commit)

    3) 一回目のsubmitChanges()ではA,Bの競合を検出し、ChangeConflictExceptionを検出し、catchの中で競合をResolveする。

    4) 2回目のSubmitChanges()でInvalidOperationExceptionをthrow()

    というフローになります。

    これが、2)のところで"一部"ではなく"全部"のレコードが競合するとInvalidOperationExceptionが発生しなくなります。

    DataContext.Logで出力されるログを見ていると、どうもレコードC(プログラム側で更新したいが競合を起こしていない)

    が2回目のSubmitChanges()でもUPDATEされていて、これのtimestamp列が2度更新されようとしているのが

    問題のようです。

     

    こういう場合はどう対処すればよいのでしょうか。

    2010年7月22日 11:02

回答

  • これはバグとのことです。
    タイムスタンプ列が絡んだ場合に発生するようです。

    LINQ update fails for concurrency issue
    http://social.msdn.microsoft.com/forums/en-US/linqtosql/thread/5bf3e5b0-e8a6-445a-9c7d-145d421d2fd1/

    上記スレッドに書かれていた以下の回避策を試したところ、エラーは発生しなくなりました。
    (追加時の動作は確認できていませんけど。)

    1. .dbml のタイムスタンプ列の [自動同期] を "OnInsert" に変更
    2. Submit 後、手動で更新行のタイムスタンプ列を再取得

    LINQ to SQL の経験はほとんどなかったので2番目の方法がわからなかったのですが、試行錯誤の結果、以下の方法で確認しました。

    var updateTargets = datacontext.GetChangeSet().Updates;
    datacontext.SubmitChanges(ConflictMode.ContinueOnConflict);
    datacontext.Refresh(
        RefreshMode.OverwriteCurrentValues, updateTargets);

    • 回答の候補に設定 山本春海 2010年7月28日 8:44
    • 回答としてマーク 山本春海 2010年8月4日 8:48
    2010年7月23日 7:49

すべての返信

  • これはバグとのことです。
    タイムスタンプ列が絡んだ場合に発生するようです。

    LINQ update fails for concurrency issue
    http://social.msdn.microsoft.com/forums/en-US/linqtosql/thread/5bf3e5b0-e8a6-445a-9c7d-145d421d2fd1/

    上記スレッドに書かれていた以下の回避策を試したところ、エラーは発生しなくなりました。
    (追加時の動作は確認できていませんけど。)

    1. .dbml のタイムスタンプ列の [自動同期] を "OnInsert" に変更
    2. Submit 後、手動で更新行のタイムスタンプ列を再取得

    LINQ to SQL の経験はほとんどなかったので2番目の方法がわからなかったのですが、試行錯誤の結果、以下の方法で確認しました。

    var updateTargets = datacontext.GetChangeSet().Updates;
    datacontext.SubmitChanges(ConflictMode.ContinueOnConflict);
    datacontext.Refresh(
        RefreshMode.OverwriteCurrentValues, updateTargets);

    • 回答の候補に設定 山本春海 2010年7月28日 8:44
    • 回答としてマーク 山本春海 2010年8月4日 8:48
    2010年7月23日 7:49
  • これはバグとのことです。
    タイムスタンプ列が絡んだ場合に発生するようです。

    LINQ update fails for concurrency issue
    http://social.msdn.microsoft.com/forums/en-US/linqtosql/thread/5bf3e5b0-e8a6-445a-9c7d-145d421d2fd1/

    上記スレッドに書かれていた以下の回避策を試したところ、エラーは発生しなくなりました。
    (追加時の動作は確認できていませんけど。)


    なるほど、そんな情報があったんですね。試してみて、結果を報告します。

    ありがとうございました。

    2010年7月27日 0:28