DAOの限界を突破する!APIレベルでのテーブル定義操作の安定化
レガシーシステムの深部において、Access VBAは依然として強力な生存戦略の一つである。しかし、数千人規模の同時接続や、外部システムとの密な連携が求められるモダンなエンタープライズ環境において、標準のDAO(Data Access Objects)によるテーブル定義の動的変更は、時としてシステムを崩壊させる「パンドラの箱」となる。
`TableDef`や`Field`オブジェクトの操作中に発生する不可解なエラー、背後で肥大化するJet/ACEエンジンのメモリリーク、そして排他制御の壁。これらはVBA初学者のコーディングミスではなく、DAOという抽象化層が持つ構造的な限界に起因する。
本稿では、DAOの限界を完全に突破し、Windows API(ODBC API / Jet Engine API)を直接叩くことで、極限の安定性とパフォーマンスを手に入れるアーキテクチャを解説する。
—
1. なぜDAOのテーブル定義操作は崩壊するのか?
DAOを用いた動的なスキーマ変更(フィールドの追加、型変更、インデックスの動的生成など)は、開発フェーズでは軽快に動作するため油断しやすい。しかし、本番環境の過酷な負荷の下では、以下の致命的な問題が露呈する。
内部キャッシュの不整合とメモリリーク
DAOは内部的にCOM(Component Object Model)のラッパーとして動作しており、オブジェクト階層(`DBEngine.Workspaces(0).Databases(0).TableDefs`)をトラバースする。この過程で生成される一時的なCOM参照は、VBAのガベージコレクションのタイミングと常に同期するわけではない。特に、ループ内で`TableDef`や`Field`を生成・破棄するコードでは、VBA側で `Set obj = Nothing` を記述しても、Jetエンジン側のメモリ空間に参照が残り続け、最終的に「メモリ不足(Error 7)」や「リソースが不足しています」という致命的なクラッシュを引き起こす。
排他制御(ロック)の排他性
DAOでのスキーマ変更は、対象テーブルに対して強力な排他ロックを要求する。マルチユーザー環境において、他のセッションが1行でもレコードを保持している、あるいはフォームのレコードソースとしてバインドされているだけで、`3219(記述が無効です)`や`3033(権限がありません)`といった例外が容赦なく発生する。
この壁を越えるには、DAOの抽象化層をバイパスし、Jetデータベースエンジンに直接低レベルの命令を流し込むアプローチが必要となる。
—
2. APIレベル制御の核心:Jet Engine / ODBC APIの直接叩き
Access(ACE/Jetエンジン)の背後には、SQL文の実行エンジンだけでなく、データ定義言語(DDL)を直接処理するISAMドライバ層が存在する。
VBAからこのレイヤーにアクセスする最も堅牢な手段は、ADO(ActiveX Data Objects)の `Connection.Execute` を用いた純粋なSQLパススルー、あるいは必要に応じたWindows API(`SQLExecDirect` 等)の駆使である。
特に、テーブル定義の変更を「トランザクション内での非同期DDL」として安全に処理するためには、DAOのオブジェクトモデルを介さず、SQL文字列を直接エンジンにコンパイルさせることが、メモリリークを防ぐ唯一の解となる。
—
3. 【実装】メモリリーク皆無・安全な動的スキーマ変更エンジン
以下のコードは、DAOのオブジェクト変数保持によるメモリリークを完全に排除し、ADOの接続プールとダイレクトDDLを活用してテーブル定義(フィールドの動的追加・インデックス付与)を行う、実務投入レベルのクラスモジュールのコアロジックである。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ クラス名: clsSchemaEngine
‘ 概要: DAOの限界を突破し、ADO/Jet API経由で極限まで安定したスキーマ変更を行う
‘ =========================================================================
Private m_ConnectionString As String
Private Sub Class_Initialize()
‘ 現在のカレントデータベースに対する最適化されたADO接続文字列を構築
m_ConnectionString = “Provider=Microsoft.ACE.OLEDB.12.0;Data Source=” & CurrentDb.Name & “;Persist Security Info=False;”
End Sub
‘ ————————————————————————-
‘ メソッド: EnsureFieldExists
‘ 概要: 指定したフィールドが存在しない場合のみ、安全に追加する(トランザクション保護付き)
‘ ————————————————————————-
Public Function EnsureFieldExists(ByVal tableName As String, ByVal fieldName As String, ByVal fieldTypeSql As String) As Boolean
Dim conn As Object
Dim sql As String
Dim isFieldExists As Boolean
EnsureFieldExists = False
isFieldExists = False
‘ ADO Connectionの生成(DAO.Databaseは使用しない)
Set conn = CreateObject(“ADODB.Connection”)
conn.Open m_ConnectionString
‘ トランザクション開始によるアトミック性の確保
conn.BeginTrans
On Error GoTo ErrorHandler
‘ 1. スキーマニア(Schema 0 = adSchemaColumns)を用いて確実かつ高速に存在確認
‘ DAOのTableDefsトラバースによるメモリリークを完全に回避
Dim rsSchema As Object
Set rsSchema = conn.OpenSchema(4, Array(Empty, Empty, tableName, fieldName)) ‘ 4 = adSchemaColumns
If Not rsSchema.EOF Then
isFieldExists = True
End If
rsSchema.Close
Set rsSchema = Nothing
‘ 2. 存在しない場合のみ、ダイレクトDDLを実行
If Not isFieldExists Then
sql = “ALTER TABLE [” & tableName & “] ADD COLUMN [” & fieldName & “] ” & fieldTypeSql
conn.Execute sql, , 128 ‘ 128 = adExecuteNoRecords (結果セットを返さないことで高速化)
End If
conn.CommitTrans
conn.Close
Set conn = Nothing
EnsureFieldExists = True
Exit Function
ErrorHandler:
Dim errDesc As String
errDesc = Err.Description
On Error Resume Next
If Not conn is Nothing Then
conn.RollbackTrans
conn.Close
Set conn = Nothing
End If
‘ 異常終了時のログ出力や独自例外スロー
Err.Raise vbObjectError + 1000, “clsSchemaEngine”, “スキーマ変更に失敗しました: ” & errDesc
End Function
このコードの優位性
1. DAOオブジェクトの完全不使用: `TableDef` や `Field` を変数としてメモリ上に保持しないため、COMの解放漏れ(メモリリーク)が構造的に発生しない。
2. `OpenSchema` による超高速存在チェック: DAOのループ処理とは異なり、ADOのシステムカタログ照会(`OpenSchema`)はO(1)に近い速度でフィールドの存在を特定する。
3. トランザクション制御: DDL文であってもADO経由であればトランザクション内で実行できるため、途中でエラーが発生してもデータベースファイルが半端な状態で破損することを防ぐ。
—
4. 現場の知見:レガシー環境保守とシステム間連携の極限安定化
長年稼働しているAccessシステムにおいて、外部の基幹系(SQL ServerやOracle、あるいはCSV群)から夜間バッチ等で自動生成されたテーブルを取り込む際、スキーマの型不一致やフィールドの欠損が原因でVBAが停止する事故が後を絶たない。
このようなシステム間連携の最前線では、以下の「防衛的プログラミング」を徹底すべきである。
- DAOの `Refresh` メソッドの呪縛からの解放:
DAOでテーブル定義を変更した際、変更を反映させるために `CurrentDb.TableDefs(0).Refresh` を連呼するコードを見るが、これはJetエンジンのキャッシュを強制破棄するためパフォーマンスを著しく低下させる。ADO接続を都度確立・破棄するモデルであれば、この `Refresh` の呪縛から完全に解放される。
- 排他制御競合時のリトライパターン:
APIレベルの操作であっても、他のプロセスがテーブルをロックしている瞬間には競合が発生する。以下のようなミリ秒単位のバックオフ(待機)を伴うリトライロジックをラップすることで、無人稼働するサーバーサイドAccessの無人運用が可能となる。
‘ リトライロジックの概念的実装
Dim retryCount As Integer
For retryCount = 1 to 5
On Error Resume Next
engine.EnsureFieldExists “T_Log”, “ProcessStatus”, “LONG”
If Err.Number = 0 Then Exit For
On Error GoTo 0
‘ 1秒待機してリトライ
VBA.Interaction.DoEvents
Application.Wait Now + TimeValue(“00:00:01”) retryCount
Next retryCount
—
5. チーフアーキテクトからの提言
「Accessだから不安定なのは仕方ない」というのは、アーキテクトの怠慢でしかない。
DAOという何十年も前の抽象化レイヤーの背後にある挙動を理解し、適切なレイヤー(ADO / 低レベルAPI)へ処理をバイパスすることで、Access VBAは驚異的な堅牢性を獲得する。
メモリのライフサイクルを支配し、リソースのリークを根絶せよ。コードの細部にまで意志を通わせた者だけが、レガシーの呪縛からシステムを解放することができる。
