【実務・中級編】ADOのDisconnected Recordsetを活用した、ネットワーク負荷を最小化する「オフライン編集」アーキテクチャ – Access VBA解析バイブル

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

【極限の知見】Access VBAにおける「Disconnected Recordset」の真髄:ネットワーク負荷をゼロに近づけるオフライン編集アーキテクチャ

多くの開発者が、Access VBAで業務ツールを構築する際、陥る「致命的な罠」がある。

それは、「ユーザーが画面を開いている間、ずっとデータベースとの接続(Connection)を維持し続ける」という設計だ。

クライアント・サーバー構成(Access Front-end / SQL Server Back-endなど)において、この設計は最悪のパフォーマンスを引き起こす。ネットワークの瞬断、レコードロックによる競合、そしてユーザーが「入力中」に発生する通信遅延。これらはすべて、設計の段階で回避できたはずの「負債」である。

本稿では、ADO(ActiveX Data Objects)の真の力を引き出す「Disconnected Recordset(切断型レコードセット)」を用いた、極限までネットワーク負荷を抑えたオフライン編集アーキテクチャを伝授する。

1. なぜ、君のアプリケーションはネットワークに弱いのか?

一般的な「接続型(Connected)」の設計では、レコードセットをオープンした瞬間から、クライアントとサーバーの間に「細い糸」のような通信経路が確立される。

ユーザーが値を1文字入力するたび、あるいはレコードを移動するたびに、その「糸」を通じて通信が発生する。これが、大規模なネットワーク環境や、VPN越しでの利用において、動作の「もっさり感」や「フリーズ」の正体だ。

接続型(Connected)の限界

  • 通信の継続: 編集中、常にサーバーとのコネクションを占有する。
  • ロックの競合: ユーザーが編集を終えるまで、サーバー上のレコードがロックされ続け、他者の作業を阻害する。
  • 脆弱性: わずかなネットワークの揺らぎが、即座に「実行時エラー」へと直結する。

これに対し、我々が目指すべきは「必要な時だけ接続し、データは手元のメモリに置いておく」という思想、すなわち切断型(Disconnected)アーキテクチャである。

2. Disconnected Recordset アーキテクチャの全体像

Disconnected Recordsetを活用した設計は、以下の3フェーズで完結する。

1. Fetch(抽出): 接続を確立 $\rightarrow$ データをメモリ(クライアント側)にロード $\rightarrow$ 即座に接続を閉じる。
2. Edit(編集): サーバーとは無関係に、メモリ上のデータのみを操作する。ネットワーク負荷はゼロ。
3. Sync(同期): 再接続 $\rightarrow$ メモリ上の変更差分を一括でサーバーへ反映 $\rightarrow$ 接続を閉じる。

このフローにより、ユーザーが入力に10分費やそうが、その間サーバーとの通信は一切発生しない。サーバーの負荷も、ロックの保持時間も最小化される。

3. プロダクション品質の完全実装例

では、これをどのようにコードに落とし込むか。単に動くだけのコードではない。エラーハンドリングとオブジェクトのライフサイクルを管理した、堅牢なモジュール構成を示す。

実装の核心:`clsDataHandler` モジュール

このクラスモジュールは、データの取得と同期の責務を分離し、呼び出し側に「接続の管理」を意識させない設計とする。

' Class Module: clsDataHandler
Option Explicit

' 接続文字列(環境に応じて動的に変更すること)
Private Const CONN_STR As String = "Provider=SQLOLEDB;Data Source=SERVER_NAME;Initial Catalog=DB_NAME;Integrated Security=SSPI;"

' 切断型レコードセットを保持するための変数
Private m_rs As ADODB.Recordset

''' <summary>
''' データをメモリ上にロードし、接続を切断する
''' </summary>
Public Function FetchData(ByVal sql As String) As ADODB.Recordset
    Dim conn As ADODB.Connection
    Set conn = New ADODB.Connection
    
    On Error GoTo ErrorHandler
    
    ' 1. 接続の確立
    conn.Open CONN_STR
    
    ' 2. クライアントサイドカーソルを指定(これがDisconnectedの絶対条件)
    ' adUseClient = 3
    Set m_rs = New ADODB.Recordset
    m_rs.CursorLocation = adUseClient
    
    ' 3. データの取得
    ' adLockOptimistic = 3 (楽観的ロック)
    ' adOpenStatic = 3 (静的カーソル)
    m_rs.Open sql, conn, adOpenStatic, adLockOptimistic
    
    ' 4. 重要:レコードセットをメモリに保持したまま、接続を閉じる
    ' これにより、メモリ上にデータが独立して存在する状態(Disconnected)になる
    conn.Close
    Set conn = Nothing
    
    Set FetchData = m_rs
    Exit Function

ErrorHandler:
    If Not conn Is Nothing Then
        If conn.State = adStateOpen Then conn.Close
    End If
    Err.Raise Err.Number, "clsDataHandler.FetchData", Err.Description
End Function

''' <summary>
''' メモリ上の変更をサーバーへ一括同期する
''' </summary>
Public Sub SyncData()
    Dim conn As ADODB.Connection
    Set conn = New ADODB.Connection
    
    On Error GoTo ErrorHandler
    
    ' 1. 再接続
    conn.Open CONN_STR
    
    ' 2. レコードセットに既存の接続を再紐付け
    ' ActiveConnectionをセットすることで、メモリ上の変更をサーバーに送る準備が整う
    Set m_rs.ActiveConnection = conn
    
    ' 3. 一括更新(Batch Update)
    ' 変更されたレコードのみがサーバーへ送信される
    m_rs.UpdateBatch
    
    ' 4. 後処理
    m_rs.Update
    conn.Close
    Set conn = Nothing
    
    Debug.Print "Sync Complete: " & Now
    Exit Sub

ErrorHandler:
    ' ここで楽観的ロック競合(Concurrency Error)のハンドリングを行うのがプロの仕事だ
    ' 他のユーザーが既に更新していた場合、ここでエラーが発生する
    MsgBox "同期エラーが発生しました。他のユーザーがデータを更新した可能性があります。" & vbCrLf & _
           "Error: " & Err.Description, vbCritical, "Sync Error"
    
    If Not conn Is Nothing Then
        If conn.State = adStateOpen Then conn.Close
    End If
    Err.Raise Err.Number, "clsDataHandler.SyncData", Err.Description
End Sub

' オブジェクト解放用のデストラクタ
Private Sub Class_Terminate()
    If Not m_rs Is Nothing Then
        If m_rs.State = adStateOpen Then m_rs.Close
        Set m_rs = Nothing
    End If
End Sub

呼び出し側の実装例(Form等)

' 標準モジュールまたはフォームのコード
Option Explicit

Private m_handler As clsDataHandler
Private m_rs As ADODB.Recordset

Sub LoadUserForm()
    Set m_handler = New clsDataHandler
    
    ' データの取得(接続は内部ですぐに切断される)
    Set m_rs = m_handler.FetchData("SELECT * FROM T_Orders WHERE Status = 'Pending'")
    
    ' 例:テキストボックス等に値をセット(実際はレコードセットをバインドするかループで回す)
    If Not m_rs.EOF Then
        txtOrderID.Value = m_rs.Fields("OrderID").Value
        txtAmount.Value = m_rs.Fields("Amount").Value
    End If
End Sub

Sub SaveChanges()
    ' ユーザーが行った変更をメモリ上のレコードセットに反映させる(実際はUI操作を介して)
    ' ここでは例として、レコードセットの値を直接書き換える
    m_rs.Fields("Amount").Value = txtAmount.Value
    
    ' 同期実行
    m_handler.SyncData
    MsgBox "保存が完了しました。", vbInformation
End Sub

4. 運用上の注意点:アーキテクトの視点

このアーキテクチャを導入する際、以下の3点に留意せよ。これを見落とすと、コードがどれほど綺麗でも現場で破綻する。

① 楽観的ロック(Optimistic Concurrency)の理解

Disconnected方式は、基本的に「楽観的ロック」を前提としている。
「誰も編集していないだろう」と仮定して進め、最後に同期する瞬間に「あ、実は誰かが更新していた」と判明した場合、エラーを投げる仕組みだ。
これを無視して、強制的に上書きする(Pessimistic Lock的な挙動)を実装すると、データの整合性は崩壊する。エラー発生時には「最新データを再取得して、ユーザーに再入力を促す」フローを必ず設計に組み込め。

② メモリ消費量への配慮

「切断型」はデータをクライアントのRAMに展開する。
数百万件のレコードを `FetchData` で一気にメモリに載せようとするのは愚策だ。必ず `WHERE` 句を用いて、「ユーザーが今、処理すべき最小限のデータセット」に絞り込んでからロードすること。

③ ライフサイクル管理

`ADODB.Recordset` オブジェクトは、接続を閉じた後もメモリ上に生き続ける。
クラスの `Class_Terminate` や、適切に `Set rs = Nothing` を行わない限り、メモリリークの原因となる。特にループ処理の中でこのパターンを使う場合は、厳格なオブジェクト管理が求められる。

結びに代えて

Access VBAは、決して「簡易的なツール作成言語」ではない。
アーキテクチャの設計次第で、数千人が利用するエンタープライズ級のシステムにもなり得る。

「接続を維持する」という安易な思考を捨て、「データをメモリ上で制御する」というプロフェッショナルな設計思想を持て。それが、ネットワークの制約に左右されない、堅牢で高速なアプリケーションを作る唯一の道である。

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