【実務・中級編】ADOの接続エラーを詳細にキャッチ:Errorsコレクションによる原因特定とログ出力 – Access VBA解析バイブル

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

表面的なエラー番号に騙されるな:ADOにおける「真実」の掴み方

業務自動化の現場において、Accessを単なる「データの入れ物」ではなく、SQL Server等の外部データベースと連携する「フロントエンド・エンジン」として活用する場合、避けては通れない壁がある。それは「エラーハンドリングの解像度」だ。

多くの開発者は、エラーが発生すると `MsgBox Err.Description` を表示して満足してしまう。しかし、SQL Serverとの連携において、VBAの `Err` オブジェクトが吐き出すメッセージは、しばしば「表面的な皮一枚」に過ぎない。

「実行時エラー ‘3704’: オブジェクトが閉じているか、または存在しません」

このメッセージだけを見て、君はデバッグを終えられるか? もし答えが「Yes」なら、君の作ったツールは、本番環境で発生する「データ型の不一致」や「制約違反」といった真の災厄を、ユーザーに隠蔽したまま放置することになる。

本稿では、ADO(ActiveX Data Objects)の真価を引き出し、SQL Serverが叫んでいる「真の理由」を完璧にキャッチするための、アーキテクト視点の設計手法を伝授する。

アーキテクチャの勘違いが招く「ブラックボックス」化

まず、DAOとADOの決定的な違いを脳に刻んでほしい。

  • DAO (Data Access Objects): Accessのエンジン(ACE/Jet)に密着したアーキテクチャ。エラーは基本的にAccess内部の論理に基づいている。
  • ADO (ActiveX Data Objects): OLE DBプロバイダーを介して、外部のデータソース(SQL Server等)へ通信するための「橋渡し」のアーキテクチャ。

ADOを使用しているとき、エラーの発生源(Origin)は多くの場合、VBAではなく「接続先のSQL Server」または「OLE DBプロバイダー」にある。

VBAの `Err` オブジェクトは、プロバイダーから返ってきたエラーを「VBAが理解できる形式」にラップ(包み隠)して報告してくる。このラップの過程で、SQL Serverが詳細に返してくれた「どの制約に触れたのか」「どの列の値が不正なのか」という極めて重要な情報が削ぎ落とされてしまうのだ。

この情報の欠落こそが、調査時間を膨大にさせる「ブラックボックス」の正体である。

核心:`Connection.Errors` コレクションを解剖する

ADOには、VBA標準の `Err` オブジェクトとは別に、`Connection` オブジェクトが保持する `Errors` コレクションが存在する。

SQL Serverでエラーが発生した際、プロバイダーは複数のエラー情報をこのコレクションに格納する。たとえVBA側で一つのエラーとして検知されたとしても、その裏側には「SQL State」や「ネイティブなエラー番号」を含む詳細な情報が隠されている。

我々プロフェッショナルがすべきことは、このコレクションをループで回し、「プロバイダーが提示している生の情報」をすべて抽出することだ。

実践:プロダクション品質のエラーハンドリング・テンプレート

以下に、実務でそのまま組み込める、堅牢なエラーキャッチ・ロギングのコード例を示す。単なる表示ではなく、ログファイルへ詳細を書き出す設計にしている。

Option Explicit

' ======================================================================
' Module: modDatabaseAccess
' Description: SQL Server連携における堅牢なエラーハンドリング実装
' ======================================================================

Public Sub ExecuteRobustTransaction()
    Dim conn As ADODB.Connection
    Dim strConn As String
    
    ' 接続文字列(環境に合わせて適切に管理すること)
    strConn = "Provider=SQLNCLI11;Server=YOUR_SERVER;Database=YOUR_DB;Trusted_Connection=yes;"
    
    Set conn = New ADODB.Connection
    
    On Error GoTo ErrorHandler
    
    ' 1. 接続開始
    conn.Open strConn
    
    ' 2. トランザクションの開始
    conn.BeginTrans
    
    ' --- ここにビジネスロジックを記述 ---
    ' 例:わざと制約違反を起こすSQLを実行(テスト用)
    conn.Execute "INSERT INTO TargetTable (ID, Name) VALUES (1, 'Test');" 
    ' ----------------------------------
    
    ' 3. コミット
    conn.CommitTrans
    Debug.Print "Transaction Committed Successfully."

ExitProcedure:
    ' 後処理:オブジェクトの解放と接続のクローズ
    On Error Resume Next
    If Not conn Is Nothing Then
        If conn.State = adStateOpen Then conn.Close
        Set conn = Nothing
    End If
    Exit Sub

ErrorHandler:
    ' 4. 重要:ADO特有の詳細エラー解析
    ' VBAのErrオブジェクトだけでなく、conn.Errorsを解析する
    Call HandleADOErrors(conn, Err)
    
    ' トランザクション中のエラーであればロールバック
    If Not conn Is Nothing Then
        If conn.State = adStateOpen Then
            conn.RollbackTrans
            Debug.Print "Transaction Rolled Back."
        End If
    End If
    
    Resume ExitProcedure
End Sub

''' <summary>
''' ADOのエラーコレクションを解析し、詳細なログを出力する
''' </summary>
''' <param name="conn">エラーを保持しているConnectionオブジェクト</param>
''' <param name="vbaErr">VBA標準のErrオブジェクト</param>
Private Sub HandleADOErrors(ByRef conn As ADODB.Connection, ByRef vbaErr As Err)
    Dim errItem As ADODB.Error
    Dim logMsg As String
    Dim logFilePath As String
    
    logFilePath = ThisWorkbook.Path & "\error_log.txt"
    logMsg = "--- Error Log: " & Now & " ---" & vbCrLf
    
    ' まず、VBA標準のエラー情報を記録
    logMsg = logMsg & "[VBA Err] Number: " & vbaErr.Number & " | Desc: " & vbaErr.Description & vbCrLf
    
    ' 次に、ADO Errorsコレクションを徹底的に走査
    If conn.Errors.Count > 0 Then
        logMsg = logMsg & "[ADO Details] Found " & conn.Errors.Count & " error(s):" & vbCrLf
        
        For Each errItem In conn.Errors
            logMsg = logMsg & "  -> Native Error: " & errItem.NativeError & vbCrLf
            logMsg = logMsg & "     SQL State: " & errItem.SQLState & vbCrLf
            logMsg = logMsg & "     Description: " & errItem.Description & vbCrLf
            logMsg = logMsg & "     Source: " & errItem.Source & vbCrLf
        Next errItem
    Else
        logMsg = logMsg & "[ADO Details] No specific ADO errors found." & vbCrLf
    End If
    
    logMsg = logMsg & "----------------------------------------" & vbCrLf
    
    ' 1. デバッグ出力
    Debug.Print logMsg
    
    ' 2. ファイルへのログ書き出し(プロダクション用)
    Call WriteLogToFile(logFilePath, logMsg)
    
    ' 3. ユーザーへの通知(詳細は隠し、最低限の情報を伝える)
    MsgBox "データベース処理中にエラーが発生しました。" & vbCrLf & _
           "管理者にログを報告してください。" & vbCrLf & _
           "(Error Code: " & vbaErr.Number & ")", vbCritical, "システムエラー"
End Sub

Private Sub WriteLogToFile(ByVal filePath As String, ByVal message As String)
    Dim fso As Object
    Dim ts As Object
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    ' 追記モード(8)で開く
    Set ts = fso.OpenTextFile(filePath, 8, True)
    ts.WriteLine message
    ts.Close
    
    Set ts = Nothing
    Set fso = Nothing
End Sub

堅牢な設計のための3つの鉄則

このコードを単なる「コピペ」で終わらせてはいけない。設計思想を理解せよ。

1. トランザクションのライフサイクルを厳守せよ

`BeginTrans` を呼んだなら、必ず `CommitTrans` か `RollbackTrans` のどちらかで終わらせる必要がある。エラー発生時に `Rollback` を忘れると、接続がロックされたままになり、後続の処理や他のユーザーの操作を阻害する致命的なバグ(デッドロックの種)を生む。

2. 「エラーの通知」と「エラーの報告」を分離せよ

ユーザーに `NativeError: 2627`(主キー制約違反)のような、生々しいデータベースの内部構造を見せてはいけない。それはセキュリティ上のリスクであり、ユーザーの混乱を招くだけだ。

  • ユーザーへ: 「処理に失敗しました。管理者に連絡してください」という抽象化されたメッセージを。
  • ログへ: `SQLState` や `NativeError` を含む詳細な技術情報を。

この分離こそが、優れたUI/UXと保守性を両立させる鍵である。

3. オブジェクトの解放は「防御的」に行え

`ExitProcedure` におけるオブジェクトの解放は、`On Error Resume Next` を活用して、たとえクローズ処理中にエラーが起きても、確実にメモリを解放するように設計せよ。リソースのリークは、長時間稼働する業務ツールにおいて、最終的にアプリケーションのクラッシュを招く。

結論

ADOを用いた開発において、`Err` オブジェクトだけでエラーを捌こうとするのは、目隠しをして戦場を駆けるようなものだ。`Connection.Errors` コレクションを掌握し、サーバー側の「声」を正確に聞き取ること。それが、バグのない、プロフェッショナルな業務自動化ツールを構築するための第一歩である。

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