VB.NETとDAOでAccessリンクテーブルのパスを一括検証・更新する方法(Excel・SharePoint対応の実践ノウハウ)
Access(.accdb)をフロントエンドに使った業務アプリでは、バックエンドや外部データへのリンクテーブルが壊れやすく、移設・共有フォルダ変更・PC入替・OneDrive/SharePoint移行などのイベントで「参照先が見つからない」状態に陥りがちです。本記事では、VB.NETからDAOを使ってリンク先パスを自動検証し、正しいパスへ再リンクするためのベストプラクティスを、Excelリンク・SharePointリンクの注意点まで含めて体系的に解説します。
結論(先に要点)
- リンク再設定はDAO(Microsoft.Office.Interop.Access.Dao)が最短・最安定。
TableDef.Connectを書き換え、RefreshLink()で反映します。 - Excelリンクも同じ手順でOK。
Connect内のDATABASE=の値(ファイルパス)だけ差し替えます。HDRやIMEXなどのパラメータはそのまま保持。 - SharePointリンクはACEエンジン経由の「仮想テーブル」。更新系SQLは制約や不安定要素が多いため、読み取り中心に使うか、ローカルテーブル経由/SharePoint API利用を検討します。
- 32/64ビットの不一致に注意。VB.NETアプリはAccess/ACEと同一ビット数(x86 or x64)に揃えるのが安全です。
なぜDAOなのか:ODBC/OleDBと比較したアプローチの違い
Accessのリンクテーブルは、Access自身が管理するDAOの世界で完結しています。ODBC/OleDB経由で再リンクを制御しようとすると、接続情報やメタデータ(テーブル種別、シート名、範囲など)を完全再現するのが難しく、再リンク用のAPIも露出が限定的です。一方DAOでは、
Database.TableDefsからリンクテーブルを列挙TableDef.Connectの書き換えTableDef.RefreshLink()で即時反映
という最小手順で確実に目的を達成できます。Accessの「リンクテーブル マネージャー」で行う操作を、そのままコードで自動化できるイメージです。
リンク種別の見極め(Access/Excel/SharePoint/ODBC)
Connect文字列の先頭やキーでおおよそ判別できます。
| リンク種別 | 典型的なConnectの例 | 判別のヒント |
|---|---|---|
| Access(ACCDB/MDB) | ;DATABASE=C:\Data\Backend.accdb | Connectが;DATABASE=で始まる |
| Excel | Excel 12.0 Xml;HDR=YES;IMEX=2;ACCDB=YES;DATABASE=C:\Data\Book.xlsx | Excelで始まり、DATABASE=キーを持つ |
| SharePoint(WSS) | WSS;...(サイトURLやリストID等) | WSSやSHAREPOINTを含む |
| ODBC/その他 | ODBC;DSN=...;DATABASE=... | ODBCで始まる |
本記事では、Access/Excel/SharePointを中心に解説します。
最小コード例:AccessとExcelの再リンク
まずは最小限のサンプルです。DAOの参照設定(Microsoft.Office.Interop.Access.Dao)を追加してください。
Imports Microsoft.Office.Interop.Access
Imports Microsoft.Office.Interop.Access.Dao
Module Module1
Sub Main()
Dim frontEnd As String = "C:\Data\FrontEnd.accdb" ' フロントエンド
Dim newAccdb As String = "D:\Data\Backend.accdb" ' 正しいバックエンド
Dim newExcel As String = "D:\Data\Book1.xlsx" ' 正しいExcel
Dim dbe As New DBEngine()
Dim db As Database = dbe.OpenDatabase(frontEnd, False, False)
For Each td As TableDef In db.TableDefs
' システムテーブルはスキップ
If td.Name.StartsWith("MSys", StringComparison.OrdinalIgnoreCase) OrElse
td.Name.StartsWith("USys", StringComparison.OrdinalIgnoreCase) Then
Continue For
End If
If td.Connect IsNot Nothing AndAlso td.Connect.Length > 0 Then
Dim c As String = td.Connect
' Accessリンク
If c.StartsWith(";DATABASE=", StringComparison.OrdinalIgnoreCase) Then
td.Connect = ";DATABASE=" & newAccdb
td.RefreshLink()
' Excelリンク
ElseIf c.StartsWith("Excel", StringComparison.OrdinalIgnoreCase) Then
td.Connect = ReplaceDatabasePathInConnect(c, newExcel)
td.RefreshLink()
End If
End If
Next
db.Close()
End Sub
' Connect内の DATABASE= の値を差し替える
Private Function ReplaceDatabasePathInConnect(connect As String, newPath As String) As String
Dim key As String = "DATABASE="
Dim idx As Integer = connect.IndexOf(key, StringComparison.OrdinalIgnoreCase)
If idx < 0 Then Return connect
Dim start As Integer = idx + key.Length
Dim endIdx As Integer = connect.IndexOf(";", start)
If endIdx < 0 Then endIdx = connect.Length
Dim currentPath As String = connect.Substring(start, endIdx - start)
Return connect.Replace(key & currentPath, key & newPath)
End Function
End Module
ExcelリンクのConnectに含まれるパラメータ(HDRやIMEXなど)はそのまま維持し、DATABASE=の値だけ差し替えています。
実務投入できる「堅牢リリンカー」:検証・ドライラン・集計・例外対応
運用現場では「現状のリンクが正しいか先に検証したい」「置換ルールをまとめて適用したい」「何件成功/失敗したか知りたい」といった要望が定番です。以下はそれらを満たす実務向けのフルサンプルです。
Imports Microsoft.Office.Interop.Access
Imports Microsoft.Office.Interop.Access.Dao
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Runtime.InteropServices
Public Module AccessRelinker
Public Sub RelinkAll(frontEndPath As String,
accessPathMap As Dictionary(Of String, String),
excelPathMap As Dictionary(Of String, String),
Optional dryRun As Boolean = False)
Dim dbe As New DBEngine()
Dim db As Database = Nothing
Dim ok As Integer = 0, ng As Integer = 0, skipped As Integer = 0
Try
db = dbe.OpenDatabase(frontEndPath, False, False)
For Each td As TableDef In db.TableDefs
If IsSystemTable(td.Name) Then
skipped += 1
Continue For
End If
Dim conn As String = If(td.Connect, String.Empty).Trim()
If conn.Length = 0 Then
' ローカルテーブル
skipped += 1
Continue For
End If
Dim kind As LinkKind = DetectKind(conn)
Select Case kind
Case LinkKind.Access
Dim oldPath = GetDatabasePathFromConnect(conn)
Dim newPath = ApplyMap(oldPath, accessPathMap)
ProcessRelink(db, td, kind, oldPath, newPath, dryRun, ok, ng)
Case LinkKind.Excel
Dim oldPath = GetDatabasePathFromConnect(conn)
Dim newPath = ApplyMap(oldPath, excelPathMap)
ProcessRelink(db, td, kind, oldPath, newPath, dryRun, ok, ng)
Case LinkKind.SharePoint
' 検証のみ(更新は制限が多いため別扱い)
If TestOpen(db, td.Name) Then
ok += 1
Else
ng += 1
End If
Case Else
' ODBC等はスコープ外としてスキップ
skipped += 1
End Select
Next
Finally
If db IsNot Nothing Then db.Close()
End Try
Console.WriteLine($"OK={ok}, NG={ng}, SKIP={skipped}")
End Sub
Private Sub ProcessRelink(db As Database,
td As TableDef,
kind As LinkKind,
oldPath As String,
newPath As String,
dryRun As Boolean,
ByRef ok As Integer,
ByRef ng As Integer)
If String.IsNullOrEmpty(oldPath) Then
ng += 1 : Return
End If
' 置換ルールに該当しない場合は現状維持
If String.IsNullOrEmpty(newPath) OrElse
String.Equals(oldPath, newPath, StringComparison.OrdinalIgnoreCase) Then
' リンク先が存在するかだけ検証
If FileExistsForKind(kind, oldPath) AndAlso TestOpen(db, td.Name) Then
ok += 1
Else
ng += 1
End If
Return
End If
If dryRun Then
Console.WriteLine($"[DRYRUN] {td.Name} : {oldPath} -> {newPath}")
If FileExistsForKind(kind, newPath) Then ok += 1 Else ng += 1
Return
End If
Try
Select Case kind
Case LinkKind.Access
td.Connect = ";DATABASE=" & newPath
Case LinkKind.Excel
td.Connect = SetConnectValue(td.Connect, "DATABASE", newPath)
End Select
td.RefreshLink()
' 実際に開けるか最終チェック
If FileExistsForKind(kind, newPath) AndAlso TestOpen(db, td.Name) Then
ok += 1
Else
ng += 1
End If
Catch ex As Exception
Console.WriteLine($"[ERROR] {td.Name} : {ex.Message}")
ng += 1
End Try
End Sub
Private Function FileExistsForKind(kind As LinkKind, path As String) As Boolean
Select Case kind
Case LinkKind.Access, LinkKind.Excel
Return File.Exists(path)
Case Else
Return True ' SharePoint/ODBCは物理ファイル存在で判断不可
End Select
End Function
Private Function GetDatabasePathFromConnect(connect As String) As String
Return GetConnectValue(connect, "DATABASE")
End Function
Private Function SetConnectValue(connect As String, key As String, newValue As String) As String
Dim pat As String = "(^|;)" & Regex.Escape(key) & "=[^;]*"
Dim repl As String = "$1" & key & "=" & newValue
If Regex.IsMatch(connect, pat, RegexOptions.IgnoreCase) Then
Return Regex.Replace(connect, pat, repl, RegexOptions.IgnoreCase)
Else
' キーが無い場合は末尾に追加
If connect.EndsWith(";") Then
Return connect & key & "=" & newValue
Else
Return connect & ";" & key & "=" & newValue
End If
End If
End Function
Private Function GetConnectValue(connect As String, key As String) As String
Dim pat As String = "(^|;)" & Regex.Escape(key) & "=([^;]*)"
Dim m = Regex.Match(connect, pat, RegexOptions.IgnoreCase)
If m.Success Then Return m.Groups(2).Value
Return String.Empty
End Function
Private Function ApplyMap(oldPath As String, map As Dictionary(Of String, String)) As String
If String.IsNullOrEmpty(oldPath) OrElse map Is Nothing Then Return oldPath
For Each kv In map
If oldPath.StartsWith(kv.Key, StringComparison.OrdinalIgnoreCase) Then
Return kv.Value & oldPath.Substring(kv.Key.Length)
End If
Next
Return oldPath
End Function
Private Function DetectKind(connect As String) As LinkKind
Dim c = connect.Trim()
If c.StartsWith(";DATABASE=", StringComparison.OrdinalIgnoreCase) Then
Return LinkKind.Access
End If
If c.StartsWith("Excel", StringComparison.OrdinalIgnoreCase) Then
Return LinkKind.Excel
End If
If c.StartsWith("WSS", StringComparison.OrdinalIgnoreCase) OrElse
c.IndexOf("SHAREPOINT", StringComparison.OrdinalIgnoreCase) >= 0 Then
Return LinkKind.SharePoint
End If
If c.StartsWith("ODBC", StringComparison.OrdinalIgnoreCase) Then
Return LinkKind.ODBC
End If
Return LinkKind.Unknown
End Function
Private Function IsSystemTable(name As String) As Boolean
Return name.StartsWith("MSys", StringComparison.OrdinalIgnoreCase) OrElse
name.StartsWith("USys", StringComparison.OrdinalIgnoreCase)
End Function
Private Function TestOpen(db As Database, tableName As String) As Boolean
Try
Using rs As Recordset = db.OpenRecordset(tableName, RecordsetTypeEnum.dbOpenSnapshot)
Dim cnt As Integer = rs.RecordCount ' アクセス試験
End Using
Return True
Catch
Return False
End Try
End Function
Public Enum LinkKind
Unknown = 0
Access
Excel
SharePoint
ODBC
End Enum
End Module
使い方の例:
Sub Main()
Dim frontEnd = "C:\Apps\FrontEnd.accdb"
' ルート置換ルール(前方一致で置換)
Dim accMap As New Dictionary(Of String, String) From {
{"C:\LegacyData\", "D:\ProdData\"},
{"\\oldfilesrv\share\", "\\newfilesrv\data\"}
}
Dim xlsMap As New Dictionary(Of String, String) From {
{"C:\LegacyExcel\", "D:\Sheets\"}
}
' まずはドライラン(検証のみ)
RelinkAll(frontEnd, accMap, xlsMap, dryRun:=True)
' 問題なければ本番反映
RelinkAll(frontEnd, accMap, xlsMap, dryRun:=False)
End Sub
ポイントは、先にドライランを実行して影響範囲と正否を確認し、その後に本番反映する流れです。ログを残す場合はコンソール出力ではなくテキストファイルへの追記に切り替えてください。
Excelリンクテーブルの実務ポイント
- Connect文字列をそのまま活かす:
HDR(1行目ヘッダー)やIMEX(混在データの読み込みモード)、ACCDBなどは環境によって意味が出ます。触るのはDATABASE=だけに留めるのが安全です。 - シート名/範囲は
SourceTableName:Sheet1$やSheet1$A1:C100などの指定はTableDef.SourceTableNameで管理され、Connect側には入りません。パス差し替えではここをいじる必要はありません。 - ファイル存在チェック:差し替え後は
File.Exists()で実在を確認し、RefreshLink()→スナップショットでのOpenRecordsetまで行うと安心です。
<table>
<thead>
<tr>
<th>キー</th>
<th>意味</th>
<th>備考</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>HDR</code></td>
<td>1行目をヘッダーとして扱う</td>
<td><code>YES/NO</code></td>
</tr>
<tr>
<td><code>IMEX</code></td>
<td>混在型の取り扱い</td>
<td>リンクでは<code>2</code>を使うケースが多い</td>
</tr>
<tr>
<td><code>DATABASE</code></td>
<td>Excelファイルのフルパス</td>
<td>今回の差し替え対象</td>
</tr>
</tbody>
</table>
SharePointリンクテーブル(WSS)の落とし穴と回避策
Accessの「外部データ -> SharePointリスト」をリンクしたテーブルは、ACEエンジンがRESTのような形で裏側のリストを仮想化して見せています。実体はDBファイルではないため、以下のような特徴・制約があります。
- 更新系SQLの制約:複数行テキスト、複数選択、ルックアップなどのフィールドが絡むと、
No available ISAMやCannot find tableといった例外を引き起こしやすい。 - スキーマの変化に弱い:SharePoint側の列追加/型変更で突然失敗するケースがある。
- 権限・バージョン依存:サイト権限不足やMFA、組織ポリシーでブロックされる場合がある。
推奨の回避策:
- 読み取り中心に割り切る(集計・参照用途)。
- 更新が必要なら、ローカルテーブルへコピー→編集→Append/更新の二段階運用にする。
- 本格的なCRUDはSharePoint/GraphのAPIで行い、AccessはUIやバッチのトリガに限定する。
- Accessの「リンクテーブル マネージャー」で再リンクし、ダメな場合はSharePoint側の権限や列型(特に複数選択系)を疑う。
32/64ビット、ACEエンジン、配布のコツ
- VB.NETアプリのプラットフォームターゲットはx86/x64を明示し、Access/ACEと同じビット数に合わせる(AnyCPUは避ける)。
- Access本体が無くても、Access Database Engine(ACE)が入っていればDAOは動作します(企業環境ではソフトウェアカタログ経由で配布)。
- COM相互運用のため、意図せぬプロセス残留を避けるなら、必要に応じて
Marshal.FinalReleaseComObject等でDAOオブジェクトを解放します。 - 共有環境では、LACCDB(ロックファイル)やウイルス対策ソフトのリアルタイムスキャンが遅延を招くことがあります。リリンクは短時間で終わるためログオン直後の実行を推奨します。
よくあるエラーと処方箋
| 症状/メッセージ | 主な原因 | 対処 |
|---|---|---|
| No available ISAM | SharePointやExcelリンクの制約、ACEの不整合 | SharePointは読み取り中心にする、ExcelのConnectを正規化、ACEのバージョン/ビット数を揃える |
| Cannot find table / 無効なオブジェクト | 参照先のシート名・リスト名・範囲が変更/削除 | SourceTableNameの再確認、リンク先の存在チェック |
| ファイルが見つかりません | 移設・権限・パス変更 | ルート置換マップを見直す、UNC/OneDriveの実パスを使う |
| プロバイダーの初期化に失敗 | ACE未導入、ビット数不一致 | ACEを導入、アプリのターゲットを揃える(x86/x64) |
導入チェックリスト
- DAO参照(Microsoft.Office.Interop.Access.Dao)を追加済みか。
- アプリのターゲット(x86/x64)はAccess/ACEと一致しているか。
- リンク先(ACCDB/Excel)の新旧ルートマップは正しいか。
- 本番反映前にドライランとバックアップを実施したか。
- SharePointリンクに対しては要件を「参照中心」に見直したか。
運用Tips(現場で効いた小ワザ)
- ユーザー毎のOneDriveパス:
C:\Users\<User>\OneDrive - 会社名\...などユーザー固有の要素は、環境変数やKnownFolderを使って動的に解決してからルート置換に掛けると堅牢です。 - 複数候補の探索:新パスが見つからない場合、候補ルートを順番に
Directory.Existsで探し、最初に見つけた場所でRefreshLink()すると移行直後の混乱に強くなります。 - 権限不足の検知:
TestOpen()でdbOpenSnapshotを試し、失敗したら詳細をログ出力(ユーザー/マシン名、時刻、テーブル名、旧新パス)してヘルプデスクに渡せる形に。 - 最初の起動で自動修復:フロントエンド(ACCDB/ACCDE)起動時に本ツールを先行実行し、リンク修復完了後にフォームを開くようにすると、ユーザーはエラーに遭遇しません。
APIでの更新が必要なとき(SharePoint連携の発展形)
SharePointリストへの本格的な書き込みが不可欠な場合は、Accessのリンクテーブル経由ではなく、アプリ側からREST/Graph APIで操作して、結果をローカルテーブルと同期する設計が安定します。Accessはフォーム/レポートや補助的なバッチ処理に特化し、リンクテーブルは参照キャッシュとして割り切るのが運用コストを抑えるコツです。
テスト戦略:安全に本番へ
- テスト用の複製フロントエンドを作成(ファイルコピーでOK)。
- 意図的にリンクを壊す(Excel/ACCDBのパスをダミーに)。
- ドライランで「検出→提案置換」を確認。ログをレビュー。
- 本番モードで再リンク実行。
OpenRecordsetで開けることを自動確認。 - 想定外のリンク(ODBC等)はスキップされているか確認。必要なら別途方針化。
付録:Connect文字列パターン集
| 種類 | サンプル | 差し替えポイント |
|---|---|---|
| Access | ;DATABASE=D:\Data\Backend.accdb | ;DATABASE=以降を新パスに置換 |
| Excel(XML) | Excel 12.0 Xml;HDR=YES;IMEX=2;ACCDB=YES;DATABASE=D:\Data\Book.xlsx | DATABASE=の値のみ置換(他は維持) |
| Excel(旧形式) | Excel 8.0;HDR=YES;IMEX=2;DATABASE=D:\Data\Book.xls | 同上 |
| SharePoint | WSS;...(サイトURL/リストID等) | 基本は検証のみ(更新は設計見直し) |
検索キーワード(調査・深掘りの出発点)
- VB.NET DAO relink TableDef
- Access DAO RefreshLink Excel
- SharePoint linked table ISAM error
- Access TableDef Connect Excel DATABASE HDR IMEX
- ACCDB DAO 64bit 32bit Interop
まとめ
- Accessリンク先パスの検証・修復は、DAOで
Connectを書き換え→RefreshLink()が最も簡単かつ確実。 - Excelリンクは
DATABASE=のみ差し替え、HDR/IMEX等のパラメータは保持。 - SharePointリンクは更新系が不安定。読み取り中心・ローカルテーブル経由・API直アクセスのいずれかで要件に合わせる。
- ビット数(x86/x64)とACEの整合性を揃え、ドライラン→本番反映の二段階運用で安全に導入。

コメント