SQL Server連携の罠を突破せよ:`dbSeeChanges` エラーの正体と、堅牢なDAOアーキテクチャの設計指針
Access VBAで開発を進め、いざSQL Serverをバックエンドに据えた途端、昨日まで動いていたコードが「実行時エラー ‘3022’: 更新できません。現在ロックされています」あるいは「更新可能なレコードセットではありません」といったエラーを吐いて止まる。
開発現場でこの問題に直面したとき、多くのプログラマは「とりあえず `dbSeeChanges` オプションを付ければ直る」という、表面的な解決策に飛びつく。
だが、それではエンジニアとして三流だ。
なぜそのエラーが発生するのか。なぜそのオプションが必要なのか。そして、なぜ単にオプションを足すだけでは「真に堅牢なシステム」とは呼べないのか。
本記事では、Access VBAとSQL Serverを連携させる際に避けては通れない、DAO(Data Access Objects)の挙動の深淵と、プロフェッショナルが実践すべきトランザクション管理の設計思想を伝授する。
1. なぜエラーは起きるのか?:Identity Column(自動採番)のジレンマ
このエラーの根本原因は、Access(ACEエンジン)とSQL Serverの間の「データの整合性を確認するプロトコルの違い」にある。
発生のメカニズム
SQL Serverのテーブルには、通常 `IDENTITY` プロパティを持つ列(自動採番列)が存在する。データを `INSERT` した際、SQL Server側で新しいIDが自動的に生成される。
ここで、DAOがデータを追加しようとしたとき、以下のプロセスが発生する:
1. DAOがSQL Serverに「データを追加してくれ」と命令を出す。
2. SQL Serverがデータを書き込み、新しいID(例:`ID=101`)を生成する。
3. ここが重要だ。 DAOは、自分が書き込んだデータが正しくサーバーに反映されたか、そして生成された新しいIDは何なのかを確認しようとする。
もし、データベースを開く際に `dbSeeChanges` オプションを指定していない場合、DAOは「サーバー側でデータに変化(IDの生成など)が起きる可能性」を考慮しない設計で動作する。その結果、サーバー側での変更を検知できず、「整合性が取れない」と判断して更新を拒否する。これが `dbSeeChanges` エラーの正体だ。
2. DAO vs ADO:アーキテクチャの選択肢
SQL Serverとの連携において、我々は常に二つの武器を使い分ける必要がある。
| 特徴 | DAO (Data Access Objects) | ADO (ActiveX Data Objects) |
|---|---|---|
| 得意領域 | リンクテーブル経由の操作、Accessネイティブな挙動 | 直接的なSQL Serverへの通信、高度なデータ型 |
| パフォーマンス | リンクテーブル経由なら極めて高速 | ネットワーク越しに直接叩く際に有利 |
| 整合性管理 | `dbSeeChanges` 等のオプション制御が必須 | ドライバレベルで吸収されることが多い |
業務効率化ツールにおいて、Accessのフォームやレポートを最大限活用したいのであれば、「リンクテーブル + DAO」の組み合わせが最も生産性が高い。ただし、その代償として、我々は `dbSeeChanges` という「作法」をマスターしなければならない。
3. プロダクション・グレードの設計:堅牢な実装パターン
単に `OpenDatabase` にオプションを添えるだけでは、不十分だ。実務レベルのアプリケーションでは、「エラーハンドリング」「トランザクションの原子性(Atomicity)」「リソースの解放」をセットで設計しなければならない。
以下に、私が設計の現場で標準として推奨している、トランザクション制御を含む実装テンプレートを示す。
【実践】堅牢なデータ更新モジュール
Option Explicit
' ======================================================================================
' モジュール名: modDatabaseManager
' 概要: SQL Server連携における安全なデータベース操作を管理する
' 設計思想:
' 1. dbSeeChangesを強制し、Identity列によるエラーを未然に防ぐ
' 2. トランザクションによる「全成功か全失敗か」の保証
' 3. エラー発生時の確実なロールバックとリソース解放
' ======================================================================================
Public Sub UpdateCustomerData(ByVal customerID As Long, ByVal newName As String)
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strConn As String
' 接続文字列(実際には環境に合わせて管理すること)
' リンクテーブルを使用する場合でも、dbを開く際のオプションが重要
On Error GoTo ErrorHandler
' 1. データベース・オブジェクトの生成
' dbSeeChanges: SQL ServerのIdentity列の変更を検知するために必須
' dbFailOnError: エラーが発生した場合に即座にロールバック可能な状態にする
Set db = OpenDatabase("Your_Linked_Table_Path_Or_CurrentDB", False, False, dbSeeChanges Or dbFailOnError)
' 2. トランザクションの開始
' 複数の操作を行う場合、途中でエラーが起きてもデータが中途半端に残らないようにする
DBEngine.BeginTrans
Debug.Print "Transaction Started."
' 3. レコードセットの操作
' SQL Serverのリンクテーブルに対して操作を行う
Set rs = db.OpenRecordset("SELECT * FROM T_Customers WHERE CustomerID = " & customerID, dbOpenDynaset)
If Not rs.EOF Then
rs.Edit
rs!CustomerName = newName
rs.Update
Debug.Print "Record Updated."
Else
Err.Raise vbObjectError + 513, "modDatabaseManager", "対象のレコードが見つかりません。"
End If
' 4. コミット(確定)
' すべての処理が正常に終了した場合のみ、変更を確定させる
DBEngine.CommitTrans
Debug.Print "Transaction Committed Successfully."
CleanUp:
' 5. リソースの解放(メモリリーク防止)
On Error Resume Next
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
If Not db Is Nothing Then
db.Close
Set db = Nothing
End If
Exit Sub
ErrorHandler:
' 6. エラーハンドリングとロールバック
' エラーが発生した場合は、直ちにトランザクションを破棄し、元の状態に戻す
Debug.Print "Error Detected: " & Err.Description
If Err.Number <> 0 Then
DBEngine.Rollback
Debug.Print "Transaction Rolled Back."
End If
' ユーザーへの通知(実務ではログ出力と併用)
MsgBox "データ更新中に致命的なエラーが発生しました。" & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical, "システムエラー"
Resume CleanUp
End Sub
4. チーフアーキテクトからの助言:保守性を高めるために
コードを書いた後、君は自分自身にこう問いかけなければならない。
Q1. その `OpenDatabase` はどこに隠されているか?
各フォームのボタンクリックイベントの中に `OpenDatabase` を書き散らすな。それは「スパゲッティ・アーキテクチャ」への入り口だ。データベースの接続管理は、必ず単一のモジュール、あるいはクラスモジュールに集約せよ。接続オプション(`dbSeeChanges`)を一箇所で管理することで、将来的な仕様変更(例えば、別のDBエンジンへの移行)に即座に対応できる。
Q2. 接続のライフサイクルを制御できているか?
`Set db = Nothing` を忘れることは、サーバーのリソースを食いつぶす行為だ。特に大量のデータをループ処理で更新する場合、オブジェクトの解放が不適切だと、接続数上限に達してシステム全体が沈黙する。
Q3. 「なぜ失敗したか」を追跡できるか?
`dbSeeChanges` エラーは、単なるエラーコードではない。それは「君の設計が、データの整合性を軽視している」という警告だ。エラー発生時には、単に `MsgBox` を出すだけでなく、エラー番号、発生箇所、そして当時のパラメータをログに記録する仕組みを組み込め。
まとめ
`dbSeeChanges` は、単なる「おまじない」ではない。SQL Serverという強力なエンジンと、Accessという柔軟なフロントエンドを繋ぐ、整合性のための契約である。
この契約を正しく理解し、トランザクションとエラーハンドリングを組み合わせた設計を行うことで、君の作成するツールは「動くツール」から「信頼されるシステム」へと昇華する。
プロフェッショナルであれ。細部に魂を込めろ。
