Access VBAを掌握する極限の知見:`DoCmd.RunCommand acCmdSaveRecord` の本当の制御と罠
開発現場でよく見かける光景がある。フォーム上のテキストボックスに値を入力し、そのまま別のボタンを押した瞬間、意図しないエラーが発生したり、バリデーションがすり抜けてゴミデータがテーブルに書き込まれたりする現象だ。
「なぜ、ここで保存が走るのか?」
「なぜ、入力チェックのコードを書いたのに、すり抜けるのか?」
Access VBAにおける最大の罠は、「コントロールのフォーカス喪失」と「レコードの暗黙の保存(Commit)」のタイミングが、開発者の意図とは無関係に裏で非同期的に制御されている点にある。
今回は、この制御権を完全に掌握するための極限の知見として、`DoCmd.RunCommand acCmdSaveRecord` を用いた入力値の強制確定と、堅牢なバリデーション・タイミング制御のアーキテクチャを伝授する。
—
1. なぜ「暗黙の保存」に頼る設計は破綻するのか?
多くの初学者、あるいは場当たり的なコードを書くプログラマーは、フォームの更新ボタンや次へ進むボタンに次のようなコードを書く。
‘ 【アンチパターン】これではバリデーションのタイミングを制御できない
Private Sub 登録ボタン_Click()
‘ ここで本当に保存されているか?実はまだされていないことが多い
Call データの処理
End Sub
Accessのフォームは、ユーザーがテキストボックスからフォーカスを移動させた瞬間、あるいは別のレコードに移動しようとした瞬間に、自動的にバッファの内容をテーブル(またはクエリ)へ書き込もうとする(暗黙のコミット)。
この「勝手に保存される仕様」のせいで、以下の問題が発生する。
1. バリデーションのすり抜け: ユーザーが入力中に別のコントロールへ移動した際、意図しないタイミングで `BeforeUpdate` イベントが走り、検証ロジックが破綻する。
2. トランザクション制御の欠如: 複数項目の整合性チェックを行いたいのに、途中の項目で勝手に保存が走ってしまう。
この暴走するAccessのライフサイクルを開発者の手元に取り戻す唯一の手段が、「意図的なレコード保存の強制」である。
—
2. `DoCmd.RunCommand acCmdSaveRecord` の本質とメカニズム
ここで登場するのが、伝説のコマンド `DoCmd.RunCommand acCmdSaveRecord` だ。
このメソッドを実行すると、Accessエンジンに対し「今すぐ現在のフォームが保持している未保存のバッファを、レコードソースに書き込め」という明確な指令(Explicit Commit)を発行できる。
ライフサイクルにおける位置づけ
このコマンドを実行した瞬間、以下のイベントチェーンが同期的に強制発生する。
1. フォームの `BeforeUpdate` イベント(ここで入力値の整合性・必須チェックを行う)
2. データの書き込み(コミット)
3. フォームの `AfterUpdate` イベント
つまり、ボタンクリックの最前端にこのコードを置くことで、「今、画面に入力されている内容を一度強制的に検証・保存させ、エラーがあればそこで処理を完全にストップさせる」という鉄壁のガードが完成する。
—
3. 【プロダクションコード】バグを根絶する堅牢なイベント制御
実際の業務システムでそのまま使える、堅牢なモジュール設計のサンプルコードを提示する。
このコードでは、以下の要件を満たしている。
- 登録ボタンが押された瞬間、他のコントロールのフォーカス状態に関わらず、確実に未保存データを確定させる。
- `BeforeUpdate` に頼り切るのではなく、ボタン側のロジックで明示的にエラーハンドリングを行う。
‘ ==============================================================================
‘ モジュール名: フォーム「F_受注入力」
‘ 概要: レコードの強制保存とトランザクション的バリデーション制御
‘ ==============================================================================
Private Sub btnSave_Click()
On Error GoTo ErrorHandler
‘ 【極限の知見】
‘ フォーカスのあるコントロールの「ロスフォーカス時イベント」を先に発火させ、
‘ その後、フォーム全体のレコード保存(BeforeUpdate)を強制的に呼び出す。
If Me.Dirty Then
DoCmd.RunCommand acCmdSaveRecord
End If
‘ ここに到達した時点で、データのバリデーションとテーブルへの一時書き込みは完了している。
‘ 追加のビジネスロジック(後続処理)を安全に実行する。
MsgBox “データは正常に検証・保存されました。”, vbInformation, “システム通知”
Exit Sub
ErrorHandler:
‘ ユーザーがバリデーションエラーなどで保存をキャンセルした場合、
‘ RunCommandは実行時エラー(エラー番号: 2046 または 2501 等)を吐く。
‘ これをトラップすることで、エラーメッセージの重複を防ぎ、処理を安全に中断する。
If Err.Number = 2046 Or Err.Number = 2501 Then
‘ Access標準のエラーメッセージが既に表示されているか、
‘ あるいはBeforeUpdate側でキャンセルされているため、ここではサイレント処理または追加ログ
Exit Sub
Else
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
Err.Description, vbCritical, “致命的エラー”
End If
End Sub
‘ ——————————————————————————
‘ フォームの更新前処理(ここで真のバリデーションを行う)
‘ ——————————————————————————
Private Sub Form_BeforeUpdate(Cancel As Integer)
‘ 1. 必須入力チェック(例:顧客IDが空でないか)
If IsNull(Me.txtCustomerID) Or Me.txtCustomerID = “” Then
MsgBox “顧客IDは必須入力です。”, vbExclamation, “入力漏れ”
Me.txtCustomerID.SetFocus
Cancel = True ‘ 【重要】保存処理を確実にキャンセルする
Exit Sub
End If
‘ 2. 業務ロジック整合性チェック(例:数量が0以下ではないか)
If Me.txtQuantity <= 0 Then
MsgBox "数量には1以上の値を入力してください。", vbExclamation, "入力値不正"
Me.txtQuantity.SetFocus
Cancel = True
Exit Sub
End If
End Sub
---
4. 実務で絶対にハマる「落とし穴」と回避策
シニアエンジニアとして、現場でありがちなトラブルと、その予防策を共有しておく。
① `RunCommand` の連続実行による無限ループ
`BeforeUpdate` イベントの中でさらに `DoCmd.RunCommand acCmdSaveRecord` を呼び出してはならない。イベントが再帰的に呼び出され、スタックオーバーフローや予期せぬ多重保存を引き起こす。保存の強制は、あくまで「ユーザーのアクション(ボタンクリック等)」の起点で行うこと。
② 非バインドフォームとの混同
この手法は、レコードソースに直接結びついた「バインドフォーム」でのみ機能する。完全な非バインドフォーム(DAOやADODBを使って手動でSQLを発行するフォーム)でこのコマンドを実行しても、エラーになるか無意味な動作をするので設計段階で混同しないこと。
—
総括
Access VBAにおけるデータ制御の基本は、「Accessの勝手な機嫌に処理を委ねないこと」である。
`DoCmd.RunCommand acCmdSaveRecord` は、単なる「保存のショートカット」ではない。フォームのライフサイクルを開発者の意図通りにねじ伏せ、堅牢なトランザクションとバリデーションのタイミングを構築するための強力なコントロール武器である。
この挙動を完全に理解しコードに落とし込めた時、あなたの作るAccessアプリから「原因不明のデータ不整合バグ」は完全に姿を消すことになるだろう。
