はじめに:なぜ設定テーブルやレジストリでは極限の現場で破綻するのか
Accessアプリケーションの運用において、システムバージョン、接続先環境フラグ(開発/検証/本番)、最終同期日時などの「設定値」をどこに保持すべきか。素人が飛びつく解決策は次の3つです。
1. 設定保持用の専用テーブルを作る(ナセル構造の崩壊:ユーザーがテーブルを開いて誤改ざんするリスク)
2. Windowsレジストリ(`SaveSetting` / `GetSetting`)を使う(PC依存の発生:端末交換やシンクライアント環境で設定が揮発)
3. 外部INIファイルやJSONを置く(ファイル管理の煩雑化:移動やコピー時に相対パスが切れ、バグの温床となる)
プロダクションレベルのアーキテクチャにおいて、真の正解は「Accessファイル(.accdb)そのもののDAOメタデータ領域にカスタムプロパティとして埋め込む」ことです。
`DAO.Database.CreateProperty` を正しく隠蔽・抽象化して利用すれば、外部ファイル不要、ユーザーの誤操作不可、ファイル移動でも確実に設定が追従する極めて堅牢な単一ファイル構成(Self-Contained Architecture)が完成します。
本稿では、実務の修羅場を潜り抜けてきたプログラミング構造に基づき、エラー3270の罠の回避、`CurrentDb`のメモリライフサイクルの厳密な扱い、そしてそのままプロダクションに投入できる完全なカプセル化コードを伝授します。
—
1. DAOプロパティ操作における「3つの罠」と内部メカニズム
カスタムプロパティを扱う際、中級レベルのエンジニアが必ずハマる重大な罠が3つ存在します。これらを解明しないままコードを書くことは、潜伏バグを埋め込む行為と同義です。
罠①:`CurrentDb` のポインタ消失とスコープ破壊
VBAにおいて `CurrentDb.CreateProperty(…)` のように、直接参照でメソッドをチェーン実行しては絶対になりません。
‘ 【アンチパターン】絶対にやってはいけない記述
CurrentDb.CreateProperty(“AppVersion”, dbText, “1.0.0”)
CurrentDb.Properties.Append … ‘ ← ここでエラーまたは不揮発化が失敗する!
`CurrentDb` 関数は呼び出されるたびに内部で新しい `DAO.Database` オブジェクトのインスタンスを生成・初期化し、処理が終わると即座に破棄(C++層でDestructorが走る)します。
そのため、変数を経由せずに操作を行うと、生成されたプロパティオブジェクトが宙に浮き、`Append` する対象のデータベースインスタンスが既にメモリから消滅しているという惨事が発生します。
鉄則:必ず `Dim db As DAO.Database` 変数に保持し、処理完了までスコープを明示的に維持すること。
罠②:存在しないプロパティ参照時に発生する「エラー 3270」
DAOの `Properties` コレクションは、存在しないキーにアクセスした際、`Nothing` や `Null` を返すのではなく、容赦なく `Runtime Error 3270: プロパティが見つかりません。` を発生させます。
したがって、プロパティの「取得(Read)」および「更新(Write前の存在チェック)」の処理には、決定的で安全なエラーハンドリング機構が不可欠です。
罠③:データ型(`DataTypeEnum`)の強制と暗黙の型変換リスク
`CreateProperty` の第2引数には、DAOの `DataTypeEnum`(`dbText`, `dbLong`, `dbBoolean`, `dbDate` など)を厳密に渡す必要があります。これを指定せずにバリアント型でアバウトに渡すと、永続化時に型変換エラーが発生するか、読み出し時に予期せぬ型崩れを起こします。
—
2. 堅牢性を極めたプロダクションコード(完全モジュール)
以下のコードを標準モジュール(例: `mod_AppProperties`)にそのままコピー&ペーストして使用してください。
型安全、完全なオブジェクト破棄、エラーハンドリング、そして存在しない場合の自動生成(Upsert動作)を完璧に実装しています。
Option Explicit
Option Compare Database
‘ ================================================================================
‘ モジュール名 : mod_AppProperties
‘ 概要 : DAO.Database カスタムプロパティ安全操作モジュール
‘ 責務 : 外部依存なしに.accdb内部へ設定値を完全永続化・取得・更新する
‘ アーキテクチャ: Error 3270を内部で捕捉し、安全なUpsert(挿入/更新)を実現
‘ ================================================================================
‘ 自前でデータ型を定義し、DAOの型を抽象化(タイプセーフの確保)
Public Enum CustomPropertyType
PropText = DAO.DataTypeEnum.dbText
PropLong = DAO.DataTypeEnum.dbLong
PropBoolean = DAO.DataTypeEnum.dbBoolean
PropDate = DAO.DataTypeEnum.dbDate
End Enum
‘ ——————————————————————————–
‘ 関数名 : GetCustomProperty
‘ 概要 : カスタムプロパティの値を取得する
‘ 引数 : PropName – プロパティ名
‘ DefaultValue – 存在しなかった場合に返すデフォルト値 (初期値: Null)
‘ 戻り値 : 保存されていた値(存在しない場合は DefaultValue)
‘ ——————————————————————————–
Public Function GetCustomProperty( _
ByVal PropName As String, _
Optional ByVal DefaultValue As Variant = Null _
) As Variant
Dim db As DAO.Database
Dim prp As DAO.Property
Dim result As Variant
‘ 1. インスタンスのスコープを確立
Set db = CurrentDb()
‘ 2. エラー処理を局部的に遮断してプロパティ取得を試みる(エラー3270対策)
On Error Resume Next
Set prp = db.Properties(PropName)
On Error GoTo 0
‘ 3. 評価と戻り値の設定
If prp Is Nothing Then
result = DefaultValue
Else
result = prp.Value
End If
‘ 4. 明示的なクリーンアップ(メモリリークの完全遮断)
Set prp = Nothing
Set db = Nothing
GetCustomProperty = result
End Function
‘ ——————————————————————————–
‘ 関数名 : SetCustomProperty
‘ 概要 : カスタムプロパティを保存(書き込み/自動生成)する
‘ 引数 : PropName – プロパティ名
‘ Value – 保存する値
‘ ValueType- プロパティの型(CustomPropertyType列挙型で指定)
‘ 戻り値 : 成功時 True / 失敗時 False
‘ ——————————————————————————–
Public Function SetCustomProperty( _
ByVal PropName As String, _
ByVal Value As Variant, _
Optional ByVal ValueType As CustomPropertyType = PropText _
) As Boolean
Dim db As DAO.Database
Dim prp As DAO.Property
Dim isExist As Boolean
On Error GoTo ErrorHandler
‘ 1. インスタンスのスコープを確立
Set db = CurrentDb()
isExist = True
‘ 2. 存在チェック(エラー3270の局所化)
On Error Resume Next
Set prp = db.Properties(PropName)
If Err.Number = 3270 Then
‘ プロパティが存在しない
isExist = False
Err.Clear
ElseIf Err.Number <> 0 Then
‘ その他の予期せぬエラー
On Error GoTo ErrorHandler
Err.Raise Err.Number, Err.Source, Err.Description
End If
On Error GoTo ErrorHandler
‘ 3. 存在有無に応じた分岐処理(Upsertパターンの実現)
If Not isExist Then
‘ 存在しない場合は新規作成し、PropertiesコレクションへAppend
‘ ※注意: CreatePropertyを実行しただけでは保存されない。Appendが必須。
Set prp = db.CreateProperty(PropName, ValueType, Value)
db.Properties.Append prp
Else
‘ 既に存在する場合は値を更新
prp.Value = Value
End If
‘ 4. DAO内部キャッシュの強制書き換え
db.Properties.Refresh
SetCustomProperty = True
ExitHandler:
‘ 厳格なオブジェクト破棄処理
Set prp = Nothing
Set db = Nothing
Exit Function
ErrorHandler:
‘ 開発環境や運用ログへエラー内容を出力
Debug.Print “[ERROR] SetCustomProperty Failed: ” & Err.Number & ” – ” & Err.Description
SetCustomProperty = False
Resume ExitHandler
End Function
‘ ——————————————————————————–
‘ 関数名 : DeleteCustomProperty
‘ 概要 : 不要になったカスタムプロパティを削除する(メンテナンス用)
‘ ——————————————————————————–
Public Function DeleteCustomProperty(ByVal PropName As String) As Boolean
Dim db As DAO.Database
On Error GoTo ErrorHandler
Set db = CurrentDb()
‘ コレクションからの削除実行
db.Properties.Delete PropName
db.Properties.Refresh
DeleteCustomProperty = True
ExitHandler:
Set db = Nothing
Exit Function
ErrorHandler:
‘ 3270(存在しない)場合は問題なしとする
If Err.Number = 3270 Then
DeleteCustomProperty = True
Else
Debug.Print “[ERROR] DeleteCustomProperty Failed: ” & Err.Number & ” – ” & Err.Description
DeleteCustomProperty = False
End If
Resume ExitHandler
End Function
—
3. 実務での具体的ユースケースと実装例
このモジュールを導入することで、業務自動化ツールとしてのクオリティは飛躍的に高まります。代表的な2つの実用例を示します。
ユースケース1:アプリケーションのバージョンチェックと初回起動初期化
フロントエンド(.accdb)のバージョン情報を埋め込み、起動時に自動チェックする例です。
Public Sub CheckApplicationVersion()
Const CURRENT_SYSTEM_VER As String = “2.1.0”
Dim installedVer As String
‘ プロパティから現在のバージョンを取得(存在しない場合は “0.0.0”)
installedVer = GetCustomProperty(“AppVersion”, DefaultValue:=”0.0.0″)
If installedVer = “0.0.0” Then
MsgBox “初回起動を検知しました。初期設定を実行します。”, vbInformation
‘ 初回のみのテーブル構築や環境整備処理を実行…
‘ バージョン情報を永続化
Call SetCustomProperty(“AppVersion”, CURRENT_SYSTEM_VER, PropText)
Call SetCustomProperty(“InstallDate”, Now(), PropDate)
ElseIf installedVer <> CURRENT_SYSTEM_VER Then
MsgBox “システムがバージョンアップされました (” & installedVer & ” -> ” & CURRENT_SYSTEM_VER & “)”, vbInformation
‘ マイグレーション処理を実行…
‘ バージョン更新
Call SetCustomProperty(“AppVersion”, CURRENT_SYSTEM_VER, PropText)
End If
Debug.Print “起動完了: System Version ” & GetCustomProperty(“AppVersion”)
End Sub
ユースケース2:環境切り替えフラグ(DEV / STG / PROD)による接続先の動的制御
開発環境と本番環境で、バックエンドデータベース(SQL Serverや別.accdb)のリンクテーブル接続先を切り替える運用です。外部設定ファイルを使わず、ファイル自体のプロパティで状態を保持します。
Public Sub SwitchEnvironment(ByVal EnvName As String)
‘ EnvName: “DEV”, “STG”, “PROD”
If EnvName <> “DEV” And EnvName <> “STG” And EnvName <> “PROD” Then
Err.Raise vbObjectError + 512, , “無効な環境名が指定されました。”
End If
‘ 環境識別フラグを保存
If SetCustomProperty(“Environment”, EnvName, PropText) Then
MsgBox “動作環境を [” & EnvName & “] に切り替えました。再起動後に適用されます。”, vbInformation
End If
End Sub
Public Function GetCurrentConnectionString() As String
Dim env As String
env = GetCustomProperty(“Environment”, DefaultValue:=”DEV”)
Select Case env
Case “PROD”
GetCurrentConnectionString = “Server=ProdServer;Database=DB_PROD;Trusted_Connection=Yes;”
Case “STG”
GetCurrentConnectionString = “Server=StgServer;Database=DB_STG;Trusted_Connection=Yes;”
Case Else
GetCurrentConnectionString = “Server=DevServer;Database=DB_DEV;Trusted_Connection=Yes;”
End Select
End Function
—
4. チーフアーキテクトからの設計アドバイス
最後に、本手法を現場に導入するにあたって意識すべきアーキテクチャの境界線を提示します。
1. フロントエンド(FE)とバックエンド(BE)の責務分離
- FEに保存すべきもの: 画面レイアウトの記憶、UIの最終選択値、クライアント固有のアプリバージョン。
- BEに保存すべきもの: データベース構造のスキーマバージョン(Migration Revision)。
- ※BEのカスタムプロパティを操作する場合は、`Set db = DAO.OpenDatabase(“C:\path\to\be.accdb”)` でDBを直接開いて操作してください。
2. セキュリティに関する厳格な注意
カスタムプロパティは一般ユーザーの目から隠されていますが、暗号化ストレージではありません。VBAコードを閲覧・実行できるユーザーであれば、上記と同様のコードで容易に値を読み取ることができます。パスワードやAPIキーなどの機密情報を平文で保持することは絶対に避けてください。(保持する場合は対称鍵暗号等で不可逆・難読化して格納すること)。
3. コンパイル・配布プロセスとの統合
`.accde` ファイル(コンパイル済みファイル)を作成してユーザーに配布する場合、あらかじめ開発用の `.accdb` 側でプロパティを設定・クリアしてからビルドを実行するビルドパイプライン(VBAスクリプトによる自動化)を構築するのが、真のプロフェッショナルの仕事です。
—
まとめ
`DAO.Database.CreateProperty` を用いたプロパティ永続化術は、Access VBA開発における「外部依存の徹底排除」を実現する強力な武器です。
- `CurrentDb` の変数は必ずローカルに保持する。
- エラー3270(Property Not Found)を安全にハンドリングする。
- 読み書きは抽象化された専用モジュールを経由させる。
この3点を厳守し、美しく洗練された、絶対に壊れないVBAアプリケーションを構築してください。
