【極限のVBA】Project VBAとSQL Serverを同期する双方向データエンジンの設計思想
多くのエンジニアが「MS Projectはブラックボックスだ」と嘆き、泥臭いCSVインポートに甘んじている。しかし、Project VBAの背後にあるDOM(Document Object Model)と、SQL Serverのセットベースな思想を調和させれば、真のエンタープライズ・プロジェクト管理システムが構築できる。
本稿では、プロジェクト保存時(`ProjectBeforeSave`)をトリガーに、タスク情報を非同期的に同期させる双方向エンジンの設計論を解く。
—
1. アーキテクチャの核心:イベント駆動の罠と回避策
Project VBAのイベントは強力だが、デッドロックやスタックオーバーフローの温床でもある。特に`ProjectBeforeSave`イベント内で重いDB処理を行うと、アプリケーションがフリーズする。
極意:
同期処理は、VBA側でデータ(ID、GUID、開始日、終了日など)を抽出して一時テーブルに書き出し、その後に`Shell`関数でバックグラウンド実行されるVB.NETのコンソールアプリ(あるいはCOM経由)へ非同期処理を委譲せよ。VBAは「データの吸い出し」と「UIのロック」に専念する。
—
2. SQL Server同期の実装コード(VBA側)
まずは、Projectのメモリを解放しながら効率よくタスクを列挙する手法だ。`For Each`ループによるコレクションの巡回は、巨大プロジェクトではパフォーマンスを著しく低下させる。インデックスアクセスを基本とせよ。
‘ プロジェクト保存イベント内でのデータ抽出例
Private Sub Project_BeforeSave(ByVal pj As Project, ByRef Cancel As Boolean)
Dim tsk As Task
Dim conn As Object
‘ メモリ最適化:不要なオブジェクトは即座にNothingへ
‘ ADODB.Connectionは遅延バインディングで接続を限定的にする
Set conn = CreateObject(“ADODB.Connection”)
conn.Open “Provider=SQLOLEDB;Data Source=SERVER;Initial Catalog=DB;Integrated Security=SSPI;”
‘ タスクIDとGUID(一意キー)を同期する
For Each tsk In pj.Tasks
If Not tsk Is Nothing Then
If tsk.Summary = False Then
‘ SQL Serverへの更新命令(Stored Procedure経由が定石)
conn.Execute “EXEC sp_UpdateTaskSync ‘” & tsk.GUID & “‘, ‘” & tsk.Name & “‘, ” & tsk.Duration
End If
End If
Next tsk
conn.Close
Set conn = Nothing
End Sub
—
3. レガシー環境における「メモリリーク」の撃退法
VBAの`Project`オブジェクトは、COMマーシャリングの過程でメモリリークを引き起こしやすい。特に、`Task`オブジェクトをループ内で何度も参照すると、ポインタの残骸が解放されないケースがある。
- 明示的な解放: `Set tsk = Nothing` をループの終端で必ず実行せよ。
- Windows APIの活用: 巨大なプロジェクトデータを外部プロセスへ受け渡す際、`GlobalAlloc`等のAPIでメモリを確保し、メモリマップドファイルを使用することで、データ転送のオーバーヘッドを劇的に減らすことができる。
—
4. SQL Server側の変更を「プル」する際の同期整合性
DB側で変更された納期やステータスをProject側に反映させるには、`Project.Tasks.Add`ではなく、既存の`Task.GUID`をキーにプロパティを上書き更新(Overwrite)するのが鉄則だ。
解決すべき壁:
Project側でチェックアウト中の競合をどう防ぐか?
これには、SQL Server側に`SyncStatus`テーブルを持たせ、Project起動時に`Project_Open`イベントで「最終同期日時」を比較し、差分のみをパッチ適用する「インクリメンタル・シンク」を実装すること。
‘ 差分同期のためのロジックサンプル
Public Sub SyncFromSQL(pj As Project)
Dim rs As Object
Set rs = conn.Execute(“SELECT FROM TaskUpdates WHERE LastModified > ‘” & GetLastSyncDate() & “‘”)
Do While Not rs.EOF
‘ GUIDを基にProject上の該当タスクを特定
Dim tsk As Task
Set tsk = FindTaskByGUID(pj, rs(“GUID”).Value)
If Not tsk Is Nothing Then
tsk.Start = rs(“StartDate”).Value
tsk.Finish = rs(“FinishDate”).Value
End If
rs.MoveNext
Loop
End Sub
—
5. 総括:システム間連携のプロフェッショナルへ
この手法を導入した現場では、Excelや手入力による「データの乖離」は撲滅される。重要なのは、「ProjectはあくまでUIであり、真のマスターデータはSQL Serverにある」という意識を徹底することだ。
- パフォーマンスを求めるなら: すべての処理をADODB.Recordsetのバッチ更新で行い、ループ回数を物理的に減らせ。
- 安定性を求めるなら: COM例外処理を必ず実装し、同期失敗時に「ロールバック」できる状態を確保せよ。
VBAは古くない。ただ、使い手のアーキテクチャ設計力が試されているに過ぎない。君が構築するその同期エンジンが、プロジェクトマネジメントの新たな標準となることを期待している。
