マクロの先へ:ADOとStreamオブジェクトで極める「バイナリデータ」の制御術
こんにちは。Access VBAの世界へようこそ。
マクロの記録を使って「ボタンを押したらフォームが開く」という仕組みを作れるようになったあなた。おめでとうございます。あなたはすでに、自動化の第一歩を力強く踏み出しました。
しかし、実務の現場、特に「企業の大規模なシステム」へと足を踏み入れると、一つの大きな壁にぶつかることがあります。それは、「画像やPDFといった、データベースの外にあるファイルを、どうやってデータベースの中に安全に格納し、取り出すか?」という問題です。
単なるテキストデータなら簡単です。しかし、バイナリデータ(BLOB:Binary Large Object)となると、話は別。単純な代入ではエラーになり、メモリを食いつぶしたり、データが壊れたりすることさえあります。
今日は、プロの現場で必須となる「ADO(ActiveX Data Objects)」と、その魔法の道具である「Streamオブジェクト」の使い方を伝授します。ここをマスターすれば、あなたは「マクロ使い」を卒業し、「エンジニア」へと進化できるはずですよ。
1. なぜ「DAO」ではなく「ADO」なのか? アーキテクチャの視点
Accessを使っていると、必ず「DAO」と「ADO」という言葉に出会います。ここを混同すると、設計の段階で迷子になってしまいます。
DAO (Data Access Objects)
Accessの「心臓部」です。`.accdb`ファイル(ローカルのAccessファイル)を操作するために最適化されています。非常に高速で、Accessのテーブル操作には最強の武器です。
ADO (ActiveX Data Objects)
「ネットワークの橋渡し役」です。SQL ServerやOracleといった、外部のサーバーにあるデータベースと通信するために設計されています。
「AccessからSQL Serverに接続して、画像データを保存したい」
この要望が出た瞬間、あなたが手に取るべき武器はADOです。ADOは、データの「流れ(Stream)」を扱う能力に長けており、巨大なバイナリデータを扱うための高度なインターフェースを提供してくれます。
2. 救世主 `ADODB.Stream`:バイナリを「流す」という考え方
バイナリデータ(画像やPDF)を扱うとき、初心者が陥る罠があります。それは「データを一つの塊(変数)としてメモリに載せようとすること」です。数MBの画像ならまだしも、高解像度の写真や重いPDFをそのまま変数に入れようとすると、PCのメモリを圧迫し、Accessがクラッシュする原因になります。
そこで登場するのが `ADODB.Stream` オブジェクト です。
イメージしてみてください。
バケツで水を運ぶのが「変数への代入」だとしたら、Stream(ストリーム)は「水道管」です。
データを一気に抱え込むのではなく、ストリーム(流れ)として、ファイルからデータベースへ、あるいはデータベースからファイルへと、スムーズに流していく。この「流れ」を制御する技術こそが、プロの仕事なのです。
3. 実践:SQL ServerのBLOBデータを読み書きする
それでは、具体的なコードを見ていきましょう。
※事前に、VBA画面の「ツール」→「参照設定」から 「Microsoft ActiveX Data Objects x.x Library」 にチェックを入れておいてくださいね。
【書き込み】ファイルをデータベースへ保存する
ファイル(画像やPDF)を読み込み、SQL Serverの `VARBINARY(MAX)` 型のフィールドに書き込む処理です。
''' <summary>
''' 指定したファイルをSQL ServerのBLOBフィールドに保存する
''' </summary>
''' <param name="filePath">保存したいファイルのフルパス</param>
''' <param name="recordID">更新対象のレコードID</param>
Public Sub SaveFileToDB(ByVal filePath As String, ByVal recordID As Long)
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim stm As ADODB.Stream
Dim strConn As String
' 1. 接続文字列の設定 (環境に合わせて書き換えてください)
strConn = "Provider=SQLOLEDB;Data Source=SERVER_NAME;Initial Catalog=DB_NAME;User ID=USER;Password=PASS;"
On Error GoTo ErrorHandler
' 2. ConnectionとRecordsetの初期化
Set conn = New ADODB.Connection
conn.Open strConn
' 3. Recordsetを開く (更新可能な状態で開くのがポイント)
Set rs = New ADODB.Recordset
rs.Open "SELECT ImageField FROM MyTable WHERE ID = " & recordID, conn, _
adOpenKeyset, adLockOptimistic
If Not rs.EOF Then
' --- ここからが Stream の出番です ---
Set stm = New ADODB.Stream
' ストリームをバイナリモードで開く
stm.Type = adTypeBinary
' ファイルを開いてストリームに読み込む
stm.Open
stm.LoadFromFile filePath
' 4. Recordsetのフィールドにストリームの内容を流し込む
' 直接バイなりデータを代入するのではなく、ストリームの塊を渡すイメージ
rs.Fields("ImageField").Value = stm.Read
' 5. 更新を確定させる
rs.Update
' 後片付け
stm.Close
Set stm = Nothing
MsgBox "保存が完了しました!", vbInformation
Else
MsgMsgBox "対象のレコードが見つかりませんでした。", vbExclamation
End If
CleanUp:
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
If Not conn Is Nothing Then If conn.State = adStateOpen Then conn.Close
Set rs = Nothing
Set conn = Nothing
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Description, vbCritical
Resume CleanUp
End Sub
【読み込み】データベースからファイルとして取り出す
逆に、DBに格納されたデータを、指定したパスにファイルとして書き出す処理です。
''' <summary>
''' DBのBLOBフィールドからデータを読み込み、ファイルとして保存する
''' </summary>
''' <param name="savePath">保存先のフルパス</param>
''' <param name="recordID">取得対象のレコードID</param>
Public Sub LoadFileFromDB(ByVal savePath As String, ByVal recordID As Long)
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim stm As ADODB.Stream
Dim strConn As String
strConn = "Provider=SQLOLEDB;Data Source=SERVER_NAME;Initial Catalog=DB_NAME;User ID=USER;Password=PASS;"
On Error GoTo ErrorHandler
Set conn = New ADODB.Connection
conn.Open strConn
Set rs = New ADODB.Recordset
rs.Open "SELECT ImageField FROM MyTable WHERE ID = " & recordID, conn, _
adOpenKeyset, adLockReadOnly ' 読み取り専用でOK
If Not rs.EOF Then
' データがNullでないかチェック
If Not IsNull(rs.Fields("ImageField").Value) Then
Set stm = New ADODB.Stream
stm.Type = adTypeBinary
stm.Open
' 1. Recordsetからバイナリデータを取得し、ストリームに書き込む
stm.Write rs.Fields("ImageField").Value
' 2. ストリームの内容をファイルとして保存
stm.SaveToFile savePath, adSaveCreateOverWrite
stm.Close
Set stm = Nothing
MsgBox "ファイルを出力しました: " & savePath, vbInformation
Else
MsgBox "データが空です。", vbExclamation
End If
End If
CleanUp:
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
If Not conn Is Nothing Then If conn.State = adStateOpen Then conn.Close
Set rs = Nothing
Set conn = Nothing
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Description, vbCritical
Resume CleanUp
End Sub
4. 陥りやすいエラーと、プロの回避術
ここまで読んで、「よし、やってみよう!」と思ったあなた。最後に、私が現場で何度も目にしてきた「落とし穴」を共有しておきます。これを知っているだけで、デバッグの時間は劇的に減ります。
① 「型が一致しません (Type Mismatch)」エラー
これは、`Stream.Type` を `adTypeBinary`(バイナリ)に設定し忘れたときに発生します。画像やPDFは「文字」ではなく「ビットの羅列」です。必ずバイナリモードであることを確認してください。
② 「レコードセットが更新できません」エラー
`rs.Open` をするときに、`LockType` を指定していないと、デフォルトでは「読み取り専用」になることがあります。データを書き込むときは、必ず `adLockOptimistic`(楽観的ロック)などを指定して、「書き込み権限」を確保しましょう。
③ メモリリーク(メモリの解放忘れ)
`Set stm = Nothing` や `rs.Close` を忘れると、Accessがどんどん重くなっていきます。特にループ処理の中でこれを行うと、あっという間にメモリを使い果たします。`ErrorHandler` を使って、「エラーが起きても必ず後片付け(CleanUp)へ行く」構造を作るのが、一流の書き方です。
最後に
いかがでしたか?
最初は難しく感じるかもしれません。しかし、`ADODB.Stream` という「水道管」の概念が掴めれば、あなたはデータの量に怯える必要はなくなります。
Access VBAは、単なる便利ツールではありません。正しく使えば、大規模な業務システムを支える強力なエンジンになります。
「マクロの記録」から一歩踏み出し、こうしたアーキテクチャを意識したコードを書けるようになれば、あなたのエンジニアとしての価値は飛躍的に高まります。
一歩ずつ、着実に進んでいきましょう。応援していますよ!
