Visio VBAを超えろ:ページ削除と外部DBの整合性を守る「アトミック・デリート」の設計思想
「ページを削除した。よし、完了だ。」
もし君が、Visioのページを `Page.Delete` メソッドで消去しただけで満足しているなら、今すぐその思考を捨ててほしい。君が作っているのは「便利なツール」ではない。データの整合性を破壊し、将来的にデータベースに「存在しないはずの参照」を撒き散らす時限爆弾だ。
実務レベルのシステムにおいて、Visioは単なる描画ツールではない。それは「データモデルの可視化レイヤー」だ。ページが特定のSQL Serverのレコード(例えば、設備管理IDやプロジェクトID)と紐付いている場合、Visio側のオブジェクト削除と、DB側のレコード更新は、「不可分な一つの操作(Atomic Operation)」として完結しなければならない。
今回は、中級者への階段を登るために、Visio VBAからSQL Serverを制御し、整合性を保ちながらクリーンアップを行う「連動型削除ツール」の極意を伝授する。
1. 破綻する設計、堅牢な設計
なぜ、単純なコードではいけないのか。設計の差は「例外(エラー)」が起きた瞬間に露呈する。
非効率・危険な設計(スクリプト・レベル)
1. Visioのページを削除する。
2. SQLの `DELETE` または `UPDATE` 文を実行する。
これの何が問題か?
もし、2のDB操作中にネットワークが切断されたらどうなる? Visioのページは消えたが、DBにはゴミデータが残る。これが「孤立したレコード(Orphaned Record)」だ。一度増え始めたゴミデータは、システム全体の信頼性を蝕む。
堅牢な設計(エンジニアリング・レベル)
プロフェッショナルは、「データベースの状態を先に確定させ、Visioの操作をその結果に従わせる」、あるいは「トランザクションによるロールバックを考慮する」設計をとる。
1. 検証フェーズ: 削除対象のページに、DB紐付け用のプロパティ(例:`RecordID`)が存在するか確認する。
2. DBトランザクション開始: SQL Server側で更新処理を開始する。
3. DB更新(ソフトデリート推奨): レコードを物理削除するのではなく、`IsDeleted = 1` のようなフラグ更新を行う。
4. Visio操作: ページを削除する。
5. 確定フェーズ: Visioの削除が成功したら、DBのトランザクションを `COMMIT` する。失敗したら `ROLLBACK` する。
2. 実装の鍵:ページとDBを繋ぐ「隠れたメタデータ」
ページとDBをどう紐付けるか? 図形(Shape)の中にIDを持たせるのは初歩的すぎる。ページそのものに、Custom Properties(ユーザー定義プロパティ)を埋め込むのが、最もスマートで保守性の高い手法だ。
Visioの `Page.CustomProperties` を利用することで、描画要素とは独立した、純粋な「メタデータ・レイヤー」を構築できる。
3. プロダクション・コード例
以下に、ADODBを用いた堅牢な連動削除のテンプレートを示す。これは単なるコピペ用ではない。各行に「なぜその処理が必要か」という意図を込めている。
Option Explicit
' --- 構成定数 ---
Private Const DB_CONN_STR As String = "Provider=SQLOLEDB;Data Source=YOUR_SERVER;Initial Catalog=YOUR_DB;User ID=YOUR_USER;Password=YOUR_PASSWORD;"
Private Const PROP_NAME_ID As String = "DB_Record_ID" ' ページに埋め込むプロパティ名
''' <summary>
''' ページとDBの整合性を保ちながらページを削除するメインルーチン
''' </summary>
''' <param name="pg">削除対象のVisioページ</param>
Public Sub SafeDeletePageWithDB(ByRef pg As Visio.Page)
Dim recordID As String
Dim conn As ADODB.Connection
Dim cmd As ADODB.Command
Dim success As Boolean
On Error GoTo ErrorHandler
' 1. メタデータの抽出
' ページに紐付くDB IDを取得。存在しない場合はエラーとして扱う。
recordID = GetPageCustomProperty(pg, PROP_NAME_ID)
If recordID = "" Then
MsgBox "エラー: このページにはDB紐付けIDが設定されていません。削除を中止します。", vbCritical
Exit Sub
End If
' 2. ユーザーへの最終確認
If MsgBox("ページおよび関連DBレコード(ID: " & recordID & ")を削除しますか?", vbQuestion + vbYesNo) <> vbYes Then
Exit Sub
End If
' 3. DB接続とトランザクションの準備
Set conn = New ADODB.Connection
conn.ConnectionString = DB_CONN_STR
conn.Open
' トランザクション開始
conn.BeginTrans
' 4. DB側の更新(ソフトデリートによる整合性確保)
' 物理削除(DELETE)ではなく、フラグ更新(UPDATE)を推奨。
' 万が一Visio側で失敗した際、後から復旧が可能になるため。
Set cmd = New ADODB.Command
With cmd
.ActiveConnection = conn
.CommandText = "UPDATE YourTable SET IsDeleted = 1, DeletedAt = GETDATE() WHERE RecordID = ?"
.Parameters.Append .CreateParameter("@ID", adVarChar, adParamInput, 50, recordID)
.Execute
End With
' 5. Visio側の操作
' DB更新が成功した後に、Visioのオブジェクトを操作する
pg.Delete
' 6. 全て成功した場合のみ、DBの変更を確定させる
conn.CommitTrans
success = True
MsgBox "ページおよびDBレコードのクリーンアップが完了しました。", vbInformation
CleanUp:
If Not conn Is Nothing Then
If conn.State = adStateOpen Then conn.Close
End If
Set conn = Nothing
Set cmd = Nothing
Exit Sub
ErrorHandler:
' エラーが発生した場合は、DBの状態を元の状態にロールバックする
If Not conn Is Nothing Then
If conn.State = adStateOpen Then
conn.RollbackTrans
MsgBox "DB更新中にエラーが発生したため、ロールバックしました。" & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical
End If
End If
' Visio側の操作でエラーが起きた場合、DBはロールバックされているため整合性は保たれる
Resume CleanUp
End Sub
''' <summary>
''' ページから特定のカスタムプロパティの値を取得するヘルパー関数
''' </summary>
Private Function GetPageCustomProperty(ByRef pg As Visio.Page, ByVal propName As String) As String
Dim cp As Visio.CustomProperty
On Error Resume Next
For Each cp In pg.CustomProperties
If cp.Name = propName Then
GetPageCustomProperty = CStr(cp.Value)
Exit Function
End If
Next cp
On Error GoTo 0
GetPageCustomProperty = "" ' 見つからない場合は空文字を返す
End Function
4. アーキテクトからの警告:運用上の注意点
このコードをプロダクション環境に投入する前に、以下の3点を必ず検討せよ。
① 「物理削除」か「論理削除」か
上記のコードでは `UPDATE … SET IsDeleted = 1` という論理削除(Soft Delete)を採用している。これはエンジニアリングにおける鉄則だ。Visio側の操作(例えば、大量のページを一括削除するループ処理など)で予期せぬエラーが起きた際、DB側を物理的に消してしまっていると、復旧が不可能になる。DB側には必ず「いつ、誰が消したか」の証跡を残せ。
② ページ削除のループ処理における罠
複数のページを一度に削除するツールを作る場合、`For Each pg In ActiveDocument.Pages` のようなループをそのまま使うな。ページを削除していくと、コレクションのインデックスがズレて、処理がスキップされたりエラーになったりする。
必ず 「後ろのページから(Step -1)」 または 「削除対象を一度配列に格納してから」 処理を行うこと。
③ エラーハンドリングの深度
VBAの `On Error GoTo` は強力だが、万能ではない。特にADODBの接続エラーやタイムアウトは、ネットワーク環境に依存する。本番環境では、接続文字列に `Connect Timeout=30` を含めるなど、インフラ側の挙動をコード側でコントロールする意識を持て。
結論
Visio VBAで業務ツールを構築する際、君の戦場はVisioのキャンバス内だけではない。その背後にあるデータベース、そしてデータ全体の整合性が、君の真の戦場だ。
「動けばいい」という思考を捨て、「壊れない設計」に時間を割け。それが、単なるスクリプト作成者と、信頼されるアーキテクトを分かつ境界線である。
