【入門編】ADOのCommandオブジェクトによる「一括更新(BatchUpdate)」と「競合発生時の手動解決」の実践 – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

Access VBAの壁を越える:ADOによる「一括更新」と、プロが実践する「競合解決」の極意

こんにちは。ここまでAccess VBAの基本を積み上げてきたあなたは、もう「マクロの記録」や「簡単なデータの読み書き」の段階は卒業しています。

しかし、実務で「ユーザーが10人、20人と増えたシステム」を運用し始めると、必ずと言っていいほど直面する「恐ろしい壁」があります。それが、「同時実行制御(コンカレンシー)」の問題です。

「Aさんがデータを書き換えている最中に、Bさんが同じデータを書き換えてしまったらどうなるのか?」

この問題に、単なる「エラーで止まる」のではなく、プロフェッショナルとして「スマートに解決」するための手法を、今日は伝授しましょう。DAOから一歩踏み出し、ADO(ActiveX Data Objects)の真の力を引き出す準備はできましたか?

ここをクリアすれば、あなたのAccess開発は「個人用ツール」から「業務基幹システム」へと昇華しますよ。

1. なぜDAOではなく、ADOを使うのか?

Access開発の標準はDAO(Data Access Objects)です。Accessのテーブルを直接操作するなら、DAOは非常に高速で使いやすい。

しかし、今回扱うような「サーバー(SQL Serverなど)との連携」や「高度な一括処理」、そして「メモリ上だけでデータを保持して、最後にまとめて投げる」といった高度なアーキテクチャを構築する場合、ADOの出番となります。

DAOとADOの決定的な違い

特徴 DAO ADO
主な用途 Access内部のテーブル操作 外部DB連携・高度な一括処理
思想 「今あるテーブル」を操作する 「データという概念」を操作する
メモリ管理 テーブルに密結合 Disconnected Recordset(切断型)が可能
一括更新 1件ずつの更新が基本 BatchUpdate による高速な一括送信が可能

今回のテーマである「BatchUpdate」は、ADOが得意とする「メモリ上で編集を溜め込み、最後にドカンと一括で送る」手法です。これにより、ネットワークの往復回数を減らし、パフォーマンスを劇的に向上させることができます。

2. 「楽観的同時実行制御」という考え方

プロの現場では、データを更新する際、常に「誰かが先に書き換えていないか?」を疑います。これを「楽観的同時実行制御(Optimistic Concurrency Control)」と呼びます。

1. 読み取り: ユーザーがレコードを読み取る(この時、データに「バージョン」や「更新日時」を持たせておくとより確実です)。
2. 編集: ユーザーが画面上でデータを書き換える(この間、DBには何も送らない)。
3. 更新試行: 「さあ、更新するぞ!」と命令を送る。
4. 競合検知: もし、自分が読み取った後に、他の誰かがデータを更新していたら、DBは「おっと、データが変わっているよ!」とエラーを返します。

この「エラーをどう受け止め、どう解決するか」が、エンジニアの腕の見せ所です。

3. 実践:Commandオブジェクトによる一括更新と競合解決

それでは、具体的なコードを見ていきましょう。
シナリオはこうです。「在庫管理システムで、複数の在庫データをメモリ上で一括修正し、最後に一括更新(BatchUpdate)を試みる。もし誰かが先に更新していたら、エラーを検知してユーザーに判断を仰ぐ」。

実装コード例

Option Explicit

' 参照設定: Microsoft ActiveX Data Objects x.x Library を有効にしてください
Public Sub ExecuteBatchUpdateWithConflictResolution()
    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim cmd As ADODB.Command
    Dim i As Integer
    
    ' 接続文字列(環境に合わせて書き換えてください)
    Dim strConn As String
    strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & CurrentProject.Path & "\YourDatabase.accdb;"

    On Error GoTo ErrorHandler

    ' 1. 接続の確立
    Set cn = New ADODB.Connection
    cn.Open strConn

    ' 2. Recordsetの生成(Optimistic Lock を指定するのがポイント)
    ' adLockOptimistic: 変更時に競合をチェックする設定
    Set rs = New ADODB.Recordset
    rs.Open "SELECT ID, ItemName, StockQty FROM Inventory", cn, adOpenStatic, adLockOptimistic

    ' --- ここからメモリ上での編集シミュレーション ---
    ' 本来は画面上のグリッドなどから値を取得しますが、ここではコードで直接操作します
    If Not rs.EOF Then
        rs.MoveFirst
        ' 1件目のデータを変更
        rs!StockQty = rs!StockQty - 10
        rs.Update ' メモリ上のRecordsetに対して「変更完了」を通知
        
        ' 2件目のデータを変更(もしあれば)
        rs.MoveNext
        If Not rs.EOF Then
            rs!StockQty = rs!StockQty + 5
            rs.Update
        End If
    End If
    ' --- 編集終了 ---

    ' 3. 一括更新の実行
    Debug.Print "一括更新を開始します..."
    rs.UpdateBatch ' ここで初めてサーバーへ一括送信される

    MsgBox "すべての更新が正常に完了しました。", vbInformation
    
CleanUp:
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
    End If
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
    End If
    Set rs = Nothing
    Set cn = Nothing
    Exit Sub

ErrorHandler:
    ' 4. 競合エラー(adErrBatchUpdate)のハンドリング
    ' エラー番号 3021 や、ADO特有の BatchUpdate エラーを判定
    If Err.Number = 3706 Or Err.Number = 3021 Then ' 状況に応じてエラー番号を調整
        Debug.Print "競合が発生しました。エラー解析を開始します。"
        HandleBatchErrors rs
        Resume CleanUp
    Else
        MsgBox "予期せぬエラーが発生しました: " & Err.Description, vbCritical
        Resume CleanUp
    End If
End Sub

''' <summary>
''' BatchUpdate中に発生したエラーを個別に解析し、ユーザーに解決を促す
''' </summary>
Private Sub HandleBatchErrors(ByRef rs As ADODB.Recordset)
    Dim errIdx As Integer
    Dim targetID As Long
    Dim serverValue As Variant
    Dim userValue As Variant
    
    ' rs.Errors コレクションには、失敗したレコードの情報が格納されている
    For errIdx = 0 To rs.Errors.Count - 1
        Debug.Print "エラー " & errIdx & ": " & rs.Errors(errIdx).Description
        
        ' エラーが発生したレコードの特定(エラーオブジェクトからRecordsetを参照可能)
        ' ※ADOのバージョンやプロバイダにより取得方法が異なる場合があります
        ' ここでは概念的な解決フローを示します
        
        ' 1. どのデータが原因か特定する
        ' 2. 現在のサーバー上の値を取得する
        ' 3. ユーザーに「サーバーの値」と「自分の値」を提示して選択させる
        
        ' --- 実務的な解決フローのイメージ ---
        ' MsgBox "以下のデータで競合が発生しました。" & vbCrLf & _
        '        "ID: " & targetID & vbCrLf & _
        '        "現在のサーバーの値: " & serverValue & vbCrLf & _
        '        "あなたが入力した値: " & userValue & vbCrLf & _
        '        "どちらを優先しますか?"
        
        ' 選択結果に基づき、rsの値を上書きして再試行するか、その行をスキップするかを決定する
        
        Debug.Print "競合解決フローを起動(UI実装が必要)"
    Next errIdx
    
    MsgBox "競合が発生したため、手動での確認が必要です。ログを確認してください。", vbExclamation
End Sub

4. アーキテクトの視点:陥りやすい落とし穴

この実装を現場に投入する際、以下の3点に注意してください。これを知っているだけで、トラブルシューティングの時間は半分以下になります。

① エラーオブジェクトの「深さ」を理解する

`rs.UpdateBatch` が失敗したとき、エラーは単一の `Err` オブジェクトだけでなく、`Recordset.Errors` コレクションの中に格納されます。
「なぜかエラーが出ているのに、どのレコードが原因かわからない!」という事態を防ぐには、必ず `For i = 0 To rs.Errors.Count – 1` のループで、エラーの発生源を特定する癖をつけてください。

② 接続のライフサイクル(Connection Lifecycle)

`Command` オブジェクトや `Recordset` を使う際、親となる `Connection` を先に閉じてしまうと、オブジェクトは迷子になり、メモリリークや予期せぬクラッシュの原因になります。
「一番最後に Connection を閉じる」。これは鉄則です。

③ ユーザーインターフェース(UI)の設計

競合が発生したとき、単に「エラーです」と出すのは二流です。
「他のユーザーが既に値を `100` に変更しています。あなたの変更値 `120` を適用しますか? それともサーバーの値を採用しますか?」
このように、「現在の状態」と「自分の意図」を比較させるUIを設計することこそが、真の業務システムエンジニアの仕事です。

まとめ:次の一歩へ

いかがでしたでしょうか。
ADOの `Command` オブジェクトや `BatchUpdate` は、確かに最初は難解に見えます。しかし、これらを使いこなすことで、Accessは「単なる表計算ソフトの延長」から「堅牢なデータベースアプリケーション」へと進化します。

1. DAOからADOへ:より高度な制御のために。
2. BatchUpdate:パフォーマンスのために。
3. エラーコレクションの走査:正確な競合解決のために。

この3点を意識して、ぜひ自分のプロジェクトに組み込んでみてください。
ここをクリアすれば、あなたはもう、Access VBAの「中級者」を超え、「プロフェッショナル」の入り口に立っていますよ。

応援しています。次回の講義でお会いしましょう!

タイトルとURLをコピーしました