AccessVBAでフロントエンド/バックエンド分割:プログラム更新でデータを消さない起動時リンクチェック

AccessVBAでフロントエンドとバックエンドを分割し起動時にリンクを確認する処理のイメージ AccessVBA

AccessVBAでプログラムを更新すると、データまで消えてしまう。

Accessで作ったシステムを納品したり、社内の何人かに配ったりしていると、いずれ次の問題にぶつかります。

機能を直した新しいaccdbを渡したら、今まで入力したデータが消えてしまった

Accessのファイル(accdb)1つの中には、フォームやVBAなどのプログラムと、テーブルのデータが一緒に入っています。そのため新しいファイルに差し替えると、プログラムと一緒にデータも入れ替わってしまいます。

シチュエーション

納品後の改修
お客様が毎日データを入力している
不具合修正や機能追加のたびに、新しいファイルを送る必要がある

社内の複数人で利用
共有フォルダのデータを、何人かで同時に参照・更新したい
1つのaccdbを全員で開くと、動作が重くなったり壊れやすくなったりする

定期的な機能追加
テーブルに列を足したり、マスタを増やしたりすることがある
そのたびに、データを手作業で新しいファイルへ移すのは危険

この記事では、Accessをプログラム(フロントエンド)データ(バックエンド)に分けます。そのうえで、起動時にリンクを確認し、切れていたらデータファイルを選び直してもらう共通部品を紹介します。

フロントエンドとバックエンドに分ける考え方

まず、何をどちらのファイルに置くかを決めます。

ファイル置くもの
バックエンド(データ)残したいデータのテーブルだけT_社員、T_部署、M_資格 など
フロントエンド(プログラム)フォーム・レポート・クエリ・VBA、作業用テーブルF_メニュー、R_社員一覧、W_集計ワーク など

フロントエンドからは、バックエンドのテーブルをリンクテーブルとして参照します。見た目は普通のテーブルと同じなので、フォームやクエリ、VBAのSQLはそのまま動きます。

作業用テーブルはフロントエンドに置く
帳票を作るために一時的にデータを入れるワークテーブルは、フロントエンド側に置きます。フロントエンドは利用者ごとにコピーして使うので、何人かで同時に使っても作業データがぶつかりません。

分割自体は、Accessの[データベースツール]→[Accessデータベース](データベース分割ツール)でも行えます。ただし分割ツールで作ったリンクは、分割したPCでのバックエンドのフルパスを覚えています。そのフロントエンドを別のPCに配ると、パスが違うためにリンクが切れて「ファイルが見つかりません」というエラーになります。

そこで、起動時に次の処理を行う仕組みを作ります。

  1. 起動時に、リンク先のデータファイルが存在して、実際に読めるかを確認する
  2. 読めなければ、データファイルを選ぶ画面を出す
  3. 選ばれたファイルが、このシステムのデータファイルかを確認する
  4. テーブルや列の追加が必要なら、データファイルに反映する
  5. 全テーブルのリンクを張り替えてメニューを開く

インストールと更新が同じ操作になる
開発側で作った新しいフロントエンドは、開発PCのパスを指したまま届きます。そのため、お客様のPCで開くと必ずリンク切れと判定され、データファイルを選ぶ画面が出ます。初めて使うときも、プログラムを更新したときも「データファイルを選ぶだけ」になり、手順書もシンプルになります。

リンク状態を確認するプロシージャサンプル

標準モジュール(例:m_LINK)を作り、以下のコードを貼り付けます。まずは、リンク対象のテーブルと、現在のリンク先・リンクの状態を調べる部分です。

Option Compare Database
Option Explicit

'--- バックエンドに置くテーブル(カンマ区切り)---
Private Const c_LINK_TABLES As String = "T_社員,T_部署"
'--- データファイルかどうかの判定に使うテーブル ---
Private Const c_CHECK_TABLES As String = "T_社員,T_部署"

'=========================================================
'  プロシージャ名:gf_GET_BE_PATH
'  機能  :現在リンクしているデータファイルのフルパスを返します。
'
'  【戻り値】
'    データファイルのフルパス(リンクが無い場合は長さ0)
'=========================================================
Public Function gf_GET_BE_PATH() As String
    On Error Resume Next
    Dim l_db As DAO.Database      ' Database
    Dim l_td As DAO.TableDef      ' テーブル定義
    Dim l_pos As Long             ' "DATABASE=" の位置
    gf_GET_BE_PATH = ""
    Set l_db = CurrentDb          ' 変数に入れておく(下の注意参照)
    Set l_td = l_db.TableDefs(Split(c_LINK_TABLES, ",")(0))
    If l_td Is Nothing Then Exit Function
    '=== Connect は ";DATABASE=C:\data\社員管理_データ.accdb" の形 ===
    l_pos = InStr(1, l_td.Connect, "DATABASE=", vbTextCompare)
    If l_pos > 0 Then gf_GET_BE_PATH = Mid(l_td.Connect, l_pos + 9)
End Function

'=========================================================
'  プロシージャ名:gf_CHECK_LINK
'  機能  :データファイルへのリンクが有効かを判定します。
'
'  【戻り値】
'    True  … リンク先が存在し、実際に読み取りできる
'    False … 未リンク・リンク切れ・読み取り不可
'
'  【処理概要】
'    ・リンク対象のテーブルがすべてリンクテーブルか確認
'    ・リンク先のファイルが存在するか確認
'    ・実際に1件読み取れるか確認(壊れたファイル等の検出)
'=========================================================
Public Function gf_CHECK_LINK() As Boolean
    On Error GoTo err_CHECK_LINK
    Dim l_db As DAO.Database      ' Database
    Dim l_td As DAO.TableDef      ' テーブル定義
    Dim l_rs As DAO.Recordset     ' 読み取り確認用
    Dim l_name As Variant         ' テーブル名
    Dim l_path As String          ' リンク先パス
    gf_CHECK_LINK = False
    Set l_db = CurrentDb
    '=== すべてリンクテーブルになっているか ===
    For Each l_name In Split(c_LINK_TABLES, ",")
        Set l_td = l_db.TableDefs(l_name)          ' 無ければエラー → False
        If Len(l_td.Connect) = 0 Then Exit Function
    Next l_name
    '=== リンク先ファイルが存在するか ===
    l_path = gf_GET_BE_PATH()
    If Len(l_path) = 0 Then Exit Function
    If Len(Dir(l_path)) = 0 Then Exit Function
    '=== 実際に読み取れるか ===
    For Each l_name In Split(c_LINK_TABLES, ",")
        Set l_rs = l_db.OpenRecordset("SELECT TOP 1 * FROM [" & l_name & "]", dbOpenSnapshot)
        l_rs.Close
    Next l_name
    gf_CHECK_LINK = True
    Exit Function
err_CHECK_LINK:
    gf_CHECK_LINK = False
End Function

gf_CHECK_LINK は、エラーが起きたら何も表示せずに False を返します。リンク切れは起動時に「よくあること」として扱い、エラーメッセージではなく、次のファイル選択の画面につなげるためです。

CurrentDb は変数に入れてから使う
Set l_td = CurrentDb.TableDefs("T_社員") のように書くと、次の行で l_td.Connect を参照したときに「オブジェクトが正しくないか、現在設定されていません。」(エラー3420)になります。CurrentDb は呼ぶたびに新しいDatabaseオブジェクトを返し、その行が終わると破棄されるため、そこから取り出したTableDefも使えなくなるからです。On Error Resume Next の中だとエラーにならず、リンクしているのにパスが空で返るという分かりにくい動きになります。Set l_db = CurrentDb で変数に入れてから使います。

ファイルの存在だけでなく「読めるか」まで見る理由
ファイルがあっても、壊れている、別のファイルに置き換わっている、ネットワークが切れている、といった場合は開けません。Dir での存在確認に加えて、実際にレコードセットを開いて確認しています。

データファイルを確認してリンクを張り替えるプロシージャ

次に、選ばれたファイルがこのシステムのデータファイルかを確認し、リンクを張り替える部分です。同じモジュールに続けて貼り付けます。

'=========================================================
'  プロシージャ名:gf_SELECT_BE_FILE
'  機能  :データファイル(accdb)を選択するダイアログを表示します。
'
'  【戻り値】
'    選択されたファイルのフルパス(キャンセル時は長さ0)
'=========================================================
Public Function gf_SELECT_BE_FILE() As String
    Dim l_fd As Object            ' FileDialog
    gf_SELECT_BE_FILE = ""
    Set l_fd = Application.FileDialog(3)    ' 3 … msoFileDialogFilePicker
    With l_fd
        .Title = "データファイルを選択してください"
        .AllowMultiSelect = False
        .Filters.Clear
        .Filters.Add "Accessデータベース", "*.accdb"
        .InitialFileName = CurrentProject.Path & "\"
        If .Show = True Then gf_SELECT_BE_FILE = .SelectedItems(1)
    End With
End Function

'=========================================================
'  プロシージャ名:gf_VALIDATE_BE
'  機能  :指定ファイルがこのシステムのデータファイルかを確認します。
'
'  【引数】
'    strPath :確認するaccdbのフルパス
'
'  【戻り値】
'    長さ0     … 正常
'    それ以外  … エラー内容(画面表示用)
'=========================================================
Public Function gf_VALIDATE_BE(strPath As String) As String
    On Error GoTo err_VALIDATE_BE
    Dim l_be As DAO.Database      ' データファイル
    Dim l_name As Variant         ' テーブル名
    Dim l_td As DAO.TableDef      ' テーブル定義
    Dim l_found As Boolean        ' 実体のテーブルが見つかったか
    Dim l_miss As String          ' 見つからなかったテーブル
    If Len(Dir(strPath)) = 0 Then
        gf_VALIDATE_BE = "指定されたファイルが見つかりません。"
        Exit Function
    End If
    '=== 読み取り専用で開いて、必要なテーブルが「実体で」あるか確認 ===
    Set l_be = DBEngine.OpenDatabase(strPath, False, True)
    For Each l_name In Split(c_CHECK_TABLES, ",")
        l_found = False
        For Each l_td In l_be.TableDefs
            '--- リンクテーブル(別のフロントエンド等)は数えない ---
            If l_td.Name = l_name And Len(l_td.Connect) = 0 Then l_found = True: Exit For
        Next l_td
        If Not l_found Then l_miss = l_miss & " ・" & l_name & vbCrLf
    Next l_name
    l_be.Close
    If Len(l_miss) > 0 Then
        gf_VALIDATE_BE = "選択されたファイルは、このシステムのデータファイルではないようです。" & vbCrLf & _
                         "次のテーブルが見つかりません。" & vbCrLf & l_miss
    End If
    Exit Function
err_VALIDATE_BE:
    gf_VALIDATE_BE = "ファイルを開けませんでした。" & vbCrLf & Err.Description
End Function

'=========================================================
'  プロシージャ名:gf_LINK_TABLES
'  機能  :指定したデータファイルへ、全テーブルのリンクを
'            作り直します。
'
'  【引数】
'    strPath :データファイルのフルパス
'
'  【戻り値】
'    True  … 正常終了
'    False … 中止またはエラー
'
'  【処理概要】
'    ・同名のローカルテーブル(リンクでない実体)があれば中止
'      (リンク作成のために削除すると、データが消えるため)
'    ・既存のリンクを削除し、新しいパスでリンクを作成
'=========================================================
Public Function gf_LINK_TABLES(strPath As String) As Boolean
    On Error GoTo err_LINK_TABLES
    Dim l_db As DAO.Database      ' Database
    Dim l_td As DAO.TableDef      ' テーブル定義
    Dim l_name As Variant         ' テーブル名
    Dim l_local As String         ' ローカル実体のテーブル
    Set l_db = CurrentDb
    '=== ローカルの実体テーブルが無いか確認 ===
    For Each l_td In l_db.TableDefs
        For Each l_name In Split(c_LINK_TABLES, ",")
            If l_td.Name = l_name And Len(l_td.Connect) = 0 Then
                l_local = l_local & " ・" & l_name & vbCrLf
            End If
        Next l_name
    Next l_td
    If Len(l_local) > 0 Then
        MsgBox "次のテーブルがこのファイル内に実体として存在するため、リンクを中止しました。" & vbCrLf & _
               l_local, vbExclamation
        Exit Function
    End If
    '=== リンクを作り直す ===
    For Each l_name In Split(c_LINK_TABLES, ",")
        On Error Resume Next
        l_db.TableDefs.Delete l_name            ' 無ければ何もしない
        On Error GoTo err_LINK_TABLES
        Set l_td = l_db.CreateTableDef(l_name)
        l_td.Connect = ";DATABASE=" & strPath
        l_td.SourceTableName = l_name
        l_db.TableDefs.Append l_td
    Next l_name
    l_db.TableDefs.Refresh
    Application.RefreshDatabaseWindow           ' ナビゲーションウィンドウを更新
    gf_LINK_TABLES = True
    Exit Function
err_LINK_TABLES:
    MsgBox "【gf_LINK_TABLES】リンク中にエラー発生" & vbCrLf & _
           "番号:" & Err.Number & vbCrLf & _
           "内容:" & Err.Description, vbCritical
    gf_LINK_TABLES = False
End Function

Connectを書き換えるだけにしない理由
リンクの張り替えは、TableDef.Connect を書き換えて RefreshLink する方法が一般的です。ただしこの方法は、まだリンクが無いテーブル(後から追加したテーブル)には使えません。今回はリンクをいったん削除して作り直すので、テーブルを追加したときも同じ関数で対応できます。

ローカルテーブルの確認は必須
TableDefs.Delete は、リンクテーブルなら「リンクだけ」を消します。ところが、同じ名前の実体のテーブルだった場合は、データごと消えてしまいます。分割前のファイルで誤って実行する事故を防ぐため、先に確認して中止しています。

データファイルかどうかの確認も必須
ファイル選択で、うっかり別のaccdb(別のシステムのファイルや、古いバックアップなど)を選ぶことがあります。必要なテーブルが揃っているかを確認してからリンクします。

リンクテーブルは「テーブルがある」と数えない
TableDefs にはリンクテーブルも含まれます。テーブル名だけで判定すると、同じフォルダに残っていた古いフロントエンド(社員管理_旧.accdb など)を選んだときにも確認を通ってしまいます。すると「リンク先がさらにリンク」という状態になり、古いフロントエンド側のリンクが切れた時点で、起動のたびにファイル選択に戻されます。Len(l_td.Connect) = 0(実体のテーブル)のときだけ見つかったと判定しています。

起動時にリンクを確認する

ここまでの部品を、起動時に呼び出す関数にまとめます。

'=========================================================
'  プロシージャ名:gf_STARTUP
'  機能  :起動時にリンクを確認し、切れていれば
'            データファイルを選択してリンクし直します。
'            AutoExecマクロから呼び出します。
'
'  【処理概要】
'    ・リンク切れならファイル選択 → 確認 → 更新 → リンクを繰り返す
'    ・ファイル選択をキャンセルしたらAccessを終了する
'    ・最後にデータファイルの更新要否を確認してメニューを開く
'=========================================================
Public Function gf_STARTUP()
    Dim l_path As String          ' 選択されたファイル
    Dim l_msg As String           ' 確認結果
    Do While Not gf_CHECK_LINK()
        MsgBox "データファイルに接続できません。" & vbCrLf & _
               "データファイルを選択してください。", vbExclamation
        l_path = gf_SELECT_BE_FILE()
        If Len(l_path) = 0 Then
            '=== キャンセル時は終了 ===
            DoCmd.Quit acQuitSaveNone
            Exit Function
        End If
        l_msg = gf_VALIDATE_BE(l_path)
        If Len(l_msg) > 0 Then
            MsgBox l_msg, vbExclamation
        ElseIf gf_UPDATE_BE(l_path) Then
            '=== 必要ならテーブル追加等を反映してからリンク ===
            If gf_LINK_TABLES(l_path) Then MsgBox "リンクが完了しました。", vbInformation
        End If
    Loop
    '=== リンクが正常でも、プログラム更新で変更が必要な場合がある ===
    If Not gf_UPDATE_BE(gf_GET_BE_PATH()) Then
        DoCmd.Quit acQuitSaveNone
        Exit Function
    End If
    DoCmd.OpenForm "F_メニュー"
End Function

リンクが正常になるまで、ファイル選択を繰り返します。間違ったファイルを選んだときは理由を表示して、もう一度選んでもらいます。gf_UPDATE_BE は後で紹介する、テーブルや列の追加をデータファイルに反映する関数です(変更が無ければ何もしません)。

AutoExecマクロから呼び出す
[作成]→[マクロ]で新しいマクロを作り、アクション「プロシージャの実行」のプロシージャ名に gf_STARTUP() と入力して、AutoExec という名前で保存します。AutoExecマクロは、ファイルを開いたときに自動で実行されます。

AutoExecマクロで「プロシージャの実行」にgf_STARTUP()を設定した画面

起動時フォームのForm_Openでは切り替えない
[ファイル]→[オプション]の「フォームの表示」で起動時のフォームを指定している場合、そのフォームの Form_Open でリンク確認をしたくなります。しかし、Form_Open の中で別のフォームを DoCmd.OpenForm で開き、Cancel = True で自分を閉じようとすると、開いたフォームごと閉じてしまい、何も表示されなくなります。起動時のフォームは指定せず、AutoExecマクロから gf_STARTUP でメニューを開く方が素直です。

実行イメージ

例として、C:\社員管理 フォルダに、フロントエンド 社員管理.accdb とバックエンド 社員管理_データ.accdb を置いた構成で動かします。

初回・更新時(リンク切れ)
開発PCで作ったフロントエンドを開くと、リンク先が見つからないため「データファイルに接続できません。」と表示されます。OKを押すとファイル選択ダイアログが開きます。社員管理_データ.accdb を選ぶと「リンクが完了しました。」と表示され、メニューが開きます。次回からは、確認だけですぐにメニューが開きます。

初回起動・プログラム更新時の流れ(接続できないメッセージ → データファイルを選択 → リンク完了 → メニュー)

間違ったファイルを選んだとき
別のシステムのaccdbなど、テーブルが揃っていないファイルを選ぶと、次のように表示されて、もう一度ファイル選択に戻ります。

選択されたファイルは、このシステムのデータファイルではないようです、というメッセージ

開いているフロントエンド自身は選べない
ファイル選択ダイアログで、いま開いているフロントエンド(社員管理.accdb)を選ぶと、ダイアログ側で「このファイルは使用されています。」と表示され、選択できません。同じフォルダに並んでいるフロントエンドをうっかり選んでも、そこで止まります。

このファイルは使用されています、というファイル選択ダイアログの警告

プログラムを更新するとき
新しい 社員管理.accdb を上書きコピーして開き、社員管理_データ.accdb を選び直すだけです。データファイルには触らないので、入力済みのデータはそのまま残ります。

テーブルや列を追加したいとき

運用していると、「社員に列を足したい」「マスタを1つ増やしたい」といった変更も出てきます。プログラムと違ってデータファイルは差し替えられないので、変更内容をVBAでデータファイルに反映する処理を用意しておきます。gf_STARTUP から呼んでいるので、新しいフロントエンドを開いたときに自動で反映されます。

リンクテーブル経由ではテーブル定義を変更できない
フロントエンドで CurrentDb.Execute "ALTER TABLE T_社員 ..." を実行すると、「リンクされているデータ ソースに対してデータ定義ステートメントを実行することはできません。」(エラー3611)になります。DBEngine.OpenDatabaseデータファイルを直接開いて実行します。

'=========================================================
'  プロシージャ名:gf_UPDATE_BE
'  機能  :データファイルにテーブル・列の追加を反映します。
'            変更内容は pf_UPDATE_ITEMS に書きます。
'
'  【引数】
'    strPath :データファイルのフルパス
'
'  【戻り値】
'    True  … 正常終了(変更が無かった場合も含む)
'    False … エラー発生
'
'  【処理概要】
'    ・読み取り専用で開き、必要な変更があるかだけを確認
'    ・変更が無ければ何もしない
'    ・変更があればバックアップを作成してから反映し、
'      リンクを作り直す
'=========================================================
Public Function gf_UPDATE_BE(strPath As String) As Boolean
    On Error GoTo err_UPDATE_BE
    Dim l_be As DAO.Database      ' データファイルを直接開く
    Dim l_items As String         ' 必要な変更の一覧
    Dim l_bak As String           ' バックアップファイル
    '=== 必要な変更があるか確認(読み取り専用)===
    Set l_be = DBEngine.OpenDatabase(strPath, False, True)
    l_items = pf_UPDATE_ITEMS(l_be, False)
    l_be.Close
    Set l_be = Nothing
    If Len(l_items) = 0 Then
        gf_UPDATE_BE = True
        Exit Function
    End If
    '=== バックアップ(同じフォルダに日時付きでコピー)===
    l_bak = Left(strPath, InStrRev(strPath, ".") - 1) & "_" & Format(Now(), "yyyymmddhhnnss") & "_bak.accdb"
    FileCopy strPath, l_bak
    '=== 変更を反映 ===
    Set l_be = DBEngine.OpenDatabase(strPath)
    Call pf_UPDATE_ITEMS(l_be, True)
    l_be.Close
    Set l_be = Nothing
    '=== 追加した列・テーブルをリンクに反映 ===
    If Not gf_LINK_TABLES(strPath) Then Exit Function
    MsgBox "データファイルを更新しました。" & vbCrLf & l_items & vbCrLf & _
           "バックアップ:" & l_bak, vbInformation
    gf_UPDATE_BE = True
    Exit Function
err_UPDATE_BE:
    MsgBox "【gf_UPDATE_BE】データファイル更新中にエラー発生" & vbCrLf & _
           "番号:" & Err.Number & vbCrLf & _
           "内容:" & Err.Description, vbCritical
    If Not l_be Is Nothing Then l_be.Close
    gf_UPDATE_BE = False
End Function

'=========================================================
'  プロシージャ名:pf_UPDATE_ITEMS
'  機能  :データファイルに必要な変更を確認・実行します。
'            リリースごとに【変更内容】の部分を追記します。
'
'  【引数】
'    p_db      :データファイル
'    p_Execute :False … 確認だけ / True … 実行する
'
'  【戻り値】
'    必要な変更の一覧(無ければ長さ0)
'=========================================================
Private Function pf_UPDATE_ITEMS(p_db As DAO.Database, p_Execute As Boolean) As String
    Dim l_items As String
    '=== 【変更内容】ここから ===
    '--- v1.1:社員にメールアドレスを追加 ---
    If Not pf_FIELD_EXISTS(p_db, "T_社員", "メールアドレス") Then
        l_items = l_items & " ・T_社員にメールアドレスを追加" & vbCrLf
        If p_Execute Then
            p_db.Execute "ALTER TABLE T_社員 ADD COLUMN メールアドレス TEXT(100)", dbFailOnError
            Call ps_ALLOW_ZERO_LENGTH(p_db, "T_社員", "メールアドレス")
        End If
    End If
    '--- v1.1:資格マスタを追加 ---
    If Not pf_TABLE_EXISTS(p_db, "M_資格") Then
        l_items = l_items & " ・M_資格を作成" & vbCrLf
        If p_Execute Then
            p_db.Execute "CREATE TABLE M_資格 (資格コード LONG CONSTRAINT PK_M_資格 PRIMARY KEY, " & _
                         "資格名 TEXT(50))", dbFailOnError
            Call ps_ALLOW_ZERO_LENGTH(p_db, "M_資格")
        End If
    End If
    '=== 【変更内容】ここまで ===
    pf_UPDATE_ITEMS = l_items
End Function

'--- テーブルの有無 ---
Private Function pf_TABLE_EXISTS(p_db As DAO.Database, p_Table As String) As Boolean
    Dim l_td As DAO.TableDef
    For Each l_td In p_db.TableDefs
        If l_td.Name = p_Table Then pf_TABLE_EXISTS = True: Exit Function
    Next l_td
End Function

'--- 列の有無 ---
Private Function pf_FIELD_EXISTS(p_db As DAO.Database, p_Table As String, p_Field As String) As Boolean
    Dim l_fld As DAO.Field
    If Not pf_TABLE_EXISTS(p_db, p_Table) Then Exit Function
    For Each l_fld In p_db.TableDefs(p_Table).Fields
        If l_fld.Name = p_Field Then pf_FIELD_EXISTS = True: Exit Function
    Next l_fld
End Function

'--- テキスト列の「空文字列の許可」をはいにする ---
'    p_Field を指定したときはその列だけ、省略時はテーブルの全テキスト列
'    (既存テーブルに列を足すときは、既存列の設定を変えないよう必ず指定する)
Private Sub ps_ALLOW_ZERO_LENGTH(p_db As DAO.Database, p_Table As String, Optional p_Field As String = "")
    Dim l_fld As DAO.Field
    '=== DDLで作ったテーブル・列は Refresh しないと見えない ===
    p_db.TableDefs.Refresh
    p_db.TableDefs(p_Table).Fields.Refresh
    For Each l_fld In p_db.TableDefs(p_Table).Fields
        If Len(p_Field) = 0 Or l_fld.Name = p_Field Then
            If l_fld.Type = dbText Or l_fld.Type = dbMemo Then l_fld.AllowZeroLength = True
        End If
    Next l_fld
End Sub

リリースのたびに、pf_UPDATE_ITEMS の【変更内容】に追記していきます。前の版の変更も残しておけば、何版か飛ばして更新した利用者のデータファイルにも、足りない変更だけが反映されます。

「無ければ追加」の形で書く
テーブルや列の有無を確認してから実行するので、何度起動しても、変更は1回だけ反映されます。確認だけのとき(p_Execute = False)と実行するときで同じ判定を使うので、「確認では必要と出たのに、実行されない」といった食い違いも起きません。

新しいテーブルは c_LINK_TABLES にも追加する
M_資格 を作る場合は、モジュール先頭の c_LINK_TABLES にも M_資格 を加えておきます。起動時は「データファイルの更新 → リンク」の順に処理するので、データファイルにまだ M_資格 が無い状態でも、作成してからリンクされます。

DDLで作ったテキスト列は空文字を受け付けない
CREATE TABLEALTER TABLE で作ったテキスト列は、「空文字列の許可」がいいえになります。この列に '' をINSERTするとエラーになります。さらに DoCmd.SetWarnings False の状態で DoCmd.RunSQL を使っていると、エラーも出ずに行ごと追加されないため、原因に気づきにくくなります。ps_ALLOW_ZERO_LENGTH で、作った直後に「はい」にしています。既存のテーブルに列を足すときは、追加した列の名前を指定します。指定しないと、既存の列(空文字を禁止していた「氏名」など)の設定まで変わってしまうためです。

DDLの直後は TableDefs を Refresh する
ExecuteCREATE TABLE を実行した直後に TableDefs("M_資格") を参照すると、「このコレクションには項目がありません。」(エラー3265)になります。DAOのコレクションが自動では更新されないためです。ps_ALLOW_ZERO_LENGTH の先頭で TableDefs.RefreshFields.Refresh を実行しています。

共有しているときは全員が閉じてから
他の人がそのテーブルを開いていると、テーブル定義の変更は「使用中」のエラーになります。更新版を配るときは、全員がAccessを閉じた状態で、1台目のPCで起動してもらいます。

分割して運用するときの注意点

実際に業務システムを分割して納品したときに、気をつけた点をまとめます。

フロントエンドは利用者ごとにコピーする
フロントエンドを共有フォルダに置いて全員で開くと、分割した意味が薄れます。各PCにフロントエンドをコピーし、バックエンドだけを共有フォルダに置きます。

共有フォルダはUNCパスで選ぶ
ネットワークドライブ(Z:\ など)の文字はPCによって違うことがあります。ファイル選択で \\サーバー名\共有名\... の形のパスを選んでもらうと、リンクが切れにくくなります。

「信頼できる場所」に置く
フロントエンドを別のフォルダに移すと、「セキュリティの警告」が出てVBAが無効になることがあります。この状態ではAutoExecマクロから gf_STARTUP を呼べません。[ファイル]→[オプション]→[トラストセンター]の「信頼できる場所」にフォルダを登録してもらいます。

バックエンドは自動で最適化されない
「閉じるときに最適化する」は、開いているフロントエンドにしか効きません。データの追加・削除でバックエンドは大きくなっていくので、定期的に最適化します(誰も開いていない状態で行います)。

リレーションシップはバックエンド側で設定する
参照整合性などのリレーションシップは、実体のテーブルがあるバックエンドで設定します。

バックアップファイルがたまる
gf_UPDATE_BE は、変更を反映するたびにデータファイルと同じフォルダへバックアップを作ります。古いものは適宜削除するよう、手順書に書いておきます。

最後に

以上が、AccessVBAでフロントエンドとバックエンドを分割し、起動時にリンクを確認する方法です。

1つのaccdbのまま運用すると、プログラムを更新するたびに「データをどうやって移すか」を考えることになります。分割しておけば、プログラムの更新はフロントエンドを差し替えるだけで済み、データはバックエンドに残り続けます。

起動時のリンク確認を共通部品にしておけば、初めて使うときも更新するときも「データファイルを選ぶだけ」になります。お客様に渡す手順書が短くなり、問い合わせも減ります。

ファイルやフォルダを選ぶダイアログを共通部品にする方法は、こちらでも紹介しています。

コメント

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