【極限の知見】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は、決して「簡易的なツール作成言語」ではない。
アーキテクチャの設計次第で、数千人が利用するエンタープライズ級のシステムにもなり得る。
「接続を維持する」という安易な思考を捨て、「データをメモリ上で制御する」というプロフェッショナルな設計思想を持て。それが、ネットワークの制約に左右されない、堅牢で高速なアプリケーションを作る唯一の道である。
