Access VBAの深淵:SQL Server連携で「dbSeeChanges」を忘れる者は、データ整合性の戦いに敗北する
Access VBAを武器に業務自動化を極めようとする者にとって、ローカルの`.accdb`から脱却し、バックエンドにSQL Server(Azure SQL Database含む)を据える構成は避けては通れない道だ。
しかし、多くの開発者がここで最初の壁にぶち当たる。
「実行時エラー ‘3622’: Identity列を持つSQL Serverテーブルにアクセスする場合は、OpenRecordsetでdbSeeChangesオプションを使用してください。」
このエラーは、単なる「書き忘れ」で済まされるものではない。その背後には、AccessのJET/ACEエンジンとSQL Serverという、設計思想の異なる二つの巨人が対話するための「儀式」が隠されている。
今回は、DAOとADOのアーキテクチャの違いを整理しつつ、この`dbSeeChanges`の正体と、プロフェッショナルが書くべき堅牢なデータ操作コードの極意を伝授する。
—
1. DAOとADO:なぜ我々は依然としてDAOを選択するのか
アーキテクチャの選定において、まずこの視点を持て。
- DAO (Data Access Objects): ACEエンジンと密結合しており、Accessの「フォーム」や「レポート」との親和性が極めて高い。リンクテーブル経由での操作は、Accessにとって「母国語」を話すようなものだ。
- ADO (ActiveX Data Objects): 汎用的なデータアクセスを提供する。SQL Serverとの親和性は高いが、Accessのバウンドフォームとの連携には余計なオーバーヘッドが生じる。
多くの自称「プロ」が「これからはADOだ」と吹聴した時期もあったが、Access VBAの世界において、リンクテーブルを介した高速なCRUD操作を追求するならば、DAOの最適化こそが正解である。しかし、DAOは元来ローカルデータベース用に設計されたため、SQL Server特有の「Identity列(自動採番)」を扱う際に、明示的なフラグを要求するのだ。
—
2. 「dbSeeChanges」の正体:なぜそれが必要なのか
SQL ServerのIdentity列は、レコードが挿入された瞬間にサーバー側で値が決定する。
一方、DAOがレコードセットを開く際、Accessは「自分が書き込んだレコードが、サーバー側でどう変化したか」を常に監視(See Changes)する必要がある。
もし`dbSeeChanges`を指定せずにIdentity列を持つテーブルを更新しようとすれば、Accessはデータ整合性を保証できないと判断し、即座に処理をストップさせる。これはエラーではなく、「データ破壊を防ぐための安全装置」なのだ。
—
3. プロダクション・グレードのデータ操作コード
では、実務でそのまま使える、堅牢なクラスモジュールレベルのコード例を見てみよう。単にオプションを付けるだけでなく、トランザクション管理とエラーハンドリングを完備した、保守性の高い設計を提示する。
‘—————————————————————————————
‘ Procedure : SafeUpdateSQLServer
‘ Author : Legendary Chief Architect
‘ Purpose : SQL Serverリンクテーブルに対する安全なレコード追加・更新
‘ Note : dbSeeChanges とトランザクション処理の完全な実装
‘—————————————————————————————
Public Sub SafeUpdateSQLServer()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim workspaceName As String
Dim isTransactionActive As Boolean
On Error GoTo ErrorHandler
‘ CurrentDbは毎回呼び出すのではなく、オブジェクト変数に格納するのが鉄則
Set db = CurrentDb
‘ トランザクションを開始(DAOのトランザクションはWorkspaceレベルで制御する)
DBEngine.Workspaces(0).BeginTrans
isTransactionActive = True
‘ SQL Serverのリンクテーブルを開く
‘ [重要] dbSeeChanges: Identity列があるテーブルには必須
‘ [重要] dbFailOnError: 更新失敗時に確実にランタイムエラーを発生させる
‘ [重要] dbOpenDynaset: リンクテーブル更新にはダイナセット形式が必要
Set rs = db.OpenRecordset(“SELECT FROM T_SalesOrders WHERE OrderID = 0”, _
dbOpenDynaset, dbSeeChanges + dbFailOnError)
‘ レコードの追加処理
With rs
.AddNew
!OrderDate = Now()
!CustomerID = 101
!TotalAmount = 5500
‘ Identity列(OrderID等)には値を代入しない。SQL Serverが自動生成する。
.Update
‘ 直前に追加したIdentity値を取得したい場合は、Bookmarkを利用する
.Bookmark = .LastModified
Debug.Print “生成されたIDは: ” & !OrderID
.Close
End With
‘ 全ての処理が成功すればコミット
DBEngine.Workspaces(0).CommitTrans
isTransactionActive = False
MsgBox “データの更新に成功しました。”, vbInformation
ExitRoutine:
‘ オブジェクトのクリーンアップ(ライフサイクル管理の徹底)
On Error Resume Next
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
Set db = Nothing
Exit Sub
ErrorHandler:
‘ エラー発生時は即座にロールバックし、不整合を防ぐ
If isTransactionActive Then
DBEngine.Workspaces(0).Rollback
End If
MsgBox “エラー発生 [” & Err.Number & “]: ” & Err.Description, vbCritical, “データ更新失敗”
Resume ExitRoutine
End Sub
—
4. 現場で陥る「罠」と回避策
① リンクテーブルの定義情報が古い
SQL Server側でテーブル定義(列の追加やIdentityの設定)を変更した際、Access側のリンクテーブルを更新(再リンク)しないと、`dbSeeChanges`を付けていてもエラーになる、あるいは挙動が不安定になる。
対策: `TableDef.RefreshLink` メソッドを定期的に、あるいはデプロイ時に実行する仕組みを組み込め。
② 大量データ更新時のパフォーマンス
DAOの`.AddNew`や`.Update`をループ内で数千回回すのは非効率だ。
対策: 大量データの場合は、`db.Execute` で直接SQLを投げるか、パススルー・クエリ(Pass-Through Query)を利用せよ。その際も、SQL Server側で制約エラーが起きた場合に備え、Access側のエラーハンドリングを怠るな。
③ Identity列がないテーブルでのdbSeeChanges
Identity列がないテーブルに対して`dbSeeChanges`を付けても実害はない。
戦略: 「SQL Serverへのリンクテーブル操作には、無条件で`dbSeeChanges`を付与する」というコーディング規約をチームに徹底させることで、不毛なデバッグ時間をゼロにできる。
—
結論:アーキテクトとしての矜持
`dbSeeChanges`を知っているだけの開発者は多い。しかし、それがなぜ必要なのかを理解し、トランザクションの整合性とオブジェクトのライフサイクルまで考慮してコードを書ける者は一握りだ。
Access VBAは、決して「古い技術」ではない。SQL Serverという強力なエンジンと組み合わせ、正しく制御すれば、現代の複雑なビジネスロジックを最速で具現化する強力な武器となる。
君が書く一行の`.OpenRecordset`に、データの命運がかかっていることを忘れるな。堅牢な設計こそが、ユーザーからの信頼を勝ち取る唯一の道である。
