【技術コラム】ワークテーブル廃止!「ADO切断+ディクショナリ方式」で作る超高速・一覧編集フォームの極意
ファイルサーバー上にバックエンド用のAccessファイル(.accdb)を配置し、複数人でフロントエンドから共有利用するシステムにおいて、
・一覧編集フォームの表示が重い(連続フォームやデータシート形式など)
・スクロールが引っかかる
・Wi-FiやVPN環境で固まる
・ファイル破損やロック競合が頻発する
といった問題に悩まされた経験はないでしょうか。
特に、大量データを扱う「一覧形式の編集フォーム」では、従来の実装方法に限界があります。
今回は、ローカルのワークテーブル(一時テーブル)を一切作成せず、Accessのパフォーマンスを極限まで引き出すアーキテクチャ、
「ADO切断+ディクショナリ方式」について、実践的な3つの極意をご紹介します。
1. 従来手法と「超えられない壁」
Accessで一覧編集フォームを作る際、一般的なのは次のような構成です。
1.バックエンドからデータを取得
2.フロントエンド内にワークテーブルを作成
3.そのワークテーブルをフォームへ連結(バインド)
この方式は安全で扱いやすい反面、データ件数が増えると深刻な問題が発生します。
■ ワークテーブル作成が重い
数万件規模になると、ワークテーブル生成処理などによるローカルディスクへの物理書き込みに数十秒かかるケースがあります。
さらに、フロントエンドファイル(.accdb)の肥大化も引き起こします。
■ ネットワーク越しの一覧フォームが遅い
リンクテーブル経由の一覧フォームでは、Access特有の
「細かく頻繁に通信する仕様(いわゆるチャッティ通信)」が問題になります。
特にWi-FiやVPN環境では、
・スクロール時の引っかかり
・編集時の待ち時間
・画面フリーズ
が発生しやすくなります。
こうした「ディスクI/O」と「ネットワーク通信」の壁を突破するために辿り着いたのが、
「ディスクへ書き込まず、すべてメモリ上で処理する」というアプローチです。
一般的に、メモリ上の処理はディスクアクセスと比べて桁違いに高速であり、
特に大量データを扱う場合、この差が体感速度へ大きく影響します。
2. 極意その1:レコードソースを空にして直接バインド
通常、フォームの「レコードソース」にテーブルやクエリを設定すると、Accessは裏側でバックエンドへ接続し、自動的にデータ管理を行います。
しかし、この「Accessによる自動制御」こそが、速度低下や不要通信の原因になります。
そこで本方式では、フォームの「レコードソース」をあえて空欄にします。
その代わり、VBAからADO(ActiveX Data Objects)を使い、データをメモリ上へ一括取得し、そのRecordsetオブジェクト自体をフォームへ直接渡します。
<サンプルで使用するテーブルのイメージ>

コード例:切断 ADO Recordset の直接バインド
' ADOでバックエンドファイルからデータを取得
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open _
"SELECT ID, ItemA, ItemB FROM T_SampleData", _
cn, _
adOpenStatic, _
adLockBatchOptimistic
' 【重要】
' バックエンドとの接続を切断
Set rs.ActiveConnection = Nothing
cn.Close
' メモリ上のRecordsetをフォームへ直接バインド
Set Me.Recordset = rs
※切断Recordsetとして扱うため、CursorLocation には adUseClient を指定しています。
この方式では、
・ワークテーブル作成不要
・ワークテーブル生成に伴うディスク書き込みが不要
・通信回数激減
となるため、数万件規模でも高速に表示できます。
スクロールも非常に滑らかで、ローカルデータを扱っているかのような操作感になります。
<フォームの実行結果のイメージ>

3. 極意その2:Accessのお節介な「自動保存」を封じる
Accessのフォームには、
行移動時に勝手に保存しようとする
という非常に強力な自動保存仕様があります。
通常、この挙動を完全制御するのはかなり困難です。
しかし本方式では、ADO Recordset を「切断状態」にしているため、この問題を根本的に回避できます。
ポイントはこの1行です。
Set rs.ActiveConnection = Nothing
この時点で、フォーム上のデータはバックエンドとの接続を失っています。
つまり、ユーザーが行移動しても、フォームにバインドされているRecordsetが切断状態のため、Accessは自動更新を実行できません。
結果として、
・意図しない自動保存を防止
・「保存ボタン押下時のみ更新」という制御が可能
・更新タイミングを開発者側で明示的に制御可能
になります。
これは、業務システムにおける安全性向上にも非常に効果的です。
4. 極意その3:ディクショナリで「変更内容」を管理する
Recordset が切断状態である以上、ユーザーが編集した内容は、そのままでは保存されません。
そこで活用するのが、VBAの Scripting.Dictionary です。
このディクショナリに、
・更新データ
・新規データ
・削除対象
を蓄積し、「保存ボタン押下時」にまとめてDBへ反映します。
コード例:変更内容をディクショナリへ記録
Private Sub Form_BeforeUpdate(Cancel As Integer)
Dim dictKey As String
If dictChanges Is Nothing Then
Set dictChanges = CreateObject("Scripting.Dictionary")
End If
' IDが空っぽ(発番前)かどうかで新規と既存を完璧に切り分ける
If IsNull(Me!ID.Value) Then
' 【未保存の新規行】仮の行番号をキーにする
dictKey = "NEW_" & Me.CurrentRecord
' ★極意:配列の「最初(インデックス0)」に命令フラグ("SAVE")を配置
dictChanges.Item(dictKey) = Array("SAVE", Null, Me!ItemA.Value, Me!ItemB.Value)
Else
' 【既存の編集行】本物のIDをキーにして記録
dictKey = CStr(Me!ID.Value)
' ★極意:配列の「最初(インデックス0)」に命令フラグ("SAVE")を配置
dictChanges.Item(dictKey) = Array("SAVE", Me!ID.Value, Me!ItemA.Value, Me!ItemB.Value)
End If
End Sub
■ 新規行と既存行を正確に判定
IsNull(ID) を利用することで、
・未保存の新規行
・既存データの編集
を明確に区別できます。
新規追加後に再編集された場合でも、同じ仮キーへ上書きされるため、状態管理が崩れません。
■ 命令フラグで処理を切り分ける
配列の最初に、
・"SAVE"
・"DELETE"
などの命令フラグを保持することで、保存時に
・INSERT
・UPDATE
・DELETE
を正確に振り分けられます。
■ ディクショナリは「最終状態」だけを保持できる
Dictionaryは同じキーに対して自動上書きされるため、同一行を何度編集しても、最終状態だけが残ります。
つまり、
・差分管理がシンプル
・メモリ効率が良い
・更新処理を最適化できる
という大きなメリットがあります。
まとめ
「ADO切断」による超高速表示と、
「ディクショナリ」による堅牢な変更管理。
この2つを組み合わせることで、Access最大のボトルネックである
「ワークテーブルへの物理アクセス」
を完全に排除できます。
長時間ロック状態を維持しないため、ネットワーク切断時の不整合リスクも低減できます。
さらに、ユーザー操作中はデータベースと切断されており、保存時のみ瞬間的に接続するため、
・排他ロック競合の激減
・ファイル破損リスクの低下
・多人数同時利用時の安定性向上
といった副次効果も得られます。
本方式は非常に高速ですが、その代わり更新管理や競合制御を開発者側で設計する必要があります。
大量データを扱うAccessシステムの高速化や、ファイル共有環境での安定運用を検討されている方は、ぜひ一度このアーキテクチャを試してみてください。
大手SIerに長年常駐し大規模システムの経験を重ねプログラムスキルとDB設計を磨く。AccessおよびExcelのVBAを得意とする。
⇒お仕事のご依頼はこちら


