【実務・中級編】DAOとADOの共存環境:同一プロジェクト内で両者を使い分ける際の命名規則と管理術 – Access VBA解析バイブル

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

二つのエンジンを飼い慣らせ:Access VBAにおけるDAOとADO共存の極限設計論

業務自動化の現場において、Accessは単なる「データベース」ではない。それは、ローカルの高速なデータ操作(DAO)と、外部サーバーへの強力な接続(ADO)を使い分ける、ハイブリッドな「データハブ」である。

しかし、多くの開発者がここで致命的なミスを犯す。「DAOとADOの混在による、名前空間の衝突」だ。

`Dim rs As Recordset` と書いたとき、その `rs` はどちらのエンジンを指しているのか? コンパイラが勝手に判断した結果、実行時に「オブジェクトはこのプロパティをサポートしていません」という、原因特定に時間を溶かすエラーを吐き出す。これはプロフェッショナルとしてあってはならない失態だ。

本稿では、大規模プロジェクトでもバグを発生させない、DAOとADOを完璧に制御するための命名規則とアーキテクチャ設計を伝授する。

1. なぜ「名前の衝突」が死を招くのか

Access VBAにおいて、DAO(Data Access Objects)とADO(ActiveX Data Objects)は、共に `Recordset` や `Connection` といった、極めて一般的なオブジェクト名を持っている。

VBAの参照設定において、ライブラリの優先順位が高い方が優先的に適用される。もし君が、SQL Serverへの書き込みのためにADOを導入し、その後にローカルテーブル操作のためにDAOの参照を追加したとする。その際、型指定を曖昧にしていると、意図しない方のオブジェクトが生成され、コードの可読性は死に、デバッグの難易度は指数関数的に跳ね上がる。

「曖昧なコードは、将来の自分への時限爆弾である」。これを肝に銘じてほしい。

2. 鉄則:プレフィックスによる「型」の明示的定義

プロフェッショナルな設計では、オブジェクト名に「どのライブラリのオブジェクトか」を明示するプレフィックスを付与することを義務付ける。

推奨される命名規則

オブジェクト種別 プレフィックス 例
DAO Recordset `daoRs` `daoRsLocal`
DAO Database `daoDb` `daoDbCurrent`
DAO Workspace `daoWs` `daoWsMain`
ADO Recordset `adoRs` `adoRsRemote`
ADO Connection `adoConn` `adoConnSql`
ADO Command `adoCmd` `adoCmdUpdate`

このルールを徹底することで、コードを読んだ瞬間に「今、どのエンジンの、どのリソースを操作しているのか」が脳内に即座にマッピングされる。

3. 実践:堅牢なハイブリッド・データ連携モジュール

以下に、ローカルのDAO Recordsetからデータを読み取り、外部のSQL ServerへADOを用いて高速に転送する、プロダクションクオリティのコード例を示す。

このコードの肝は、「完全修飾名(Fully Qualified Name)」の使用と、「厳格なエラーハンドリング・ライフサイクル管理」にある。

Option Explicit

''' <summary>
''' ローカルのDAOレコードセットからSQL Serverへデータを同期する
''' </summary>
Public Sub SyncLocalToRemote()
    ' --- 宣言部: プレフィックスによる明示的な型定義 ---
    ' DAOオブジェクト
    Dim daoWs As DAO.Workspace
    Dim daoDb As DAO.Database
    Dim daoRs As DAO.Recordset
    
    ' ADOオブジェクト
    Dim adoConn As ADODB.Connection
    Dim adoCmd As ADODB.Command
    
    ' 制御用変数
    Dim strLocalSql As String
    Dim strRemoteConnStr As String
    Dim lngSuccessCount As Long
    
    ' エラーハンドリング
    On Error GoTo ErrorHandler

    ' --- 初期設定 ---
    strLocalSql = "SELECT ID, TargetValue FROM tblLocalData WHERE IsSynced = False"
    strRemoteConnStr = "Provider=SQLNCLI11;Data Source=YourServer;Initial Catalog=YourDB;Integrated Security=SSPI;"

    ' --- 1. DAOによるローカルデータ取得 ---
    Set daoWs = DBEngine.Workspaces(0)
    Set daoDb = CurrentDb
    Set daoRs = daoWs.OpenRecordset(strLocalSql, dbOpenSnapshot)

    ' --- 2. ADOによるリモート接続確立 ---
    Set adoConn = New ADODB.Connection
    adoConn.ConnectionString = strRemoteConnStr
    adoConn.CursorLocation = adUseClient ' クライアントサイドカーソルによる安定性確保
    adoConn.Open

    ' トランザクションの開始 (ADO側)
    adoConn.BeginTrans
    
    ' コマンドオブジェクトの準備 (パフォーマンス向上のため再利用)
    Set adoCmd = New ADODB.Command
    With adoCmd
        .ActiveConnection = adoConn
        .CommandText = "INSERT INTO tblRemoteData (RemoteID, RemoteValue) VALUES (?, ?)"
        .CommandType = adCmdText
    End With

    ' --- 3. データ転送ループ ---
    Do Until daoRs.EOF
        ' パラメータバインドによるSQLインジェクション防止と高速化
        adoCmd.Parameters.Append adoCmd.CreateParameter("@id", adInteger, adParamInput, , daoRs!ID)
        adoCmd.Parameters.Append adoCmd.CreateParameter("@val", adVarChar, adParamInput, 255, daoRs!TargetValue)
        
        adoCmd.Execute
        
        ' パラメータのクリア(次回のループのため)
        adoCmd.Parameters.Delete 0
        adoCmd.Parameters.Delete 0
        
        lngSuccessCount = lngSuccessCount + 1
        daoRs.MoveNext
    Loop

    ' 全て成功したらコミット
    adoConn.CommitTrans
    MsgBox lngSuccessCount & " 件のデータを正常に同期しました。", vbInformation

CleanUp:
    ' --- 4. ライフサイクル管理: 逆順での解放 ---
    ' メモリリークとファイルロックを防ぐため、生成の逆順で確実にNothingを代入する
    On Error Resume Next
    If Not adoCmd Is Nothing Then Set adoCmd = Nothing
    If Not adoConn Is Nothing Then
        If adoConn.State = adStateOpen Then adoConn.Close
        Set adoConn = Nothing
    End If
    If Not daoRs Is Nothing Then
        daoRs.Close
        Set daoRs = Nothing
    End If
    If Not daoDb Is Nothing Then Set daoDb = Nothing
    If Not daoWs Is Nothing Then Set daoWs = Nothing
    Exit Sub

ErrorHandler:
    ' エラー発生時はロールバック
    If Not adoConn Is Nothing Then
        If adoConn.State = adStateOpen Then adoConn.RollbackTrans
    End If
    
    MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
           "Error No: " & Err.Number & vbCrLf & _
           "Description: " & Err.Description, vbCritical, "Sync Error"
    Resume CleanUp
End Sub

4. アーキテクトからの警告:見落としがちな3つの落とし穴

設計を完璧にしたつもりでも、以下の「重み」を理解していなければ、本番環境でシステムは沈黙する。

① トランザクションの境界線

DAOの `Workspace.BeginTrans` と ADOの `Connection.BeginTrans` は、全く別物である。
ローカルの更新とリモートの更新を一つのトランザクションとしてまとめたい場合、VBA単体では不可能な場合が多い(分散トランザクションが必要になる)。「ローカルが成功してリモートが失敗した」際のリカバー戦略(補償トランザクション)をあらかじめ設計に組み込んでおけ。

② カーソル位置(CursorLocation)の選択

ADOを使用する際、`adUseClient`(クライアントサイド)を使うか `adUseServer`(サーバーサイド)を使うかは、パフォーマンスに直結する。
Accessから外部へ大量のデータを流し込む場合は、`adUseClient` を選択し、メモリ上に展開することで、ネットワークの往復回数を減らし、安定性を確保するのが定石だ。

③ オブジェクトの解放順序

コードの最後で `Set … = Nothing` を書くことは基本中の基本だが、重要なのは「逆順で解放すること」だ。
`Recordset` を閉じる前に `Connection` を閉じてはいけない。`Connection` を閉じる前に `Command` を破棄してはいけない。これを無視すると、メモリ上に「幽霊オブジェクト」が残り続け、Accessが不安定になる原因となる。

結論

DAOとADOの共存は、Access開発における「諸刃の剣」である。
しかし、「完全修飾名による型定義」と「プレフィックスによる厳格な命名規則」、そして「徹底したライフサイクル管理」を遵守すれば、これほど強力な武器はない。

曖昧さを排除せよ。それが、保守性の高い、真のプロフェッショナルが書くコードである。

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