徹底検証ADO

実用度は向上したのか

ADOとData Environmentオブジェクトの活用



初音 玲 HATSUNE, Akira



Visual Basicのデータアクセス方法は、DAO/Jetから始まって、RDO、ADOと進化してきた。この進化の道のりは、すべての情報を統一した方法でアクセスするという基本コンセプトを机上の空論から実践的方法へと昇華する道のりだった。その成果として、ADOではある程度は安定していろいろな接続先にアクセスできるようになりつつある。しかし、そのトレードオフとして、プロパティの設定値などがかなり煩雑になってしまった。その煩雑さを解消する方法として、Data Environmentオブジェクトを活用してみるのがよいだろう。

MDACって何だ!?

 Data Environmentオブジェクトを知るためには、まずはMDAC(Microsoft Data Access Components)について理解する必要があるだろう。MDACは、マイクロソフト社が提唱しているCOMベースのデータベースアクセス技術だ。MDACは、ADO(Active Data Objects)、RDS(Remote Data Services)、OLE DBそしてODBCなどで構成されている。MDACの目的は、さまざまな情報源(OracleやSQL Server、E-mailやWebページ)に対して共通のプログラミング手法を提供するとともに、それぞれの情報源に適したネイティブ接続を提供しようというもので、マイクロソフト社のUDA(Universal Data Access)の土台となる技術だ(図1)。

図1:UDAのアーキテクチャ
図1

 MDACは、1996年8月にMDAC 1.0としてプレリリースされ、1996年9月にMDAC 1.1として発表された。しかし、MDAC 1.1は必ずしも安定していたとは言えなかった。なんとか使い物になるようなったのは、RDSの前身であるADC(Advanced Data Connector)が搭載されるようになったMDAC 1.5からだろう。その後もMDACは現在に至るまで急速に機能を追加・拡張されていて、たとえば、Visual Basic 6.0になってイベントドリブン機能を導入してMDAC 2.0になったことも記憶に新しい。もちろんその後もさらにバージョンが上がり執筆時点の規約バージョンはMDAC 2.1、詳細バージョンはMDAC 2.1.2.4202.3だ。このようにMADCは現在でも機能追加だけではなく、バグフィックスも行なわれているので、最新のバージョンが公開されているhttp://www.microsoft.com/developer/data/を時々チェックするのが良いだろう。

ADOって何だ!?

 ADOはOLE DBに対する「ラッパー」として働き、OLE DBを直接利用するよりも使いやすいインターフェイスを提供することを目的としている。OLE DBがCOMインターフェイスであり、ODBCがAPIであるという違いはあるが、ADOとはODBC APIに対するDAO/JetやRDOに対応するものだ。
 しかし、インターフェイスの方式が違う以上にDAO/JetとADOには大きな違いがある。それは、ODBC APIを直接使うことでDAO/Jetを超えることができたのだが、ADOが提供しているインターフェイス(図2)がOLE DBと極めて高い親和性があるため、OLE DBを直接使う意味がほとんどないという点だ。時々、ADOを使わずにOLE DBを直接使って欲しいという開発依頼があるが、それはメインテナンス性を下げるだけの無駄な行為であると言えるだろう。

図2:ADOのオブジェクトモデル
図2

ADO MDって何だ!?

 MDACが進化を続けた結果、ADOでも共通化できない状況が生まれてきた。これは、ADOが共通化するために標準としているものがRDBMSイメージなので、RDBMSで表現できないものには対応できないためだ。その代表的なものが、OLAP(Online Analytical Processing)サーバーやOLAPキューブファイルなどの多次元データベースと呼ばれるものだ。そこで、ADOを拡張したADO MD(Multi Demention)が提唱された。
 ADO MDは多次元データベースの多次元キューブと呼ばれる独特なデータ管理手法に対応するオブジェクトを提供している。ただし、ADO MDと接続できるOLE DBは何でもよいというわけではなく、多次元データプロバイダ(MDP)だけである。つまり、ADO MDの拡張部分は、MDPにより提供されているということなのだ。このように、ADOの標準オブジェクトでカバーしきれないときにはオブジェクトを拡張してしまう(実際は、OLE DBプロバイダ側の拡張を反映する)ことすら許されているようだ。こうなってくるとコアな部分は共通でもトータルとしては別のインターフェイスになってしまうこともある。よってADOに求めてよいのは、OLE DBに対して簡単にCOM接続できるという点だけだろう。

OLE DBって何だ!?

 OLE DBはODBCが目指したものと同じことを目指した技術で、いろいろなデータを論理的に行と列で管理し、SELECT SQL文などでアクセスできるようなインターフェイスを提供している。
 OLE DBには、大きく分けて、
の3つの要素から構成されている(図3)。

図3:OLE DBの3要素
図3

OLE DBデータプロバイダ

 OLE DBデータプロバイダは、RDBMS、ISAM、スプレッドシートなど、実際にデータを格納しているコンポーネントが含まれていて、それぞれに対応したOLE DBプロバイダによりレコードセットという共通した仮想データ管理手段を提供する。

OLE DBサービスプロバイダ

 OLE DBサービスプロバイダは、OLE DBデータプロバイダのように実際にデータを保持しない。その代わり、クエリープロセッサやカーソルなどのようにデータを処理してデータコンシューマに返却する機能を含んでいる。そのため、データコンシューマから見ればデータプロバイダであり、データプロバイダから見ればデータコンシューマに見えるため、OLE DBの機能を説明するときに上手く説明しないと全体像が見えなくなってしまう原因になりかねない要素だ。

データコンシューマ

 データコンシューマは、OLE DBから返却されたデータを扱う部分で、ADOやアプリケーション、開発ツールなどが含まれる。

ADOを使う〜前準備〜

 Data Environmentオブジェクトの説明に入る前に、サンプルプログラムを参考にしてADOの理解を深めておく。そのためには、ADOで接続する先を用意しなければならない。

MSDEを用意する

 OLE DBプロバイダが用意されているデータストアはさまざまあるが、今回はMSDE(Microsoft Database Engine)を採用する。よって、今回のサンプルは接続先さえ変更すれば、そのままSQL Server 7で稼働する。
 MSDEは、Visual Studio 6.0のPlus Packに含まれているので、Visual Basic 6.0やVisual Studio 6.0のProfessional EditionやEnterprise Editionのユーザーならば無料で利用できる。手元にMSDE for Visual Studioがないときには、マイクロソフトのWebページ(http://www.microsoft.com/JAPAN/developer/vstudio/msde/download.htm)からダウンロードするか、CD-ROMを申し込むことができる。

サンプルデータを復元する

 サンプルデータを投入するには、T-SQLのストアドプロシージャを呼び出せばよい。付録CD-ROMに収録したVB04MSDE.MDFとVB04MSDE.LDFをMSDEのデータディレクトリにコピーし、T-SQLのsp_attach_dbを呼び出すSQL文を、

osql -u "sa" -i imp.sql
のように投入する。このときパスワードを聞いてくるが、インストール直後ならば、saのパスワードは「なし」なので、そのときは[Enter]キーをタイプする。

ADOを使う〜プログラミング〜

画面作成

 ADOを使うサンプルの画面仕様としては、“検索条件を指定してそれに一致したデータを表示してデータの追加・更新・削除できる”というものにする(図4)。

図4:サンプルADOの画面
図4

ADOのオブジェクトを作成する

 frmADOのForm_LoadイベントサブプロシージャでADOのコネクションオブジェクトとレコードセットオブジェクトを生成する(リスト1)。

リスト1:frmADOのForm_Loadイベントプロシージャ
Private Sub Form_Load()
  Show
  Me.MousePointer = vbHourglass
  Me.Refresh
' Connectionオブジェクトの生成
  Set mcnMSDE = New ADODB.Connection
' MSDEとの接続
  mcnMSDE.ConnectionString = "Provider=SQLOLEDB.1;" & _
                             "Persist Security Info=False;" & _
                             "User ID=sa;" & _
                             "Initial Catalog=VB04MSDESQL"
  mcnMSDE.Open
' Recordsetオブジェクトの生成
  Set mrsMSDE = New ADODB.Recordset
  Me.MousePointer = vbDefault
End Sub

 ADOコネクションオブジェクトは、RDBMS(MSDE)との接続を管理するオブジェクトなので当然ユーザーIDやパスワードを指定する。また、レコードセットオブジェクトは検索条件に一致したデータの集合(レコードセット)を管理するオブジェクトなので、実際に使用し始めるのは検索条件を指定してからになるため、この時点ではオブジェクトの作成のみとしている。

検索条件を指定する

 frmADOのcmdSearch_Clickイベントサブプロシージャで検索フォームを呼び出し、その結果を使ってADOレコードセットオブジェクトの使用を開始する(リスト2)。

リスト2:frmADOのcmdSearch_Clickイベントプロシージャ
Private Sub cmdSearch_Click()
  Dim strWhere    As String
  Dim blnRet      As Boolean
  Dim strSQL      As String
  Me.MousePointer = vbHourglass
  Me.Refresh
  blnRet = False
  Call frmSamp208a.psubDataSet(Me)
  frmSamp208a.Show vbModal, Me
  If frmSamp208a.pblnDataGet(strWhere) Then
    Me.MousePointer = vbHourglass
    Me.Refresh
' レコードセットを作り直す
    If mrsMSDE.Fields.Count > 0 Then
        mrsMSDE.Close
    End If
    strSQL = "SELECT ISBN,書名,発行日,価格,ページ数,備考 " & _
             "FROM ISBN "
    If strWhere <> "" Then
      strSQL = strSQL & "WHERE " & strWhere
    End If
    strSQL = strSQL & " ORDER BY ISBN"
    mrsMSDE.Open strSQL, mcnMSDE, adOpenDynamic, _
                 adLockOptimistic
    Call cmdFirst_Click
' コマンドボタンを使用可能にする
    blnRet = True
    cmdFirst.Enabled = True
    cmdPrev.Enabled = True
    cmdNext.Enabled = True
    cmdLast.Enabled = True
    cmdAddNew.Enabled = True
    cmdUpdate.Enabled = True
    cmdDelete.Enabled = True
  End If
  If Not blnRet Then
  ' コマンドボタンを使用不可にする
    cmdFirst.Enabled = False
    cmdPrev.Enabled = False
    cmdNext.Enabled = False
    cmdLast.Enabled = False
    cmdAddNew.Enabled = False
    cmdUpdate.Enabled = False
    cmdDelete.Enabled = False
  End If
  Me.MousePointer = vbDefault
End Sub

 リスト2の中で、

If mrsMSDE.State <> adStateClosed
としているのは、ADOレコードセットのCloseメソッドの実行が必要かを判断するためだ。この判断なしで闇雲にCloseメソッドを発行するとプログラム起動直後に[検索]ボタンを左クリックしたときには、ADOレコードセットオブジェクトは、当然生成されただけなのでCloseメソッドでエラーが発生してしまうからだ。

レコードを移動する

[先頭][前][次][最終]ボタンを左クリックすれば、それぞれ先頭レコード、ひとつ前のレコード、ひとつ後のレコード、最終レコードに移動する。そのためには、Clickイベントサブプロシージュの中でADOレコードセットオブジェクトのメソッドを呼び出せばよい。

レコードを追加する

 新規にレコードを追加するには、
  1. 追加するデータを画面に表示
  2. AddNewメソッドの実行(リスト4)

    リスト4:レコードの追加
    Private Sub cmdAddNew_Click()
      Dim iintloop        As Integer
      On Error GoTo errClick:
      Me.MousePointer = vbHourglass
      Me.Refresh
      mrsMSDE.AddNew
      For iintloop = 0 To 5
        If Trim$(txtISBN(iintloop).Text) <> "" Then
          mrsMSDE.Fields(iintloop).Value = _
           Trim$(txtISBN(iintloop).Text)
        Else
          mrsMSDE.Fields(iintloop).Value = Null
        End If
      Next
      mrsMSDE.Update
    exitClick:
      On Error Resume Next
      Me.MousePointer = vbDefault
      Exit Sub
    errClick:
      MsgBox Error$, vbOKOnly + vbExclamation, _
             App.Title
      Resume exitClick:
    End Sub
    

  3. Fieldsコレクションへの転機
  4. Updateメソッドの実行
という手順を踏む。

レコードを更新する

 レコードを更新するときの手順は追加のときとほとんど同じだ。ただし、DAOやRDOにあった(AddNewメソッドに対応する)Editメソッドの実行は必要ない。更新対象のデータを表示して値を編集したら[更新]ボタンを左クリックしてcmdEdit_Clickサブプロシージャを実行する。

リスト5:レコードの更新
Private Sub cmdUpdate_Click()
  Dim iintloop        As Integer
  On Error GoTo errClick:
  Me.MousePointer = vbHourglass
  Me.Refresh
  For iintloop = 0 To 5
    If Trim$(txtISBN(iintloop).Text) <> "" Then
      mrsMSDE.Fields(iintloop).Value = _
       Trim$(txtISBN(iintloop).Text)
    Else
      mrsMSDE.Fields(iintloop).Value = Null
    End If
  Next
  mrsMSDE.Update
exitClick:
  On Error Resume Next
  Me.MousePointer = vbDefault
  Exit Sub
errClick:
  MsgBox Error$, vbOKOnly + vbExclamation, _
         App.Title
  Resume exitClick:
End Sub

レコードを削除する

 レコードを削除するときの手順は、削除対象のデータを表示して[削除]ボタンを左クリックし、Deleteメソッドを実行すればよい。その後、カレントレコードを移動して削除データを画面から消去する。

リスト6:レコードの論理削除
Private Sub cmdDelete_Click()
  On Error GoTo errClick:
  Me.MousePointer = vbHourglass
  Me.Refresh
  mrsMSDE.Delete
  mrsMSDE.MoveNext
  If mrsMSDE.EOF Then
    mrsMSDE.MovePrevious
  End If
  Call subDispSet
exitClick:
  On Error Resume Next
  Me.MousePointer = vbDefault
  Exit Sub
errClick:
  MsgBox Error$, vbOKOnly + vbExclamation, _
         App.Title
  Resume exitClick:
  Resume Next
End Sub

Data Environmentって何!?

 ADOを直接使うことで、大抵の要望を実現するためのコードを記述することができる。それは取りあえず繋ぐだけでもコードの記述が必要だということになる。たとえば、ADOコネクションオブジェクトのOpenメソッドを使うときに必要なOLE DBプロバイダの指定などは、とても煩雑なものだ。というのも、ADOというのは自分自身で使うパラメータ以外は、OLE DBプロバイダにそのまま渡すために、ADOのオブジェクトに対するプロパティ値やメソッドのパラメータ値などが特定しづらいことが原因になっている。これは、ADOコネクションオブジェクトだけではなく、その他のADOのオブジェクトにも言えることだ。そのような煩雑さを解消するために、Data Environmentオブジェクトに定型的な部分を定義し、それを呼び出すことでADOを使ったときと同じような機能を実装できる。

Data Environmentを定義する

 Visual BasicでData Environmentオブジェクトを使うためには、[プロジェクト]から[その他のActiveXデザイナ]-[Data Environment]メニューを左クリックし、プロジェクトにData Environmentオブジェクトを新規追加する(図5)。

図5:Data Environmentのプロジェクトへの追加
図5

コネクションオブジェクトの追加

 Data Environmentオブジェクトを新規追加してデザイナが起動されたら、MSDEとのコネクション定義を覚えさせるためにコネクションオブジェクトを追加する。そして、OLE DBプロバイダの指定とそのプロバイダに応じた接続情報を設定する(図6)。設定値のほとんどは、ドロップダウンリストから設定でき、また実際にMSDEに接続してその情報を取得してくる。そのため設定間違いも解消できるし煩雑さも解消できるだろう。

図6:コネクションオブジェクトの設定
図6

コマンドオブジェクトの追加

 MSDEとのコネクションが定義できたら、次はデータを取得するためのコマンドを定義する。コマンドオブジェクトは、コネクションオブジェクトの下に追加することになる(図7)。コマンドオブジェクトの定義は、コネクションオブジェクトの定義以上に効果的かもしれない。それは、SQLビルダを使って実際のデータを使いながらSQLを組み立てていけるからだ(図8)。

図7:コマンドの追加
図7

図8:コマンドのプロパティの指定
図8

Data Environmentを使う

画面仕様

 Data Environmentを使うサンプルの画面仕様は、サンプルADOと同じ仕様にする。

Data Environmentオブジェクトを作成

 Visual BasicプロジェクトにData Environmentオブジェクトが含まれているので、frmADOのForm_Loadイベントサブプロシージャなどで、改めてオブジェクトを作成する必要はない。

検索条件を指定する

 frmADOのcmdSearch_Clickイベントサブプロシージャで検索フォーム(frmSamp208a)を呼び出し、その結果を使ってData Environmentレコードセットオブジェクトの使用を開始する。Data Environmentオブジェクトはコマンドに対応したレコードセットを自動で作成するので、dcmdMSDEコマンドに対応するレコードセットはrsdcmdMSDEになる(リスト7)。

リスト7:レコード検索
Private Sub cmdSearch_Click()
  Dim strWhere    As String
  Dim blnRet      As Boolean
  Dim strSQL      As String
  Me.MousePointer = vbHourglass
  Me.Refresh
  blnRet = False
  Call frmSamp208a.psubDataSet(Me)
  frmSamp208a.Show vbModal, Me
  If frmSamp208a.pblnDataGet(strWhere) Then
    Me.MousePointer = vbHourglass
    Me.Refresh
  ' レコードセットを作り直す
    If denvMSDE.rsdcmdMSDE.State <> adStateClosed Then
      denvMSDE.rsdcmdMSDE.Close
    End If
    strSQL = "SELECT ISBN,書名,発行日,価格,ページ数,備考 " & _
             "FROM ISBN "
    If strWhere <> "" Then
      strSQL = strSQL & "WHERE " & strWhere
    End If
    strSQL = strSQL & " ORDER BY ISBN"
    denvMSDE.rsdcmdMSDE.Open strSQL, , adOpenDynamic, _
                             adLockOptimistic
    Call cmdFirst_Click
  ' コマンドボタンを使用可能にする
    blnRet = True
    cmdFirst.Enabled = True
    cmdPrev.Enabled = True
    cmdNext.Enabled = True
    cmdLast.Enabled = True
    cmdAddNew.Enabled = True
    cmdUpdate.Enabled = True
    cmdDelete.Enabled = True
  End If
  If Not blnRet Then
  ' コマンドボタンを使用不可にする
    cmdFirst.Enabled = False
    cmdPrev.Enabled = False
    cmdNext.Enabled = False
    cmdLast.Enabled = False
    cmdAddNew.Enabled = False
    cmdUpdate.Enabled = False
    cmdDelete.Enabled = False
  End If
  Me.MousePointer = vbDefault
End Sub

レコードを移動する

[先頭][前][次][最終]ボタンを左クリックすれば、それぞれ先頭レコード、ひとつ前のレコード、ひとつ後のレコード、最終レコードに移動する。そのためには、Clickイベントサブプロシージュの中でData Environmentレコードセットオブジェクトのメソッドを呼び出せばよい。たとえば、[先頭]ボタンを左クリックしたら、リスト8が実行される。

リスト8:[先頭]ボタンで呼び出されるプロシージャ
Private Sub cmdFirst_Click()
  On Error GoTo errClick:
  Me.MousePointer = vbHourglass
  Me.Refresh
  denvMSDE.rsdcmdMSDE.MoveFirst
  If Not denvMSDE.rsdcmdMSDE.BOF Then
    Call subDispSet
  End If
exitClick:
  On Error Resume Next
  Me.MousePointer = vbDefault
  Exit Sub
errClick:
  MsgBox Error$, vbOKOnly + vbExclamation, App.Title
  Resume exitClick:
End Sub

レコードを追加・更新・削除する

 レコードの値を変更する場合もレコードの移動と同様に、Data Environmentレコードセットのメソッドを実行する。使い方は、ADOレコードセットのメソッドとまったく同一だ。たとえば、レコードの追加は、リスト9のようなコードになる。

リスト9:レコード追加
Private Sub cmdAddNew_Click()
  Dim iintloop        As Integer
  On Error GoTo errClick:
  Me.MousePointer = vbHourglass
  Me.Refresh
  denvMSDE.rsdcmdMSDE.AddNew
  For iintloop = 0 To 5
    If Trim$(txtISBN(iintloop).Text) <> "" Then
      denvMSDE.rsdcmdMSDE.Fields(iintloop).Value = _
        Trim$(txtISBN(iintloop).Text)
    Else
      denvMSDE.rsdcmdMSDE.Fields(iintloop).Value = Null
    End If
  Next
  denvMSDE.rsdcmdMSDE.Update
exitClick:
  On Error Resume Next
  Me.MousePointer = vbDefault
  Exit Sub
errClick:
  MsgBox Error$, vbOKOnly + vbExclamation, App.Title
  Resume exitClick:
End Sub

ADOとData Environment

 このようにADOと同様に使えるのは、Data Environmentオブジェクトの構造がADOのオブジェクト構造にとてもよく似ているからだ(図9)。そして、ADOのオブジェクトに対応したData Environmentオブジェクトには、同じようなイベントやプロパティが存在する(図10)。よって、ADOで実現されている非同期実行やイベントドリブンなどもData Environmentオブジェクトで使うことができる。

図9:Data Environmentのオブジェクトモデル
図9

図10:Data Environmentのイベント
図10

Data Environmentをデータコントロールとして活用する

 Visual Basicでは、ある一定の約束事にしたがって作成したオブジェクトをデータソースとしてコントロールに連結することができる。Data Environmentオブジェクトは、データソースとして指定することができるだけではなく、もっと手軽にデータアクセス画面を作る手段を提供している。たとえば、Data Environmentコマンドオブジェクトをフォームオブジェクトにドラッグ&ドロップすれば自動的にコントロールが配置され、またData Environmentオブジェクトとの連結も完了するのだ(図11)。

図11:コマンドのフォームへのドラッグ&ドロップ
図11

 この、何もプログラミングしていないフォームだけの状態で実行しても、ログイン画面が自動的に表示され、きちんとフォーム画面も表示できる(図12)。あとは、レコードを移動する手段や更新する手段を用意するだけで済むという簡単さだ。このとき注意しなければならないのは、コントロール名がコマンドの要素名(RDBMSで言えば列名)になっていることだ(図13)。

図12:Data Environmentオブジェクトと連結したときの実行結果
図12

図13:自動的に生成されたコントロール名
図13

簡単になったことのトレードオフ

 Data Environmentオブジェクトをフォームにドロップする手法は、画面を手軽に作成する手段ではあるが、それに対するトレードオフも当然存在する。まず第1点は、データコントロールと連結したとき共通の問題だが、フォームの初期表示時にデータを取得するために表示に時間がかかってしまうという点。もうひとつはコマンドを変更してData Environmentレコードセットの再作成したときに画面との連結が外れてしまうという点だ。再作成によりオブジェクト自体が管理するレコードセットの状態は更新されるが、それ以降画面にレコードの状態が自動表示されることはない。そのため、サンプルでもFilterプロパティにより擬似的に実現している(リスト10)。

リスト10:Filterプロパティによってレコードの状態を自動表示する
Public Sub psubFind(ByRef rrsRec As ADODB.Recordset,
                    ByRef rstrWhere As String)
  rrsRec.MoveFirst
  rrsRec.Filter = rstrWhere
End Sub

さいごに

 ADO 1.0が登場したころは、速度も遅くまた安定して動作もしなかったため、たとえSQL Serverを接続先に選択したとしてもADOを採用すべきではないと感じた。また、Visual Basic 6.0が発売された直後のADO 2.0も安定はしていたが速度は遅かった。しかし、それから1年以上たってADO 2.1が登場したり、マシン性能が向上したこともあってか、なかなか実用的なものになってきたように思う。しかし、相変わらずJet用OLE DBプロバイダやOracle用OLE DBプロバイダを選択したときには挙動不審となるときがある。しかし、本来の目的であるSQL Serverとの接続には安心して選択してもよい時期になってきたのかもしれない。

■動作確認環境
ThinkPad 240 2609-31J
  Windows 98 SE  4.10.2222A
  IE5 5.00.2614.3500
  Office 2000 Developer
  Visual Basic 6.0 (SP3)


VB Magazine ライブラリ| Visual Basic WorkGroup
int21 ホームページ| PCDN ホームページ

PCDN
Copyright (c) 1998 int21 CorporationAll Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp