表面的なエラー番号に騙されるな: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` コレクションを掌握し、サーバー側の「声」を正確に聞き取ること。それが、バグのない、プロフェッショナルな業務自動化ツールを構築するための第一歩である。
