脱・ハードコーディング。ADOのFieldsコレクションを極め、真の「汎用データインポートエンジン」を構築せよ
現場のエンジニアが陥りがちな最大の罠。それは、「仕様が変わるたびにコードを書き換える」という、再利用性の欠片もない実装だ。
「Aテーブルへのインポートはこう書く」「Bテーブルならこう書く」……。もし君が、`rs.Fields(“CustomerName”).Value = …` といったフィールド名を直接コードに書き込んでいるとしたら、君が作っているのは「ツール」ではない。単なる「使い捨てのスクリプト」だ。
真に価値のある業務自動化エンジニアが作るべきは、データの構造(メタデータ)を動的に解釈し、どんなテーブル構造にも適応できる「インポートエンジン」である。
本稿では、ADO(ActiveX Data Objects)の`Fields`コレクションを自在に操り、テーブル構造に依存しない、堅牢で汎用的なインポートプロシージャの設計思想と実装を伝授する。
1. なぜDAOではなくADOなのか? アーキテクチャの選択
Access開発において、DAO(Data Access Objects)はローカルなAccessエンジンを操作する上で極めて高速だ。しかし、「外部データとの連携」や「汎用的なデータ移動」を主眼に置くなら、選択肢は迷わずADOになる。
ADOが汎用ツールに適している理由
1. メタデータの抽象化: ADOの`Fields`コレクションは、接続先のデータソース(Excel, SQL Server, CSV, Accessなど)が何であれ、共通のインターフェースでフィールド情報にアクセスできる。
2. デカップリング(疎結合): ソース(送り元)とターゲット(送り先)の構造を、フィールド名という「共通言語」だけで紐付ける設計が可能になる。
3. トランザクション制御の柔軟性: 複数のデータソースをまたぐ、あるいは複雑なエラーリカバリを伴う処理において、ADOの接続オブジェクトによるトランザクション管理は極めて強力だ。
2. 設計思想:メタデータ駆動型インポート
ハードコーディングを排除するための鍵は、「フィールド名による動的マッピング」にある。
インポート処理のアルゴリズムを以下のように定義する。
1. ソース(Source)のレコードセットを走査する。
2. 各フィールドについて、その名前(Name)を取得する。
3. ターゲット(Target)のレコードセット(またはテーブル構造)を走査し、同じ名前のフィールドが存在するかを判定する。
4. 存在する場合のみ、値を流し込む。
この設計により、ソースに余分な列があっても、ターゲットに足りない列があっても、プログラムを一行も書き換えることなく動作し続ける。
3. プロダクション・コード:汎用インポートエンジンの実装
以下に、実務でそのまま利用可能なレベルの堅牢なコードを示す。単なるループ処理ではない。「トランザクションによる整合性確保」と「エラーハンドリングによるロールバック」を組み込んだ、プロフェッショナルな設計だ。
Option Compare Database
Option Explicit
''' <summary>
''' 汎用データインポートプロシージャ
''' ソースレコードセットからターゲットテーブルへ、フィールド名が一致するデータを転送する。
''' </summary>
''' <param name="conn">接続済みのADO.Connectionオブジェクト</param>
''' <param name="rsSource">データ供給元となるADO.Recordset</param>
''' <param name="strTargetTableName">書き込み先となるAccessテーブル名</param>
Public Sub GenericDataImporter(ByRef conn As ADODB.Connection, _
ByRef rsSource As ADODB.Recordset, _
ByVal strTargetTableName As String)
Dim rsTarget As ADODB.Recordset
Dim fldSrc As ADODB.Field
Dim fldTgt As ADODB.Field
Dim isMatched As Boolean
Dim rowCount As Long
' 1. エラーハンドリングの開始
On Error GoTo ErrorHandler
' 2. トランザクションの開始(全件成功か、全件失敗かの原子性を担保)
conn.BeginTrans
Debug.Print "--- Import Process Started ---"
' 3. ターゲットレコードセットのオープン
' ※SQLを使用することで、インポート先の制御を容易にする
Set rsTarget = New ADODB.Recordset
rsTarget.Open "SELECT * FROM [" & strTargetTableName & "]", conn, _
adOpenKeyset, adLockOptimistic
' 4. ソースデータの走査
If rsSource.EOF Then
Debug.Print "Source is empty. Nothing to import."
GoTo CleanUp
End If
Do Until rsSource.EOF
rsTarget.AddNew
' 5. フィールドのマッピング(ここが設計の肝)
' ソースの各フィールドに対してループ
For Each fldSrc In rsSource.Fields
isMatched = False
' ターゲット側に同名のフィールドが存在するか確認
' ※本来は事前にターゲットのフィールドリストをキャッシュしておくとより高速
On Error Resume Next
Set fldTgt = rsTarget.Fields(fldSrc.Name)
If Err.Number = 0 Then
isMatched = True
End If
On Error GoTo ErrorHandler ' エラーハンドリングを戻す
' 一致した場合のみ、値を転送(Null値の考慮も自動で行われる)
If isMatched Then
' 型不一致によるエラーを避けるため、値の代入は慎重に行う
' 実務ではここで型変換ロジックを挟むことも検討すべき
fldTgt.Value = fldSrc.Value
End If
Next fldSrc
rsTarget.Update
rowCount = rowCount + 1
rsSource.MoveNext
Loop
' 6. コミット(すべての処理が正常に完了した場合のみ)
conn.CommitTrans
Debug.Print "Successfully imported " & rowCount & " rows to " & strTargetTableName
CleanUp:
' オブジェクトの解放(メモリリーク防止)
If Not rsTarget Is Nothing Then
If rsTarget.State = adStateOpen Then rsTarget.Close
Set rsTarget = Nothing
End If
Exit Sub
ErrorHandler:
' 7. ロールバック(エラー発生時は、それまでの処理をすべて破棄)
If Not conn Is Nothing Then
If conn.State = adStateOpen Then
conn.RollbackTrans
Debug.Print "Transaction Rolled Back due to Error: " & Err.Description
End If
End If
' エラー情報の通知
MsgBox "Critical Error during import: " & Err.Description, vbCritical, "Import Failed"
Resume CleanUp
End Sub
4. アーキテクトの視点:運用における3つの急所
このコードを現場に投入する際、君が意識すべき「プロの作法」が3つある。
① トランザクションの重み
上記のコードでは `conn.BeginTrans` を使用している。これは、1万件のデータのうち9,999件目がエラーになったとき、「中途半端にデータが入った状態」を防ぐためだ。
「データが重複している」「一部だけ入っている」といった不整合は、後続の業務プロセスを破壊する致命的なバグになる。「All or Nothing」。これがデータエンジニアの鉄則だ。
② パフォーマンスのトレードオフ
上記のコードでは、各レコードごとに `rsTarget.Fields(fldSrc.Name)` を評価している。これは非常に柔軟だが、数百万件規模のデータではオーバーヘッドになる。
もしパフォーマンスがボトルネックになることが予見されるなら、「インポート前にターゲットのフィールド名一覧を `Collection` や `Dictionary` オブジェクトに格納しておく」という最適化を施せ。
③ 型の不一致(Type Mismatch)への備え
`fldTgt.Value = fldSrc.Value` は、型が一致していれば完璧に動作する。しかし、ソースが「文字列の ‘123’」で、ターゲットが「数値型」の場合、ADOはエラーを吐く。
実務レベルのツールにするなら、フィールドの `Type` プロパティを比較し、必要に応じて `CInt` や `CDbl`、あるいは `Format` 関数による型変換を挟む「型変換レイヤー」を実装することを強く推奨する。
結びに代えて
「動かないコード」を書く者は、仕様書に従う。
「変化に強いコード」を書く者は、データ構造を理解する。
`Fields`コレクションを単なる値の入れ物としてではなく、「データのメタデータを解釈するための窓口」として捉えることができたとき、君の書くプログラムは、単なる自動化ツールから、真の「エンジンの設計」へと昇華する。
次なるプロジェクトでは、ハードコーディングの誘惑を断ち切り、この設計思想を実装してみせよ。
