【テクニカル・上級編】ADOの「Errorsコレクション」を駆使した、詳細なDBエラーログの自動生成ツール – Access VBA解析バイブル

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

「Err.Number」の限界を超えて:ADO Errorsコレクションによる、SQL Server接続における極限のデバッグ・アーキテクチャ

現場のエンジニアが書くコードの多くは、致命的な欠陥を抱えている。
それは、エラーハンドリングの「浅さ」だ。

`On Error GoTo Err_Handler` と書き、`MsgBox Err.Number & ” : ” & Err.Description` を出力して満足している。もし君がそう考えているのなら、君はまだ「システム」を運用しているのではなく、単に「ツール」を作っているに過ぎない。

特に、Access VBAをフロントエンドとし、バックエンドにSQL Serverを据えた分散アーキテクチャにおいて、クライアント側の `Err` オブジェクトが提供する情報は、氷山の一角に過ぎない。サーバー側で何が起きたのか、制約違反の詳細なコンテキストは何なのか、トランザクションがどの段階で破綻したのか。それらを知るためには、ADO(ActiveX Data Objects)の深淵、すなわち `Errors` コレクションを掌握しなければならない。

本稿では、レガシーなAccess環境からモダンなSQL Serverへの橋渡しにおいて、真に信頼に足る「詳細エラーログ生成メカニズム」の設計思想を解説する。

1. アーキテクチャの断絶:DAOとADOの決定的な違い

まず、根本的な認識を改める必要がある。DAO(Data Access Objects)とADOは、エラーの捉え方そのものが異なる。

  • DAO は、Jet/ACEエンジンという「ファイル操作」の延長線上にある。エラーはローカルなファイルシステムやファイル構造に起因することが多く、エラー情報は比較的単純だ。
  • ADO は、ネットワーク越しに「プロトコル(TDS等)」を介してサーバーと対話する。ここでのエラーは、ネットワークの瞬断、サーバー側のデッドロック、制約違反、タイムアウトなど、多層的なレイヤーから発生する。

クライアント側の `Err` オブジェクトは、ADOが受け取った「最初の、あるいは要約されたエラー」しか保持しない。しかし、SQL Serverは一つの操作に対して複数のエラーを返すことがある。この「エラーの連鎖」を捕捉できるのは、`ADODB.Connection` オブジェクトが保持する `Errors` コレクションだけだ。

2. 武器の選定:Errorsコレクションの深掘り

SQL Serverとの通信において、真に価値のある情報は以下のプロパティに集約されている。

  • `Description`: サーバーが返した詳細なメッセージ(「主キー制約違反」などの具体的な内容)。
  • `NativeError`: SQL Server固有のエラー番号。これこそが、MSDNを引くための真の鍵だ。
  • `SQLState`: ANSI標準のSQLステート。
  • `Severity`: エラーの深刻度。

これらを無視して `Err.Description` だけをログに書き込むのは、現場のエンジニアとしては怠慢と言わざるを得ない。

3. 実装:極限のデバッグ・ログ生成モジュール

以下に、私が長年の現場で磨き上げてきた、メモリ管理と詳細捕捉を両立させたログ生成の設計パターンを示す。ここでは、Windows APIを用いて実行時のプロセスIDやシステム情報を付与し、ログのトレーサビリティを極限まで高めている。

Option Compare Database
Option Explicit

' --- Windows API Declarations ---
' システムの文脈をログに刻むために、プロセスIDを取得する
Private Declare PtrSafe Function GetCurrentProcessId Lib "kernel32" () As Long

''' <summary>
''' 高精度DBエラーロガー
''' ADOのErrorsコレクションを走査し、詳細なコンテキストをログ出力する。
''' </summary>
Public Module ErrorLogger
    
    Private Const LOG_FILE_PATH As String = "C:\Logs\DB_Error_Log.txt" ' 本番環境では適切なパスへ

    ''' <summary>
    ''' ADO Connectionオブジェクトからエラーを抽出し、詳細ログを生成する
    ''' </summary>
    ''' <param name="cn">エラーが発生したADODB.Connectionオブジェクト</param>
    ''' <param name="sql">実行しようとして失敗したSQL文(任意)</param>
    Public Sub LogADODetails(ByRef cn As ADODB.Connection, Optional ByVal sql As String = "")
        Dim errObj As ADODB.Error
        Dim logMsg As String
        Dim pid As Long
        
        pid = GetCurrentProcessId()
        
        ' 1. 基本情報の構築
        logMsg = "==================================================" & vbCrLf
        logMsg = logMsg & "TIMESTAMP: " & Now & vbCrLf
        logMsg = logMsg & "PID: " & pid & " | USER: " & Environ("USERNAME") & vbCrLf
        logMsg = logMsg & "--------------------------------------------------" & vbCrLf
        
        If sql <> "" Then
            logMsg = logMsg & "FAILED SQL: " & sql & vbCrLf
        End If
        
        ' 2. ADO Errorsコレクションの走査 (ここが核心)
        ' 単一のエラーではなく、サーバーから返されたエラーの列をすべて記録する
        If cn.Errors.Count > 0 Then
            logMsg = logMsg & "[ADO Errors Found: " & cn.Errors.Count & "]" & vbCrLf
            For Each errObj In cn.Errors
                logMsg = logMsg & "  >> NativeError: " & errObj.NativeError & vbCrLf
                logMsg = logMsg & "     SQLState:   " & errObj.SQLState & vbCrLf
                logMsg = logMsg & "     Severity:   " & errObj.Severity & vbCrLf
                logMsg = logMsg & "     Description: " & errObj.Description & vbCrLf
            Next errObj
        Else
            ' Connection.Errorsが空の場合、VBA標準のErrオブジェクトを確認する
            logMsg = logMsg & "[Standard VBA Error]" & vbCrLf
            logMsg = logMsg & "  >> Err.Number: " & Err.Number & vbCrLf
            logMsg = logMsg & "     Err.Description: " & Err.Description & vbCrLf
        End If
        
        logMsg = logMsg & "==================================================" & vbCrLf & vbCrLf
        
        ' 3. ファイルへの書き出し (Appendモード)
        Call WriteToLogFile(logMsg)
    End Sub

    Private Sub WriteToLogFile(ByVal text As String)
        On Error Resume Next ' ログ出力自体の失敗でメイン処理を止めない
        Dim fso As Object
        Set fso = CreateObject("Scripting.FileSystemObject")
        
        ' ファイルがなければ作成、あれば追記
        Dim ts As Object
        Set ts = fso.OpenTextFile(LOG_FILE_PATH, 8, True)
        ts.WriteLine text
        ts.Close
        
        ' オブジェクトの明示的解放(メモリ最適化の鉄則)
        Set ts = Nothing
        Set fso = Nothing
        On Error GoTo 0
    End Sub
End Module

実践的な利用パターン(トランザクション管理下)

エラーログを機能させるには、呼び出し側のアーキテクチャが重要だ。特にトランザクション処理においては、エラー発生時に「即座にロールバック」しつつ、「エラーの詳細を捕捉」するという順序を厳守しなければならない。

Public Sub ExecuteComplexTransaction()
    Dim cn As ADODB.Connection
    Dim sql As String
    
    Set cn = New ADODB.Connection
    sql = "UPDATE Inventory SET Stock = Stock - 1 WHERE ItemID = 101" ' 例としてのSQL
    
    On Error GoTo Transaction_Error
    
    ' 接続設定
    cn.ConnectionString = "Provider=SQLNCLI11;Data Source=YourServer;Initial Catalog=YourDB;Integrated Security=SSPI;"
    cn.Open
    
    ' トランザクション開始
    cn.BeginTrans
    
    ' 業務ロジック実行
    cn.Execute sql
    
    ' 全て成功すればコミット
    cn.CommitTrans
    Debug.Print "Transaction Committed Successfully."

Exit_Sub:
    ' オブジェクトの解放は、成功・失敗に関わらず必ず行う
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
        Set cn = Nothing
    End If
    Exit Sub

Transaction_Error:
    ' 1. まずロールバック(接続が生きている場合のみ)
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then
            cn.RollbackTrans
            Debug.Print "Transaction Rolled Back."
        End If
        
        ' 2. エラーログの詳細出力を実行
        ' ここで、実行しようとしたSQLを渡すことで、再現性を確保する
        Call LogADODetails(cn, sql)
    End If
    
    MsgBox "致命的なエラーが発生しました。詳細はログを確認してください。", vbCritical
    Resume Exit_Sub
End Module

4. 極限の知見:メモリ管理とオブジェクトのライフサイクル

シニアエンジニアとして、以下の2点には常に神経を尖らせてほしい。

オブジェクトの明示的解放 (`Set obj = Nothing`)

Access VBAはCOM(Component Object Model)の塊だ。特にADOオブジェクトは外部プロセス(SQL Server)と密接に結合している。エラーハンドリング内で `Set cn = Nothing` を怠れば、たとえ `RollbackTrans` を呼んでいても、サーバー側で「ゾンビ・トランザクション」が残り続け、他のユーザーのプロセスをロックし続ける原因となる。

Windows APIの活用

ログに `GetCurrentProcessId` を含める理由は、マルチユーザー・マルチプロセス環境において、どのプロセス(あるいはどのインスタンスのAccess)がエラーを吐いたのかを特定するためだ。これは、不具合の再現性を追い込む際の「決定的な証拠」となる。

結論

エラーハンドリングとは、単に「止まらないようにすること」ではない。「何が起きたのかを、後から誰が見ても完璧に再現・解析できるようにすること」である。

`Err.Number` という甘えを捨て、`ADODB.Errors` という真実を掴め。それが、レガシーな資産を、堅牢なエンタープライズ・システムへと昇華させるための唯一の道だ。

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