Access VBAを掌握する極限の知見:DoCmd.RunCommandによる「レコードの保存」強制とバリデーション制御の極意
開発現場でAccess VBAを取り扱う際、最も多くのエンジニアを泥沼に引きずり込む魔物が何か知っているか?
それは「データの保存タイミングとバリデーションの非同期」だ。
「フォームに入力した値をチェックして保存したいのに、意図した順序でイベントが走らない」
「`CurrentDb.Execute` で直接SQLを叩こうとしたら、画面上の入力値がまだテーブルに反映されておらず、データが消失した」
「『保存しますか?』というAccess標準の鬱陶しいダイアログが勝手に割り込んでくる」
こうしたトラブルに直面し、泥縄式の `Me.Dirty = False` や `DoEvents` の乱用でコードを汚染していないだろうか。
今回は、Accessオブジェクトモデルのライフサイクルを完全に掌握し、`DoCmd.RunCommand` を用いてレコードの保存を意図通りに強制・制御するための極限の知見を授けよう。
—
1. なぜ「保存の競合」と「バリデーション漏れ」が起きるのか?
Accessのバウンドフォーム(テーブルやクエリと直結したフォーム)は、非常に優れたUIフレームワークである反面、「いつデータを確定(コミット)するか」の主導権をAccessエンジンが握っているという致命的な特徴がある。
ユーザーがテキストボックスからフォーカスを外した瞬間、あるいは別のレコードに移動した瞬間、Accessは勝手に以下の一連のイベントを発火させる。
1. `BeforeUpdate` (フォームの更新前処理)
2. データバッファのディスク/メモリへの書き込み
3. `AfterUpdate` (フォームの更新後処理)
ここで問題になるのが、「開発者が任意のタイミングで、この一連の保存プロセスを同期的に実行させたい」というケースだ。例えば、独自のエラーチェックロジックを走らせた後に確実にデータを保存し、その直後にトランザクションを絡めた別処理を行いたい場合などである。
ここで `Me.Dirty = False` を使うアマチュアが多いが、これではAccess内部の非同期なメッセージループに処理が委ねられてしまい、複雑なフォーム間連携においてタイミングズレ(競合)を引き起こす原因となる。
—
2. 決定打:`DoCmd.RunCommand acCmdSaveRecord` の優位性
同期的に、かつ確実に現在のレコードをデータベースエンジンへコミットするための唯一にして最強の手段がこれだ。
DoCmd.RunCommand acCmdSaveRecord
このコマンドは、UIスレッドに対して「現在アクティブなレコードのバッファ内容をただちにテーブルに書き込め」というハードな同期命令を下す。これにより、以下のメリットが生まれる。
- イベントの強制同期: 実行した瞬間に `BeforeUpdate` が走り、バリデーションエラーがあれば即座にキャッチできる。
- カレントレコードの確定: この命令の直後であれば、`Me.ID` などのオートナンバーや、入力されたばかりの値を確実につかむことができる。
しかし、ただ闇雲に叩くだけでは不十分だ。プロダクション環境に耐えうる「堅牢な設計」を構築するためのコードパターンを公開しよう。
—
3. 【実務コード】バグを駆逐する堅牢な保存&バリデーション制御
以下のコードは、フォーム上の「保存ボタン」が押された際のイベントプロシージャの模範解答だ。エラーハンドリング、トランザクションの概念、そしてAccess特有のトラップを完全に回避する構造に仕立てている。
Option Compare Database
Option Explicit
Private Sub btnSave_Click()
On Error GoTo ErrorHandler
‘ 1. ユーザー定義の独自バリデーション(テーブルに書き込む前の事前チェック)
If Not ValidateCustomBusinessRules() Then
Exit Sub
End Sub
‘ 2. コントロールのフォーカスを外し、入力中の値を確実にバッファへ確定させる
‘ ※アクティブコントロールが入力中の場合、RunCommand前にフォーカスを退避させることが定石
Me.CmdDummy.SetFocus
‘ 3. DoCmd.RunCommandによる「レコードの保存」の強制
‘ ここでフォームの BeforeUpdate イベントが同期的に発火する
If Me.Dirty Then
DoCmd.RunCommand acCmdSaveRecord
End If
‘ 4. 保存成功後の後続処理(メッセージ表示やログ出力など)
MsgBox “データを正常に保存しました。”, vbInformation, “システム通知”
Exit Sub
ErrorHandler:
‘ Accessの標準エラーメッセージを抑制し、開発者が意図したハンドリングを行う
Select Case Err.Number
Case 2046
‘ 「現在このコマンドは実行できません」エラー(Dirtyではない場合等)
Resume Next
Case 3022
‘ インデックスの重複エラーなど
MsgBox “一意性制約に違反するデータが含まれています。” & vbCrLf & _
“詳細: ” & Err.Description, vbCritical, “保存エラー”
Case Else
‘ その他の予期せぬエラー
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Number & ” – ” & Err.Description, vbCritical, “致命的エラー”
End Select
End Sub
‘ 独自ビジネスロジックのバリデーション関数
Private Function ValidateCustomBusinessRules() As Boolean
ValidateCustomBusinessRules = False
‘ 必須入力チェックの例
If IsNull(Me.txtCustomerName) Or Trim(Me.txtCustomerName) = “” Then
MsgBox “顧客名は必須入力です。”, vbExclamation, “入力チェック”
Me.txtCustomerName.SetFocus
Exit Function
End Function
‘ 整合性チェックの例
If Me.txtEndDate < Me.txtStartDate Then
MsgBox "終了日は開始日以降の日付を指定してください。", vbExclamation, "整合性チェック"
Me.txtEndDate.SetFocus
Exit Function
End Function
ValidateCustomBusinessRules = True
End Function
---
4. プロフェッショナルが教える開発の急所と注意点
① フォームの `BeforeUpdate` イベントとの共生
`DoCmd.RunCommand acCmdSaveRecord` を実行すると、必ずフォームの `Form_BeforeUpdate(Cancel As Integer)` が呼び出される。
もしフォーム側の `BeforeUpdate` で入力不備を検知して `Cancel = True` を設定した場合、`RunCommand` 自体が実行時エラー(実行時エラー 2046 またはトラップ可能なエラー)として跳ね返ってくる。
そのため、エラーハンドラー(`On Error GoTo`)を必ず実装し、キャンセルによるエラーを適切にハンドリングするか、あるいは上記サンプルコードのように、`RunCommand` の前に独自のバリデーション関数(`ValidateCustomBusinessRules`)を挟んであらかじめ弾く設計にするのが、最もスマートでバグを生みにくい。
② ダミーコントロール(`CmdDummy`)の重要性
テキストボックスに文字を入力したままの状態で `RunCommand` を叩くと、コントロールの更新前イベントとフォームの更新前イベントが複雑に絡み合い、意図しない挙動を示すことがある。
実務で堅牢なフォームを作る際は、画面の隅に非表示、あるいはフォーカスを受け取るだけの透明なボタン(例:`CmdDummy`)を配置し、保存処理の直前にそこにフォーカスを移動させることで、「入力中のコントロールからフォーカスを外す(=コントロールの更新を確実に完了させる)」という物理的なステップを踏むのが、ベテランエンジニアの常套手段である。
—
総括
Access VBAにおいて、データを制する者はアプリケーションを制する。
`DoCmd.RunCommand acCmdSaveRecord` は、単なる保存のショートカットではない。Accessの非同期なオブジェクトライフサイクルを開発者の意図通りに調教し、「同期的なトランザクション制御」をもたらすための強力な武器である。
甘い設計や場当たり的なコードの記述を捨て去り、このアーキテクチャを実装に組み込むことで、あなたの作るAccess業務システムは、ミッチリと堅牢で、ユーザーの信頼に足るプロフェッショナルなツールへと昇華するはずだ。
