【テクニカル・上級編】ADOのDisconnected Recordsetで実現する:オフライン環境でのデータ編集ツール – Access VBA解析バイブル

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

切断された記録セット(Disconnected Recordset)の極意:高可用性・オフライン編集アーキテクチャの構築

ネットワークの瞬断、不安定なVPN、あるいはサーバー負荷によるタイムアウト。これらは、我々が実戦の現場で日常的に直面する「敵」である。

多くの開発者は、Access VBAにおいて「接続を維持したまま操作する」という、あまりにも脆弱な設計に逃げ込む。DAOのRecordsetをそのまま開き、ユーザーが値を入力している間、ずっとサーバーとのコネクションを握り続ける——。このような設計は、現代のエンタープライズ環境においては、単なる「バグの温床」でしかない。接続が切れた瞬間にシステムは死に、サーバー側には未完了のトランザクションが残り、ロックの競合を引き起こす。

真のアーキテクトが目指すべきは、「接続の最小化」である。
本稿では、ADO(ActiveX Data Objects)の真髄とも言える「Disconnected Recordset」を用いた、オフライン編集・一括同期アーキテクチャの設計思想とその実装手法について、深淵にまで踏み込んで解説する。

1. アーキテクチャのパラダイムシフト:Connected vs Disconnected

通常のCRUD操作は、クライアントとサーバーが常に「対話」を続ける Connected Model である。これに対し、我々が採用するのは、データをメモリ上に完全に引き込み、接続を物理的に切断してから編集を行う Disconnected Model だ。

Disconnected Model のライフサイクル

1. Fetch Phase: サーバーに接続し、必要なデータをクライアント側のメモリ(Client-side Cursor)へ一括取得する。
2. Disconnect Phase: 取得完了後、即座にサーバーとのコネクションを破棄する。
3. Edit Phase: クライアントのメモリ上にあるRecordsetに対して、ユーザーが自由に編集を行う。この間、ネットワークの状態は一切関係ない。
4. Sync Phase: 編集完了後、再度サーバーへ接続し、変更差分(Delta)のみをトランザクション内で一括適用する。

この設計により、ネットワークの不安定さは「編集プロセス」から完全に排除される。

2. 実装の核心:Client-side Cursor の制御

Disconnected Recordsetを実現するための絶対条件は、`CursorLocation` プロパティを `adUseClient` に設定することだ。これを忘れた瞬間、Recordsetはサーバーサイドカーソルを要求し、接続切断と同時にオブジェクトは無効化される。

以下に、その実装の雛形を示す。

' --- Module: modDataEngine ---
Option Explicit

' ADO定数の参照設定が必要 (Microsoft ActiveX Data Objects x.x Library)
Public Function GetDisconnectedRecordset(ByVal strConn As String, ByVal strSQL As String) As ADODB.Recordset
    Dim rs As ADODB.Recordset
    Dim cn As ADODB.Connection
    
    On Error GoTo Err_Handler
    
    ' 1. Connectionオブジェクトの生成
    Set cn = New ADODB.Connection
    cn.ConnectionString = strConn
    cn.CursorLocation = adUseClient ' 【極めて重要】クライアントサイドカーソルを指定
    cn.Open
    
    ' 2. Recordsetの生成
    Set rs = New ADODB.Recordset
    
    ' 3. データの取得
    ' Openメソッドの引数に adOpenStatic, adLockOptimistic を指定
    rs.Open strSQL, cn, adOpenStatic, adLockOptimistic
    
    ' 4. 接続の切断
    ' Recordsetにデータを保持させたまま、Connectionを閉じる
    ' これにより、物理的なネットワーク通信はここで終了する
    cn.Close
    Set cn = Nothing
    
    ' 5. 取得したRecordsetを返す
    Set GetDisconnectedRecordset = rs
    Exit Function

Err_Handler:
    Debug.Print "Error in GetDisconnectedRecordset: " & Err.Description
    If Not rs Is Nothing Then Set rs = Nothing
    If Not cn Is Nothing Then Set cn = Nothing
    Set GetDisconnectedRecordset = Nothing
End Function

3. メモリ管理とオブジェクトのライフサイクル

VBAにおける最大の落とし穴は、COMオブジェクトの解放漏れによるメモリリークである。特に、大量のデータをRecordsetに展開する場合、メモリ管理を誤ると、Accessプロセス自体が膨張し、最終的に `Out of Memory` でクラッシュする。

伝説的アーキテクトの作法

  • 明示的なNothing代入: `Set obj = Nothing` は単なるマナーではない。COMの参照カウントを確実に減らすための必須工程である。
  • Scopeの限定: オブジェクトの生存期間を最小限に抑えるため、可能な限りプロシージャ内で完結させる。
  • Windows APIによる監視: 開発環境では、`GetProcessMemoryInfo` 等のAPIを用いて、プロセスのワーキングセットが異常に増大していないかを監視する。
' --- Module: modMemoryManagement ---
' Windows APIを利用したメモリ使用量の監視(概念的な実装)
Declare PtrSafe Function GetProcessMemoryInfo Lib "psapi.dll" ( _
    ByVal hProcess As LongPtr, _
    ByRef lpProcessMemoryInfo As PROCESS_MEMORY_COUNTERS, _
    ByVal cb As Long) As Long

Type PROCESS_MEMORY_COUNTERS
    cb As Long
    PageFaultCount As Long
    PeakWorkingSetSize As LongPtr
    WorkingSetSize As LongPtr
    ' ... 他のメンバ
End Type

' 実行中のメモリ負荷をログに出力するルーチンを組み込むことで、
' メモリリークの検知を自動化する。

4. 同期フェーズ:トランザクションによる整合性の担保

編集が終わった後の「一括同期」は、最もリスクの高いフェーズだ。ここでエラーが発生した場合、データの不整合(Partial Update)を避けるため、「すべて成功するか、すべて失敗するか」を保証しなければならない。

ADOの `UpdateBatch` を使う手法もあるが、VBAの複雑なビジネスロジックを介在させる場合は、明示的な `Transaction` 管理下でループ処理を行う方が、エラーハンドリングの粒度を高められる。

' --- Module: modSyncEngine ---
Public Sub SynchronizeChanges(ByVal strConn As String, ByRef rsOffline As ADODB.Recordset)
    Dim cn As ADODB.Connection
    Dim i As Long
    
    On Error GoTo Err_Sync
    
    Set cn = New ADODB.Connection
    cn.ConnectionString = strConn
    cn.Open
    
    ' トランザクションの開始
    cn.BeginTrans
    
    ' Recordsetの変更箇所を走査
    ' ※Disconnected Recordsetの特性を利用し、変更があったレコードのみを抽出
    ' ここでは簡略化のため、全レコードをループして更新を試みる例を示す
    rsOffline.MoveFirst
    Do Until rsOffline.EOF
        ' 実際には、rsOffline.Fields("ModifiedFlag").Value 等で判定するのがスマート
        ' ここでは、レコードの状態を確認し、更新をかける
        
        ' サーバー側のレコードに同期
        ' 注意: 本来はここで楽観的同時実行制御(Optimistic Concurrency)を行う
        ' サーバー側のデータが既に変わっていないかを確認するロジックが必要
        
        ' ... 更新ロジック ...
        
        rsOffline.MoveNext
    Loop
    
    ' すべて成功したらコミット
    cn.CommitTrans
    Debug.Print "Synchronization Successful."

CleanUp:
    If Not rsOffline Is Nothing Then Set rsOffline = Nothing
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
        Set cn = Nothing
    End If
    Exit Sub

Err_Sync:
    ' エラー発生時はロールバックして、データの整合性を守る
    If Not cn Is Nothing Then cn.RollbackTrans
    MsgBox "同期中に致命的なエラーが発生しました。変更は破棄されました。" & vbCrLf & Err.Description, vbCritical
    Resume CleanUp
End Sub

5. 結論:堅牢なシステムへの道

Disconnected Recordsetを用いたアーキテクチャは、実装の複雑さを増大させる。しかし、その代償として得られる「ネットワーク耐性」と「サーバー負荷の低減」は、大規模な社内システムや、不安定な現場環境で稼働するツールにおいて、計り知れない価値を持つ。

我々エンジニアが作るべきは、単に「動く」ツールではない。
過酷な環境下においても、沈黙を守り、正確にデータを刻み続ける、「壊れない」アーキテクチャである。

メモリの一片、接続の一秒にまで執着せよ。そこにのみ、真のエンジニアリングが存在する。

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