「ページ削除」という名の破壊的プロセス:VisioとSQL Serverの整合性を守るトランザクション設計
Visioを単なる「図面作成ツール」として扱っているうちは、エンジニアとして二流だ。
エンタープライズ・アーキテクチャの領域において、Visioは「動的なデータモデルの可視化エンジン」であり、その各ページは外部データベース(RDBMS)の特定のエンティティと密結合した、一種の「ビュー」として機能すべきである。
ここで問題となるのが、「ライフサイクルの不整合」だ。
ユーザーがVisio上のページを削除した際、その背後にあるSQL Serverのレコードが生き残っていたらどうなるか? いわゆる「ゴースト・レコード(幽霊データ)」の発生である。これはシステムの整合性を破壊し、将来的なデータ集計や整合性チェックにおいて致命的なバグを引き起こす。
本稿では、Visio VBAを用いて、ページの削除操作とDB側のレコード更新を「アトミック(不可分)」に実行するための、極限の設計手法を伝授する。
1. 概念設計:Two-Phase Commitの模倣
VBAには、分散トランザクション(MSDTC)を直接制御する標準的な手段は乏しい。しかし、我々シニアエンジニアは、アプリケーション層で「擬似的な2フェーズコミット」を実装することで、このリスクを最小化できる。
設計思想は以下の通りだ:
1. DBトランザクションの開始: SQL Server側で書き込み準備を行う。
2. DBレコードの論理削除/更新: 物理削除ではなく、まずはフラグ更新(`is_deleted = 1`等)を推奨する。
3. Visioオブジェクトの削除操作: DBの更新が成功した直後に、Visioのページを削除する。
4. 成否の判定とロールバック:
- DB更新成功 + Visio削除成功 $\rightarrow$ DBトランザクションを `Commit`。
- DB更新成功 + Visio削除失敗 $\rightarrow$ DBトランザクションを `Rollback`。
- DB更新失敗 $\rightarrow$ Visio削除は行わず、エラーを通知。
2. メモリ管理とCOMオブジェクトの生存期間
Visio VBAにおいて、最も見落とされがちなのがCOMオブジェクトの明示的な解放である。
特に、ADODB ConnectionやRecordset、そしてVisioのPageオブジェクトをループや複雑な条件分岐の中で扱う場合、エラー発生時にオブジェクトがメモリ上に残留し、プロセスがゾンビ化するリスクがある。
`Set obj = Nothing` は単なる作法ではない。それは、リソースの所有権をOSに確実に返還するための、エンジニアとしての責務である。
3. 実装:連動型削除クラス `CPageSyncManager`
以下に、堅牢なエラーハンドリングとトランザクション制御を組み込んだ実装例を示す。単なるプロシージャではなく、カプセル化されたクラス設計を採用する。
Class Module: `CPageSyncManager`
' ==============================================================================
' Class: CPageSyncManager
' Description: VisioページとSQL Serverレコードの整合性を保つための管理クラス
' ==============================================================================
Option Explicit
Private m_conn As Object ' ADODB.Connection
Private m_strConn As String ' Connection String
Private m_targetPage As Visio.Page ' 削除対象のページ
' 初期化メソッド
Public Sub Initialize(ByVal connectionString As String)
m_strConn = connectionString
Set m_conn = CreateObject("ADODB.Connection")
End Sub
' ページの同期削除実行
' @param targetPage: 削除対象のVisioページオブジェクト
' @return Boolean: 成功時True, 失敗時False
Public Function SynchronizedDelete(ByVal targetPage As Visio.Page) As Boolean
Dim strSQL As String
Dim pageID As String
Dim success As Boolean
On Error GoTo ErrorHandler
' 1. ページのメタデータ(ID等)を取得
' ※ここではShapeのDataプロパティにDBのPrimary Keyが格納されていると仮定
pageID = GetPageMetadata(targetPage, "DB_Record_ID")
If pageID = "" Then
Err.Raise vbObjectError + 513, "CPageSyncManager", "ページにDB識別子が設定されていません。"
End If
' 2. DB接続とトランザクション開始
m_conn.Open m_strConn
m_conn.BeginTrans
' 3. DB側のレコード更新(論理削除)
' 物理削除(DELETE)よりも論理削除(UPDATE)の方が、監査ログの観点から望ましい
strSQL = "UPDATE SystemRecords SET IsDeleted = 1, DeletedAt = GETDATE() " & _
"WHERE RecordID = '" & pageID & "'"
m_conn.Execute strSQL
' 4. Visioページの削除実行
' DB更新が成功した後に、Visio側の破壊的処理を行う
targetPage.Delete
' 5. 全て成功した場合のみ、DBをコミット
m_conn.CommitTrans
success = True
Debug.Print "Successfully synchronized deletion for Page ID: " & pageID
CleanUp:
' 6. リソースの明示的解放(極めて重要)
If Not m_conn Is Nothing Then
If m_conn.State = 1 Then m_conn.Close
End If
Set m_conn = Nothing
Set m_targetPage = Nothing
SynchronizedDelete = success
Exit Function
ErrorHandler:
' 7. エラー発生時のロールバック処理
Debug.Print "Error encountered: " & Err.Description
If Not m_conn Is Nothing Then
If m_conn.State = 1 Then
m_conn.RollbackTrans ' DB側の変更を取り消す
Debug.Print "Database transaction rolled back."
End If
End If
' エラー情報を呼び出し元に伝搬させる、あるいはログ出力
success = False
Resume CleanUp
End Function
' ページ内のShapeからメタデータを抽出する内部関数
Private Function GetPageMetadata(ByVal pg As Visio.Page, ByVal tagName As String) As String
Dim shp As Visio.Shape
' ページ内の特定のShape(例:隠しShape)からデータを取得するロジック
' ここでは簡略化のため、最初のShapeから取得する例とする
On Error Resume Next
Set shp = pg.Shapes(1)
GetPageMetadata = shp.Cell(visSectionProp, tagName).ResultToString(visITypeText)
On Error GoTo 0
End Function
標準モジュールでの呼び出し例
Sub ExecutePageDeletion()
Dim syncManager As CPageSyncManager
Dim targetPg As Visio.Page
Dim connStr As String
' 環境に応じた接続文字列(Windows認証を想定)
connStr = "Provider=SQLOLEDB;Data Source=SERVER_NAME;Initial Catalog=DB_NAME;Integrated Security=SSPI;"
' 現在選択されているページを取得(または特定のロジックで特定)
Set targetPg = ActivePage
Set syncManager = New CPageSyncManager
' 初期化
syncManager.Initialize connStr
' 同期削除の実行
If syncManager.SynchronizedDelete(targetPg) Then
MsgBox "ページと関連データの同期削除に成功しました。", vbInformation
Else
MsgBox "削除プロセス中にエラーが発生しました。整合性は維持されています。", vbCritical
End If
' インスタンスの破棄
Set syncManager = Nothing
End Sub
4. アーキテクトの視点:レガシー環境への配慮
この実装において、以下の3点に留意せよ。
1. Windows APIによるプロセス監視:
大規模な自動化処理では、Visioがハングアップした場合に備え、`kernel32.dll` の `TerminateProcess` 等を検討することもあるが、基本的にはVBAの `On Error` ハンドラと `Rollback` で制御するのが「正道」である。プロセスを強制終了させることは、DB接続の未完了な切断を招き、DB側にロックを残留させるリスクがあるからだ。
2. エラー・ログの永続化:
`Debug.Print` は開発用だ。本番環境では、エラー内容、発生時刻、対象の `PageID` を、ファイルシステムまたは別の「ログ専用DBテーブル」に書き出す仕組みを組み込め。
3. パフォーマンスの重み:
`BeginTrans` から `CommitTrans` までの間に、重い計算処理やユーザー入力を挟んではならない。トランザクションの保持時間は、DBの排他ロック(Exclusive Lock)の保持時間と直結する。ロックの長時間保持は、システム全体のデッドロックを誘発する。
結論
Visio VBAによる自動化は、単に「操作を自動化する」ことではない。「データのライフサイクルを、図面というインターフェースを通じて正しく制御する」ことである。
この「同期削除」のパターンをマスターすれば、君の構築するシステムは、単なるスクリプトの集合体から、エンタープライズに耐えうる「堅牢なアプリケーション」へと昇華されるだろう。
