データベースが設計する「真の世代管理」:AutoCAD VBAによるSQL Server連携型リビジョン制御の実装
図面管理を「ファイル名に手動で枝番をつける作業」だと思っているなら、今すぐその思考を捨ててほしい。それはエンジニアリングではなく、単なる「作業」だ。
設計変更のたびに、誰かがファイル名に `_Rev01`、`_Rev02` と打ち込む。その過程で必ず発生するのが、枝番の重複、更新漏れ、そして「どれが最新の正本か分からない」という混沌である。
プロフェッショナルが構築すべきは、「図面ファイル」と「データベース(SQL Server)」をアトミック(不可分)に同期させる、自律型世代管理システムだ。
本記事では、AutoCAD VBAを起点とし、SQL Server上の履歴テーブルと連動して、保存時に自動でリビジョンをインクリメントし、ファイル名へ反映させる堅牢なソリューションの設計思想と実装を伝授する。
1. 設計思想:Single Source of Truth (信頼できる唯一の情報源)
多くの開発者が陥る罠は、「ファイル名がリビジョンを規定している」という設計だ。これでは、ファイルがコピーされた瞬間に管理系が崩壊する。
真に堅牢なシステムでは、「SQL Serverがリビジョンを決定し、ファイルはその指示に従って名前を変える」という一方向の依存関係を構築する。
アーキテクチャの要件
1. DB主導の決定: 保存実行時、VBAはまずDBへ「この図面の現在の最新リビジョンは何番か?」を問い合わせる。
2. アトミックな更新: 「新しいファイル名の生成」→「図面の保存(SaveAs)」→「DBのリビジョン更新」というプロセスを一つのトランザクションとして扱う。
3. エラーハンドリングの徹底: ファイルの保存に失敗した(ディスクフルや権限エラー)場合、DBのリビジョンだけが進んでしまうという「データの不整合」を絶対に許さない。
2. データベース設計(SQL Server)
まずは、管理の基盤となるテーブルを定義する。単なるファイル名リストではなく、メタデータ(作成者、更新日時、リビジョン番号)を保持できる構造にする。
CREATE TABLE DrawingHistory (
DrawingID INT IDENTITY(1,1) PRIMARY KEY,
BaseFileName NVARCHAR(255) NOT NULL, -- 枝番を除いた元のファイル名
CurrentRevision INT NOT NULL DEFAULT 0,
LastUpdatedBy NVARCHAR(100),
LastUpdatedAt DATETIME DEFAULT GETDATE(),
FilePath NVARCHAR(500) NOT NULL,
CONSTRAINT UQ_BaseFileName UNIQUE (BaseFileName)
);
3. プロダクション級の実装コード
ここからは、AutoCAD VBAからの実装だ。ここでは、ADODB(ActiveX Data Objects)を使用してSQL Serverと通信する。
注意: 参照設定で `Microsoft ActiveX Data Objects x.x Library` にチェックを入れていることを前提とする。
モジュール構成:`modRevisionManager`
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;"
''' <summary>
''' 保存プロセスを実行するメインルーチン
''' </summary>
Public Sub ExecuteAutoRevisionSave()
Dim acadDoc As AcadDocument
Set acadDoc = ThisDrawing
Dim baseName As String
Dim currentPath As String
Dim newFileName As String
Dim newRev As Integer
Dim connection As ADODB.Connection
On Error GoTo ErrorHandler
' 1. 基本情報の取得
' 現在のフルパスから、枝番を除いた「ベース名」を抽出するロジックが必要
' ここでは簡易的に、現在のファイル名が「Drawing_Rev01.dwg」なら「Drawing」をベースとする
baseName = GetBaseFileName(acadDoc.Name)
currentPath = acadDoc.Path
If currentPath = "" Then
MsgBox "図面が一度も保存されていません。まず手動で保存してください。", vbCritical
Exit Sub
End If
' 2. DB接続
Set connection = New ADODB.Connection
connection.Open DB_CONN_STR
' 3. DBから現在のリビジョンを取得し、次のリビジョンを決定
' ここで「SELECT ... FOR UPDATE」的な排他制御を検討すべきだが、VBA単体では
' 取得と更新の間に隙間ができるため、トランザクションを利用する
connection.BeginTrans
newRev = GetNextRevisionFromDB(connection, baseName)
' 4. 新しいファイル名の構築 (例: Drawing_Rev02.dwg)
newFileName = currentPath & "\" & baseName & "_Rev" & Format(newRev, "00") & ".dwg"
' 5. AutoCAD図面の保存実行 (SaveAs)
' ※重要: ファイル保存が成功しない限り、DBの更新へ進まない
acadDoc.SaveAs newFileName
' 6. DBのリビジョンとメタデータの更新
UpdateDBRevision connection, baseName, newRev, acadDoc.Name
' 7. トランザクション完了
connection.CommitTrans
MsgBox "リビジョンアップ完了: " & vbCrLf & newFileName, vbInformation, "Success"
CleanUp:
If Not connection Is Nothing Then
If connection.State = adStateOpen Then connection.Close
End If
Set connection = Nothing
Exit Sub
ErrorHandler:
If Not connection Is Nothing Then connection.RollbackTrans
MsgBox "致命的なエラーが発生しました。リビジョンは更新されていません。" & vbCrLf & _
"Error: " & Err.Description, vbCritical, "System Error"
Resume CleanUp
End Sub
''' <summary>
''' DBから次のリビジョン番号を取得する
''' </summary>
Private Function GetNextRevisionFromDB(conn As ADODB.Connection, baseName As String) As Integer
Dim rs As ADODB.Recordset
Dim sql As String
' ベース名が存在しなければ新規登録(Rev00)、存在すれば現在のRev + 1を返す
sql = "SELECT CurrentRevision FROM DrawingHistory WHERE BaseFileName = '" & baseName & "'"
Set rs = New ADODB.Recordset
rs.Open sql, conn, adOpenStatic, adLockReadOnly
If rs.EOF Then
' 新規登録用の初期SQLを別途用意するか、ここでINSERTを検討
' 今回は簡略化のため、既存レコードがある前提とする
GetNextRevisionFromDB = 1
Else
GetNextRevisionFromDB = rs!CurrentRevision + 1
End If
rs.Close
Set rs = Nothing
End Function
''' <summary>
''' DBの情報を更新する
''' </summary>
Private Sub UpdateDBRevision(conn As ADODB.Connection, baseName As String, newRev As Integer, fullName As String)
Dim sql As String
sql = "UPDATE DrawingHistory SET " & _
"CurrentRevision = " & newRev & ", " & _
"LastUpdatedAt = GETDATE(), " & _
"LastUpdatedBy = '" & Environ("USERNAME") & "', " & _
"FilePath = '" & Replace(fullName, "\", "\\") & "' " & _
"WHERE BaseFileName = '" & baseName & "'"
conn.Execute sql
End Sub
''' <summary>
''' ファイル名からベース名を取得(枝番 "_RevXX" を除去するロジック)
''' </summary>
Private Function GetBaseFileName(fullName As String) As String
' 実務では正規表現(VBScript.RegExp)を使用して、
' "_Rev[0-9]{2}" のパターンを正確に除去する設計にすべき
Dim pos As Integer
pos = InStrRev(fullName, "_Rev")
If pos > 0 Then
GetBaseFileName = Left(fullName, pos - 1)
Else
' 拡張子を除去
GetBaseFileName = Left(fullName, InStrRev(fullName, ".") - 1)
End If
End Function
4. チーフアーキテクトの視点:ここが「分かれ目」だ
このコードを単にコピペして終わらせるなら、君はただのプログラマーだ。この設計の背後にある「リスク管理」を理解してこそ、プロフェッショナルと言える。
① トランザクションの境界線(Atomicity)
コード内で `connection.BeginTrans` と `connection.CommitTrans` を使っている理由を理解しているか?
もし、`acadDoc.SaveAs` が成功した後に、ネットワーク瞬断で `UpdateDBRevision` が失敗したらどうなる?
DB上は「Rev01」のままだが、ファイルシステムには「Rev02」が存在する。この「不整合」こそが、自動化ツールにおける最大の敵だ。
「ファイル保存が成功したときのみ、DBを確定させる」。この順序を守ることが、堅牢な設計の鉄則だ。
② パフォーマンスの重み
`GetNextRevisionFromDB` 内で毎回 `Recordset` を開閉しているが、大量の図面をバッチ処理する場合は、Connectionオブジェクトを使い回す(Connection Poolingの概念)設計に昇格させる必要がある。
③ 拡張性:ファイルパスの正規化
`Replace(fullName, “\”, “\\”)` を見逃してはいけない。SQL Serverの文字列リテラルにおいて、バックスラッシュはエスケープ文字として扱われるリスクがある。DBに格納する際は、常にパスの正規化を意識せよ。
④ セキュリティ
本コードでは簡略化のために接続文字列を定数にしているが、実務では `App.config` や環境変数、あるいは暗号化された設定ファイルから取得すべきだ。ソースコードにパスワードをハードコードするのは、プロフェッショナルとして最も恥ずべき行為の一つである。
結びに代えて
自動化とは、単に「楽をするための手段」ではない。「人間が介在することによって発生する不確実性を、排除するためのエンジニアリング」だ。
SQL ServerとAutoCADを同期させるこのシステムは、図面管理の信頼性を劇的に向上させる。しかし、その信頼性は、エラーが起きたときに「システムがどう振る舞うか」を設計段階で決めておくことでしか担保できない。
さあ、次のプロジェクトでは、単なる「便利ツール」ではなく、「壊れないシステム」を構築してほしい。
