【実務・中級編】DoCmd.RunCommand acCmdSaveRecordの正しい使い所:フォーム更新の確定とバリデーションのタイミング – Access VBA解析バイブル

スポンサーリンク

Access VBAの「呪い」を解く:DoCmd.RunCommand acCmdSaveRecordの正しい流儀

Accessで業務システムを構築する際、多くのエンジニアが「データの保存」で躓く。
「なぜかデータが保存されない」「バリデーションがすり抜ける」「意図しないレコードが作成される」——これらはすべて、Accessの「バッファ管理のライフサイクル」を理解していないことから生じる初歩的なミスだ。

今回は、`DoCmd.RunCommand acCmdSaveRecord` を単なる「保存ボタン」としてではなく、堅牢な業務アプリを支える「確定のトリガー」として機能させるための極限の知見を授ける。

1. なぜ「力技の保存」が必要なのか?

Accessのフォームは、フォーカスが外れたタイミングや、別のレコードへ移動した瞬間に自動でバッファをフラッシュ(保存)する。しかし、業務アプリにおいては「ボタンを押した瞬間にバリデーションを行い、強制的に確定させる」という制御が必須だ。

ここで安易に `DoCmd.RunCommand acCmdSaveRecord` を使うと、「バリデーションが動く前に保存が完了してしまう」という致命的な構造的欠陥を生むことがある。

鉄則:保存は「イベントの連鎖」の中で制御せよ

保存ボタンに書くべきは「保存の命令」ではない。「保存を試行させるためのトリガー」である。

2. 堅牢なバリデーションの設計図

フォームの入力値をチェックする場所は、ボタンクリックイベント内ではない。`Form_BeforeUpdate` イベントこそが、データの最終防衛ラインだ。

ボタンの役割は「レコードの保存を強制的に開始させること」であり、チェックの結果「保存を拒否する」のは `BeforeUpdate` の仕事である。この役割分担ができていないコードは、仕様変更のたびに崩壊する。

実践的なプロダクションコード

以下のコードをフォームモジュールに実装せよ。

‘ フォームの保存ボタンクリックイベント
Private Sub btnSave_Click()
‘ 1. エラーハンドリングは必須。トランザクションの失敗を検知する
On Error GoTo Err_Handler

‘ 2. フォームがダーティ(未保存の変更がある)なら保存を試行
If Me.Dirty Then
‘ acCmdSaveRecord は BeforeUpdate イベントを誘発する
DoCmd.RunCommand acCmdSaveRecord
End If

MsgBox “保存が完了しました。”, vbInformation

Exit_Handler:
Exit Sub
Err_Handler:
‘ 3. BeforeUpdateでCancel = True が発生した場合、エラーが発生するが
‘ それは「想定内」であるため、ここで握りつぶすか、意図的に制御する
If Err.Number = 2001 Then
‘ 保存が拒否された場合(BeforeUpdateでのエラー)
Else
MsgBox “予期せぬエラーが発生しました: ” & Err.Description
End If
Resume Exit_Handler
End Sub

‘ フォームの最終防衛ライン
Private Sub Form_BeforeUpdate(Cancel As Integer)
‘ ここで全てのバリデーションを行う
‘ 1つでも条件を満たさなければ Cancel = True で保存を強制拒否する

If IsNull(Me.txtEntryDate) Then
MsgBox “日付を入力してください。”, vbCritical
Cancel = True ‘ 保存のキャンセル
Me.txtEntryDate.SetFocus
Exit Sub
End If

‘ 追加の複雑なチェックもここで行う
If Not ValidateBusinessLogic() Then
Cancel = True
End If
End Sub

3. なぜこの設計が最強なのか?

1. 疎結合の実現: 保存ボタンは「保存してくれ」と願うだけ。チェックロジックはフォームが自分自身で守る。この分離により、例えば「閉じるボタン」による終了時でも同じバリデーションが機能する。
2. バッファ管理の整合性: `Me.Dirty` をチェックすることで、無駄な保存処理(テーブルへの無意味なI/O)を防ぐ。これは小規模なAccessアプリであっても、マルチユーザー環境でのロック競合を減らすための重要な配慮だ。
3. エラーハンドリングの明確化: `acCmdSaveRecord` が `BeforeUpdate` でのキャンセルをトリガーにエラーを返す仕様を逆手に取り、明確に制御下に置いている。

4. チーフアーキテクトからのアドバイス

実務において、`acCmdSaveRecord` を使うときは以下の点も忘れてはならない。

  • Null値の罠: フォームの連結コントロールにNullを許容しない設定(Required)がテーブル側にある場合、VBAでのバリデーションをすり抜けてAccess標準の警告ダイアログが出る。これはユーザー体験を損なうため、必ずVBA側で `BeforeUpdate` を使って先に弾くこと。
  • トランザクション: 複雑な複数テーブル更新を行う場合は、`DoCmd` ではなく `CurrentDb.Execute` と `DAO.Database.BeginTrans` を使用するフェーズに移行すべきだ。

Accessは「手軽に作れる」がゆえに、「適当に作られる」運命にある。しかし、あなたが書くコードは違うはずだ。「どのタイミングでデータが確定し、どこでバリデーションが走るのか」。このライフサイクルを掌握した者だけが、保守性の高い、真に「動く」業務システムを構築できる。

さあ、コードを書き換えよう。あなたのアプリケーションは、もっと堅牢になれるはずだ。

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