Visual Basic 4.0 Enterprise Editon
クライアント/サーバー型RDBMS用ミドルウェアオブジェクト
Remote Data Objects
Visual Basic Enterprise Editionには、リモートOLEオートメーション、Remote Data Objects(RDO)、Visual Source Safeといったネットワークを前提とした機能が追加されている。前回のリモートOLEオートメーションに引き続き、
今回はRemote Data Objects について紹介しよう。
酒井 法雄
今、デベロッパーは、いろいろな点でVisual Basic 4.0に注目している。32bit対応、OLE 2.0対応、OLEオートメーション、データアクセス機能と、実に多彩な機能を持って登場したVisual Basic 4.0であるから、これも当然である。しかし、その中でもっとも注目されているのがデータアクセス機能、それもクライアント/サーバー型RDBMSへのアクセスであろう。
MicrosoftのBasic製品にデータアクセス機能が搭載されるのは、これが初めてのことではない。古くはMS-DOS用のMicrosoft BASIC 7.0(日本語版あり)にもISAMのライブラリが含まれていた。そして、ついに日本語版が発売されなかったVisual Basic 3.0にも、Accessの.MDB形式のファイルを扱うことのできるデータベースエンジンJetが搭載されていた。これは実際には、Access 1.1とまったく同じエンジンが搭載されており、データを扱うためのオブジェクトや構文に違いがあるというだけであった。Access 2.0が発売されてからは、Office Developers Kitに含まれた新しいエンジンやCrystal Reportsを使って、Access 2.0形式のファイルに対応することもできた。
そして、JetからODBCのレイヤーを介して、リモートマシンにあるRDBMSにアクセスすることができたのである。実は、我が国でも発売されていたVisual Basic 2.0にもODBCへの接続機能はあったものの、そのインプリメントレベルは究めて低く、対応したドライバがなかった。このため、GUIのフロントエンド開発ツールとして高い評価を得ていたVisual Basicではあったが、現実的にはクライアントツールとはなりえなかったのである。
Visual Basicに注目していたデベロッパーは、Visual Basic 3.0でなんとかクライアント/サーバー型RDBMSにアクセスしようとしたものも少なくなかったが、動かないVisual Basic 2.0をあきらめてAccessを使うというケースも多く見られた。
しかし、Accessは米国ではビギナー向けの商品である。デベロッパー向けにはFox Proという製品があるのだ。Fox Proが発売されなかった我が国では、いかにもAccessが万能のRDBMSアプリケーションであるかのように語られていたし、MSKKもそのように売ってきたが、本来我が国で売るなら、住所録の管理をして年賀状と暑中見舞いにハガキを印刷するのがせいぜいのものだったのである。そのAccessも2.0となって、Fox Proのテクノロジーを取り入れ、だいぶパフォーマンスがアップしたものの、クライアント/サーバー型RDBMSのフロントエンドとなるには、まだまだ不十分な性能であった。また、Accessはとっつきはいいものの、使い込もうとすると突然難しくなるという壁がある。さらに、ある程度DBが大きくなってくると、劇的にパフォーマンスが悪くなるといったこともあった。
そういうわけで、流行りのダウンサイジングでクライアント/サーバー環境を構築しようとしていたSIや企業は、実用に耐えうるクライアントツールの出現を待ち続けていたものである。昨年は、まさにそういった状況であった。Oracle Power Objects、BORLAND Delphi、Lotus Notes ViP、CA-Visual Objectsと、次々とクライアントツールが発売されたが、出揃うかどうかというタイミングで、OSは32bitのWindows 95になってしまった。Windows 95はネットワークに標準対応したOSであり、従来のLanManagerを使う方法に比較して、劇的にクライアントOSとしての素性はいい。しかし、これらのツールはいずれも16bitであったから、今度は32bitのクライアントツールを望む声が出てきたわけだ。
ここで一言述べておくならば、Windows 95は32bit APIや32bitドライバはあるものの、構造的には16bitのAPIが残っており、そこが足を引っ張って32bitアプリケーションは高速に動作しない。むしろ、よくできた16bitアプリケーションの方が高速なのである。ウソだと思うなら、Windows 3.1をインストールしなおして、16bitのVisual Basicやアプリケーションを動かしてみればよい。驚くほどスカスカ動く。もちろん、Windows NTならば、32bitのOfficeもVisual Basicも、ずっと気分よく高速に動作する。
しかし、人の心はそうはいかない。Microsoftが32bit OSだと公言する以上、32bitのツールが欲しくなるのは当然だ。私も仕事上Windows 95を使わなくてはならないので、NT 3.51と併用しているが、Windows 95は速度の点でも、安定度の点でも、とてもではないが現状では仕事に使うことはできない。それでも、ついOffice 95などをインストールしてしまい、今度はWordのバグで原稿を飛ばして、この原稿も大変に緊縛した状況で書いている。やはり、こんなものは使わなければよかった。
というような愚痴はこれくらいにして、そういう実に巧みなタイミングで発売されたのが、Visual Basic 4.0である。そして、タイミングだけではなく、その内容もよく考えられている。その最たるものが今回紹介するクライアント/サーバー型RDBMS専用のオブジェクトであるRDO(Remote Data Objects)であろう。
Visual BasicからリモートRDBMSにアクセスするには、いくつかの方法がある。しかし、従来からあるJetエンジンを経由するDAO (Data Access Objects)も、新しいRDOも、いずれもODBCを使う。
そもそもODBCとはなんだろうか?
ODBCは、Open
DataBase Connectivityの略である。ODBCの構造を図に示す。
ODBCとはMicrosoftが音頭を取って作られたミドルウェアの規格である。ミドルウェアとは、クライアントアプリケーションからサーバーRDBMSにアクセスするためのソフトウェアである。従来は、いろいろなツールで作成されたアプリケーションからサーバーにアクセスするための手段は、RDBMSメーカーが独自に作ったAPIセットを使っていた。OracleのSQL*Net、SybaseのDB Libraryなどがこれに当たる。しかし、こういったAPIセットは極めて低レベルであるため、開発にかかるコストや時間が大きかった。そこで、アプリケーションからもっとラクにアクセスするための、高級なソフトウェアが望まれたのである。これをミドルウェアと呼んでいる。しかし、これも各社独自のものであった。
ODBCは、このようにバラバラのミドルウェアの規格を統一し、標準化しようというものである。クライアントツールからは、ODBCの提供するAPIを使ってアクセスする。このAPIは、接続先がOracleだろうがSQL Serverだろうが、Infomixだろうが、DB2だろうが、まったく同じである。APIをコールするときのパラメータを変えてやれば、何にでも接続できるというわけだ。したがって、ODBCを使うためのツールも、接続先のRDBMSも、自由に選ぶことができるというわけだ。
クライアントツールがODBCのAPIにアクセスすればいいことは分かった。しかし、ODBCのレイヤーから先、RDBMSに接続するためには、そのためのドライバが必要になる。Oracleに接続したいなら、Oracle用のドライバが必要になる。ODBCは、ドライバマネージャというレイヤーから、クライアント側から指定されたドライバに接続するという構造なのだ。
ということは、ODBC自体は共通にアクセスするためのAPIセットに過ぎない。本当に主たる処理をするのは、ODBCドライバということになる。したがって、ODBCドライバの出来、不出来でODBCを使ったときのパフォーマンスは劇的に変化することになる。また、ドライバは何をやってもいいわけだから、接続先は必ずしもサーバーRDBMSでなくてもよい。実際に、ExcelやAccessのファイル、果てはテキストファイルにアクセスするためのODBCドライバもあるのだ。
ODBCを使うためには、本来はODBCのAPIにアクセスする必要がある。このための資料や、ODBCドライバを作成するための資料は、ODBC SDKに含まれている。ODBCについてのドキュメントは、アスキー出版から「ODBC 2.0プログラマーズリファレンス
& SDKガイド」が発売されている。現在のODBCのバージョンは2.5だし、この本はとても高価だが、ODBCについての知識が欲しいときには必須となるものだ。
Visual Basic 3.0やAccessに搭載されている、Jetエンジンを使うためのVisual Basic側のオブジェクトをDAO(Data Access Objects)と呼んでいる。このDAOを使う方法は、ODBCを使ってクライアント/サーバー型RDBMSにアクセスすると、どうしても構造上パフォーマンスが悪くなる。このため、ODBC自体のパフォーマンスが悪いと言われがちだが、これはODBCが悪いわけではない。
DAOは、データベースを構成する要素をオブジェクトとして捉え、さまざまなパラメータやデータをオブジェクトに属するプロパティとし、データベースオブジェクトに対する操作をメソッドとして定義したものである。したがって、データベースを構成するためのオブジェクトは階層構造を取っており、同じオブジェクトが複数存在するようなテーブル、フィールドといった多くのオブジェクトは、オブジェクトの集まりとも言えるコレクションとしてアクセスすることもできる。実際には、DAOのオブジェクト構造は図のようになっている。
DAOのオブジェクト階層
DAOでは、Dataコントロールとバウンダリコントロールと呼ばれる何種類かのコントロールを組み合わせて、データベースの特定のフィールドに関連づけることができる。たとえば、テキストボックスやピクチャーボックス、チェックボックス、リストボックス、コンボボックス、OLEコンテナといったコントロールには、バウンダリ機能がある。その他にも、OCXとしてDBList、DBCombo、DBGrid、RichTextBoxなどにもバウンダリ機能がある。こういったバウンダリ機能を使えば、開発工数を大幅に軽減することができる。
また、データコントロールを使えば、レコードの移動などには何もコードを記述する必要はない。さらに、特定のデータだけを探すFindFirstなどメソッドを使うこともできる。データコントロールを使わなくても、MoveNextなどのメソッドを使ってレコードを移動することも可能だ。
もちろん、アクションクエリーなども実行することができるし、ネストこそできないが、トランザクションも可能である。
このような機能は、本来Jetを使うための仕組みである。同じようなことをSQL文だけで実現することを考えたら、ものによっては実にカンタンになんでもできてしまう気になる。さらに、SQL文についての知識がなくても、ISAMのような単純なデータベースを使ったことがあれば、なんとかプログラミングできてしまうのも、DAOのよいところだ。
しかし、このようにカンタンにできてしまうということは、ウラがあると考えなくてはならない。DAOを経由してODBCを使うときには、図のような構造を通っていくことになる。
Visual BasicやAccessからは、あくまでもDAOの構文を使ってアクセスするのだが、実際にはJetエンジンの提供する非公開APIを通り、Jetのクエリーマネージャを経由する。ここから、Remote managerと呼ばれるレイヤーを通ってODBCドライバマネージャに到達する。
ここで問題なのは、Query Managerである。図からも明らかなように、JetはISAMの構造を基本としている。つまり、インデックスを元にしてデータにアクセスするのだ。これは、どんなデータベースでも内部的にはやっていることだから当然である。しかし、そのISAM Managerに直結してJetのAPIやQuery Managerは存在しているのだ。つまり、MoveNextだのFindFirstだのといったメソッドを使うと、このQuery Managerが適当なSQL文を作成してリモートマネージャに送るのである。
いつも、最適なSQL文を送るのであれば問題はない。しかし、決してそうではないのだ。というのも、Jetは接続時に、接続先のリモートデータから対応するテーブルのインデックスを取ってこようとする。そして、このインデックスを元にしてISAM的にアクセスしようとするのだ。ということは、インデックスが長大なときには、接続時やクエリー時に非常に時間がかかってしまうのだ。さらに、インデックスがないときには、データそのものを全部持ってこようとする。このときには大量のデータをJetが抱え込むことになるから、データに修正を加えられても、適切なSQL文を作成してサーバー側のデータを更新したり、再クエリーしたりしていては、時間がかかりすぎてしまう。したがって、このようなときにはリードオンリーになってしまう。
SQL文ではなくテーブルを指定するのが当然のようにできているから、ついテーブルごと接続し、MoveなんとかやFindなんとかメソッドを使おうと考えてしまうが、これではパフォーマンスがいよいよ落ちてしまうというわけだ。
C/Sでの問題(DAO)
というわけで、年賀状作成用RDBMSとも言えるパーソナルユースのAccessのエンジンを経由していることに問題があるのだ。せっかく高性能なRDBMSサーバーを使っているのに、すべてが台なしになってしまうのである。
実際にはこのような問題を回避するため、Recordsetを作成するときにQuery Managerを経由しないで直接ODBCにSQL文を送る設定をすることができる。いわゆるPASSTHROUCHクエリーである。この方法を使えば、高速になるのは間違いないが、取得したレコードセットはリードオンリーになり、DAOの利点は何も使えなくなる。これならば、単にSQL文を投げる仕組みだけあればいいのだから、ODBCやDAOといったたいそうな仕組みなど必要ないということになってしまうのだ。
このような、JetエンジンのオーバーヘッドのためODBC自体が酷評されることになってしまった。こうした事態に一番頭を悩ませたのは、ほかならぬMicrosoftである。「悪いのはODBCではなく、Jetを経由する以上いしかたないことなのだ」といった文章を作り、積極的にばらまいたのは記憶に新しい。しかし、だったらどうすればいいのかといった解決法は記述されていなかった。これではどうしようもない。しかし、クライアント/サーバー型RDBMS市場を取りこめるかは、Microsoftに取っても最重点課題の一つであったハズだ。
こうして、Visual Basic 4.0と共に発表されたのが、RDOである。その名も、Remote Data Objectsと、リモートデータベースにアクセスすることを前提としたオブジェクトであることを詠っている。RDOはODBCを使ってリモートデータベースにアクセスする。したがって、特徴や制限の多くは、ODBCドライバの性能に依存する。
RDOには、次のような特徴がある。
さらに、RDOを手軽に使うためのデータコントロールライクなRDC(Remote Data Control)も用意されている。データコントロールはDAOを使うが、RDCはRDOを使う。すなわち、その機能やパフォーマンスも、DAOとRDOの差と同様である。
データコントロールはJETに依存するために多くの制限を受けるが、RDCはRDOと同様にODBCドライバの実装機能に依存する。このためデータベースサーバーの機能のインプリメントが低いレベルのODBCドライバでは低い機能しか実現できない。
しかし、ドライバの機能さえよければ、RDBMSRDCのパフォーマンスはRDOと同様にデータベースサーバーのパフォーマンスに依存するため、高性能なものとなる。
RDCはコントロールの形式をとるためにデータコントロールと同様にプログラム指向ではないが、データコントロールに比較するとより細かい処理ができるようになっている。特に、コントロールゆえにRDOでは実現できないイベントによる通知機能は特記に値する。
RDCには次のような特徴がある。
RDOは、DAOと同様にODBCを利用することのできるオブジェクトとして提供されている。しかし、DAOと違って、ローカルクエリーエンジンは持っていない。RDOのプロパティはメソッドは、そのほとんどがODBC APIに対応する形で実現されている。すなわち、ODBC APIをオブジェクトの形で利用するための仕組みなのである。
RDOの実態は、In-Process OLEサーバーである。しかし、OLEオートメーションオブジェクト層は小さく軽いためにRDOを使用するためのクライアントマシンの負荷はODBCを使用したときの負荷とほとんど変らない。またOLEオートメーションによるインターフェースを実装しているためにODBC APIを直接呼出すよりも遥かに簡単で保守性に優れたコードの記述が可能である。
ODBCドライバの機能次第では、多くの機能をサーバーに依存させることができ、検索速度をドラスティックに改善できる。つまりデータベースサーバーの検索速度向上させるようにチューニングし、サーバーマシンの性能を引き出し、パフォーマンスのよいクエリーなどの処理を実現できる。
このように、RDOは間に入る余計な処理をするレイヤーが少ないだけ高速に処理をすることができる。次に、代表的な新世代のミドルウェアでの構造的な比較をしてみよう。
上記のデータアクセスを機能別レイヤー別にした表を以下に示す。一般的には、レイヤーの数が少ないデータアクセスであればそのオーバーヘッドが少なくるため高速に動作する。
・Oracle 7 Serverへの接続
・Microsoft SQL Serverへの接続
次の図は、それぞれの手法を使って開発したときに必要となる時間とデータアクセスの性能の関係を示している。開発工数、難易度、汎用性、再利用性、将来性、保守性等を踏まえて、最良の方法は、RDO, RDC, OO4O, SQL DMOのいずれかであろう。この4つ以外の手法は効率が悪いかパフォーマンスに難があると言える。
コラム
SQL Server 6.0 DMO
SQL Server 6.0付属の分散管理オブジェクトである。SQL Server 6.0のDB Libraryをアクセスするための複数階層のオブジェクトレイヤーである。クライアントプログラムはこのオブジェクトを使ってSQL Serverへアクセスする。またアクセス権限さえあればSQL Server自体のスタートやストップも可能であり、この意味ではDB Libraryの能力を超え、極めて強力である。
32ビットVB4からデータベースへアクセスするケースでのパフォーマンスではODBCダイレクトアクセスやRDOを凌ぎ、最も高速に動作する。しかしながらDB Libraryはデータベース構造に依存しているため、Oracle7
Serverなどには接続することはできない。ODBC等と比較するとプログラムは汎用性が失われる。
VB for SQL
DB LibraryダイレクトのAPIセット。自由度が高いくデータアクセスパフォーマンスが高いプログラムを作成できる。しかし、低水準であるため各データベース構造に依存したコードを記述する必要があり、汎用性が失われる。コード量も増える上、データベースの構造とサービスを把握しておく必要があるため、高度な技術力と各DB製品の仕様の理解が必要である。
なおSQL Server 4.21aへDB Libraryを使用してアクセスするため開発セットとしてマクロソフトはSQL Server programmer*s Tool Kitを販売している。Visual Basic 2.0用のVBXは用意されているが、32bit用OCXは含まれていない。したがって、32bit Visual Basic 4.0から利用することはできない。ただしC/C++で開発するときには16/32ビットDB Libraryが含まれるため、16/32ビットアプリケーションを開発することができる。
Oracle Objects for OLE 2.0(32bit版)
Oracle Server 用のミドルウェアである。SQL DMOよりも先に、In-Process OLE Serverのミドルウェアとして登場した。OLE Automation対応アプリケーションであれば、どんなものからも呼び出し可能である。
当初は16bit版のみであったが、近々32bit版のバージョン2.0が発売される予定だ。2.0ではFindXXX系のメソッドが追加される。
OO4OはSQL*Net にダイレクトのインターフェイスを持つので、ODBCドライバを必要としない。ゆえに、ODBCドライバの性能に依存しない。
OO4Oの文法は、DAOの文法に準拠している。したがって、DAOで作成したOracle7 Serverアクセス用コードをすぐにパフォーマンスのよいものに改善することができる。
また、データコントロールライクなOraData OCXコントロールが付属している。従って、OCXに対応しているアプリケーションからも使用が可能である。
MFC、OWL用クラスライブラリも付属しているので、いろいろなプラットホームから使うことができる。
パラメータクエリーの機能を持つので、IN、OUTの引数をバインド変数として使うようなストアドプロシージャもコールすることができる。
OO4Oのオブジェクト構造
OO4Oを使ってストアドプロシージャコールとレコード移動をするプログラム
'Global objects
Global OraSession As Object
Global OraDatabase As Object
Global DlvdDynaset As Object
' Not required to be constant.
Global Const DlvdQuery$ = "select * from TEST1"
Global Const CallSTP = "begin AddTest1(:deptno, :dname, :loc, :newno); end;"
Global Const DatabaseName$ = "2:"
Global Const Connect$ = "oodemo/oracle"
Global Const WarnFirstDlvd$ = "You are already on the first"
Global Const WarnLastDlvd$ = "You are already on the last"
Private Sub Form_Load()
'OraSession and OraDatabase are global
Set OraSession = CreateObject("OracleInProcServer.XOraSession")
Set OraDatabase = OraSession.OpenDatabase(DatabaseName$, Connect$, 0&)
OraDatabase.Parameters.Add "deptno", "1", 1
OraDatabase.Parameters.Add "dname", "test1", 1
OraDatabase.Parameters.Add "loc", "loc1", 1
OraDatabase.Parameters.Add "newno", "", 2 '2 = output
Set DlvdDynaset = OraDatabase.CreateDynaset(DlvdQuery$, 0&)
Call DlvdRefresh
End Sub
Private Sub cmdCall_Click()
Dim num As Integer
num = txtNumber.Text
OraDatabase.Parameters("deptno").Value = num
OraDatabase.Parameters("dname").Value = "name" & CStr(num)
OraDatabase.Parameters("loc").Value = "loc" & CStr(num)
OraDatabase.ExecuteSQL CallSTP
txtNewNO = OraDatabase.Parameters("newno").Value
DlvdDynaset.Refresh
Call DlvdRefresh
End Sub
Private Sub cmdExit_Click()
End
End Sub
Private Sub cmdFirst_Click()
DlvdDynaset.MoveFirst
If DlvdDynaset.BOF = True Then
MsgBox WarnFirstDlvd$
DlvdDynaset.MoveFirst
End If
Call DlvdRefresh
End Sub
Private Sub cmdLast_Click()
DlvdDynaset.MoveLast
If DlvdDynaset.EOF = True Then
MsgBox WarnLastDlvd$
DlvdDynaset.MoveLast
End If
Call DlvdRefresh
End Sub
Private Sub cmdNext_Click()
If DlvdDynaset.EOF <> True Then
DlvdDynaset.MoveNext
If DlvdDynaset.EOF = True Then
MsgBox WarnLastDlvd$
DlvdDynaset.MoveLast
End If
End If
Call DlvdRefresh
End Sub
Private Sub cmdPrevious_Click()
If DlvdDynaset.BOF <> True Then
DlvdDynaset.MovePrevious
If DlvdDynaset.BOF = True Then
MsgBox WarnFirstDlvd$
DlvdDynaset.MoveFirst
End If
End If
Call DlvdRefresh
End Sub
Private Sub DlvdRefresh()
If DlvdDynaset.BOF <> True And DlvdDynaset.EOF <> True Then
txtDID = DlvdDynaset.Fields("DEPTNO").Value
txtORDID = DlvdDynaset.Fields("DNAME").Value
txtONAME = DlvdDynaset.Fields("LOC").Value
Else
txtDID = ""
txtORDID = ""
txtONAME = ""
End If
End Sub
RDOは、DAOと同様にオブジェクトの階層になっている。その構造は、図の通りだ。
ここからも分かるように、DAOの構造とかなり似ているところがある。いずれもRDBMSにアクセスするための手段であるから、当然のことだ。したがって、RDOはJETDAOと同じような手順や方法で使うことができる。
次の表はRDOオブジェクトとそれに対応するDAOオブジェクトを示している。ここからも分かるように、サーバーRDBMSを目的としたRDOと、あくまでもローカルデータベースを前提として作られて拡張されてきたDAOの違いがある。
RDOオブジェクトとコレクションは、リモートODBCデータベースシステムのコンポーネントを作成したり操作するためのプロパティやメソッドを提供する。次の図は、オブジェクトの階層構造に、各オブジェクトやコレクションごとのプロパティやメソッド、およびその関係を示したものだ。
RDOのオブジェクト階層はODBC APIの構造に依存している。次の図は一般的なODBC APIによるデータ検索の処理サイクルである。
ここでは、3つのステップで処理リサイクルが行われる。
1. 環境ハンドルの確立
2. 接続ハンドルの取得
3. クエリーを発行する。このとき、毎回ステートメントハンドルを作成し結果を受け取る。
この3つのサイクルは、RDOでの処理の手順でも同様である。
実際には、RDOは次の手順で使うことになる。
1. rdoEnvironmentオブジェクトの取得
オブジェクトの最上位に位置するrdoEngineには、暗黙のうちにrdoEnvronmentsコレクションの0番目のrdoEnvironmentオブジェクトrdoEnvironments(0)が初期化される。
Dim oENV As rdoEnvironment
Set oENV = rdoEngine.rdoEnvironments(0)
2. rdoConnectionオブジェクトの作成
rdoEnvironmentのログオン情報
rdoEnvironmentのトランザクション適用範囲
rdoConnectionの作成
rdoConnectionの操作
Const DSN As String = "Personal Oracle7"
Const CONNECT As String = "UID=oodemo;PWD=oracle"
Dim oDBC As rdoConnection
Set oDBC = oENV.OpenConnection(DSN, _
rdDriverComplete, _
True, _
CONNECT)
3. クエリーの要求とrdoResultsetの作成
SQL文、カーソルタイプ、ロックタイプを設定
Dim oRset As rdoResultset
Set oRset = oDBC.OpenResultset(txtSQL, _
rdOpenForwardOnly, _
rdConcurReadOnly)
4. 列情報の取得
Dim oCOL As rdoColumn
Dim vsWork As String
If Err = 0 Then
'get col names
For Each oCOL In oSTMT.rdoColumns
vsWork = vsWork & oCOL.Name & vbTab
Next oCOL
lstResult.AddItem vsWork
lstResult.AddItem String$(255, "=")
'get data
Do Until oSTMT.EOF
vsWork = ""
For Each oCOL In oSTMT.rdoColumns
vsWork = vsWork & oCOL.Value & vbTab
Next
lstResult.AddItem vsWork
oSTMT.MoveNext
Loop
Else
MsgBox Err.Description, vbCritical, Err
End If
5. 接続ベースのトランザクション
必要に応じて行う。
6. 接続の開放
oRset.Close
oDBC.Close
oENV.Close
実行図

RDOでは、ODBCドライバとサーバーの機能が許せば、サーバー側カーソルを使うことが可能だ。これには、rdoEngineのrdoDefaultCursorDriverプロパティを設定する。内容は次の通り。
定数 値 内容
rdUseIfNeeded 0 (既定値) ODBC ドライバは、該当するスタイルのカーソルを使用します。Server-side cursorsが使用できる場合は、これを使用します。
rdUseODBC 1 RDO レイヤは ODBC カーソル
ライブラリを使用します。このオプションにより、クエリーの結果を表すレコード
セットが小さい場合は性能が向上しますが、大きい場合は急速に劣化します。
rdUseServer 2 ODBC ドライバは、server-side cursorsを使用します。大規模な操作の大半で性能が向上しますが、ネットワーク
トラフィックの増加を引き起こします。
例
rdoEngine.rdoDefaultCursorDriver = CursorDriver
RDOには、用途に応じて動作の違うカーソルを用意することができる。これは、
OpenResultsetメソッドのtype引数で指定する。カーソルには、次の4つの種類がある。どれが使えるかは、ODBCドライバの機能に依存する。また、同時に指定するLockTypeとの組み合わせにも依存する。
検索速度のパフォーマンスがもっともよいのは前方専用タイプである。しかし、これはMovePreviousなどで後方参照をすることはできない。キーセットタイプは汎用のタイプである。動的タイプはもっとも柔軟性が高いが、パフォーマンスは落ちる。
Type
rdOpenForwardOnly 前方専用タイプ
(既定値)
rdOpenStatic 静的タイプ
rdOpenKeyset キーセット
タイプ
rdOpenDynamic 動的タイプ
LockType
rdConcurLock 排他的同時実行。
rdConcurReadOnly 読み取り専用
(既定値)。
rdConcurRowver 行の ID に基づいた共有的同時実行。
rdConcurValues 行の値に基づいた共有的同時実行。
例
Set oRSET = oDBC.OpenResultset(SQLStatement, _
ResultsetType, _
LockType)
rdConcurLockは排他的ロックである。この方式では行を更新するのに必要な最低レベルのロックが使用される。このオプションはEditまたはAddNewメソッドが実行されるとただちに行または行を含むデータページをロックしUpdateメソッドが変更結果をデータソースに書き込むまでロックしたままにする。
rdConcurRowverは行バージョンを使用する共有的ロック状態だ。ODBCカーソルライブラリとデータソースは行IDまたはTIMESTAMPの値を比較して行が変更されたかどうかを判断する。
rdConcurValuesは行の値を使用する共有的ロック状態である。ODBCカーソルライブラリとデータソースはデータ値を比較する。
rdReadOnlyはカーソルが読み取り専用である。更新は認められない。
RDOでのカーソルの行ポインタは、rdoResultSetのMoveXXXメソッドを使って移動することができる。ただし、前方参照カーソルでは後方参照はできない。
MoveNext 次のポインタに移動
MovePrevious 前のポインタに移動
MoveFirst 最初のポインタに移動
MoveLast 最後ポインタに移動
Move n n行相対移動(nは負でも可)
また、次のプロパティに行ポインタを代入すれば、絶対位置や百分率位置での移動が可能だ。
AbsolutePosition 絶対位置指定
PercentPosition 百分率位置指定
例
oRSET.AbsolutePosition = 105
oRSET.PercentPosition = 50
●ブックマークによる行ポインタの移動
rdoResultsetオブジェクトはブックマークをサポートしている。ブックマークを使えば、現在の位置をVariant変数に保存できる。なんらかの処理をしてポインタを移動した後でも、Bookmarkプロパティを設定すれば、rdoResultsetに保存した位置に戻すことができる。Bookmarkが使えるかどうかは、ODBCドライバに依存する。これは、Bookmarkableプロパティで知ることができる。
例
ブックマークの設定
Private bm As Long
If oRSET.Bookmarkable = True Then
bm = oRSET.Bookmark
Else
MsgBox "Can not use Bookmark", vbCritical
End If
例
ブックマークへの移動
If oRSET.Bookmarkable = True Then
oRSET.Bookmark = bm
If Err Then
MsgBox Err.Description, vbCritical, "Move Error"
Else
表示処理
End If
取得したrdoResultsetに含まれるレコード件数は、RowCountプロパティで知ることができる。ただし、カーソルタイプによっては、最後のレコードに移動しないと取得できないことがある。
rdoResultsetオブジェクトは、カーソルセットによっては変更することが可能だ。これは、Updatableプロパティで知ることができる。
行を更新するには、次の手順を踏む。これは、DAOのときとまったく同様だ。
更新行へ移動
Editメソッドを実行
値を変更
Updateメソッドを実行
rdoResultsetのUpdatableプロパティがTrueのとき、行を追加することができる。これには、次の手順を踏む。
AddNewメソッドを実行
値を変更
Updateメソッドを実行
行を削除したいときには、Deleteメソッドを実行する。
次のような場合には、変更結果は破棄される。
Editメソッドまたはを実行した後、Updateメソッドを実行する前に行を移動したとき
Moveメソッドの引数0を指定したとき
CancelUpdateメソッドを実行したとき
トランザクション処理でロールバックされたとき
例

Private Const DSN As String = "Personal Oracle7"
Private Const CONNECT As String = "UID=oodemo;PWD=oracle"
Private oENV As rdoEnvironment
Private oDBC As rdoConnection
Private oRSET As rdoResultset
Private Sub DispFields()
txtITEMID = oRSET("ITEMID")
txtINAME = oRSET("INAME")
txtIRPRICE = oRSET("IRPRICE")
txtIIPRICE = oRSET("IIPRICE")
txtIORDER = oRSET("IORDER")
End Sub
Private Sub SetFields()
oRSET("ITEMID") = txtITEMID
oRSET("INAME") = txtINAME
oRSET("IRPRICE") = txtIRPRICE
oRSET("IIPRICE") = txtIIPRICE
oRSET("IORDER") = txtIORDER
End Sub
Private Sub ClearFields()
txtITEMID = ""
txtINAME = ""
txtIRPRICE = ""
txtIIPRICE = ""
txtIORDER = ""
End Sub
Private Sub cmdAdd_Click()
On Error Resume Next
ClearFields
oRSET.AddNew
If Err Then
MsgBox Err.Description, vbCritical, "Move Error"
End If
End Sub
Private Sub cmdDelete_Click()
On Error Resume Next
oRSET.Delete
If Err Then
MsgBox Err.Description, vbCritical, "Error"
End If
oRSET.MoveNext
End Sub
Private Sub cmdEdit_Click()
On Error Resume Next
oRSET.Edit
If Err Then
MsgBox Err.Description, vbCritical, "Move Error"
End If
End Sub
Private Sub cmdMFirst_Click()
oRSET.MoveFirst
DispFields
End Sub
Private Sub cmdMLast_Click()
oRSET.MoveLast
DispFields
End Sub
Private Sub cmdMNext_Click()
On Error Resume Next
oRSET.MoveNext
If Err Then
MsgBox Err.Description, vbCritical, "Move Error"
Else
If Not oRSET.EOF Then
DispFields
Else
oRSET.MoveLast
MsgBox "EOF"
End If
End If
End Sub
Private Sub cmdMPrevious_Click()
On Error Resume Next
oRSET.MovePrevious
If Err Then
MsgBox Err.Description, vbCritical, "Move Error"
Else
If Not oRSET.BOF Then
DispFields
Else
oRSET.MoveFirst
MsgBox "BOF"
End If
End If
End Sub
Private Sub cmdUpdate_Click()
On Error Resume Next
SetFields
oRSET.Update
If Err Then
MsgBox Err.Description, vbCritical, "Move Error"
End If
End Sub
Private Sub Form_Load()
Dim SQL As String
On Error Resume Next
rdoEngine.rdoDefaultCursorDriver = rdUseIfNeeded
Set oENV = rdoEngine.rdoEnvironments(0)
Set oDBC = oENV.OpenConnection(DSN, _
rdDriverComplete, _
True, _
CONNECT)
'exec sql
SQL = "select * from x_items_mst order by itemid"
Set oRSET = oDBC.OpenResultset(SQL, _
rdOpenKeyset, _
rdConcurValues)
If Err Then
MsgBox Err.Description, vbCritical, Err
Unload Me
Else
DispFields
End If
End Sub
Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
oRSET.Close
oDBC.Close
oENV.Close
End Sub
Private Sub Timer1_Timer()
chkLockEdits.Value = -oRSET.LockEdits
End Sub
現在の行ポインタが他者によってロックされているかどうかは、rdoResultsetのLockEditsプロパティで知ることができる。
Edit、AddNew、Delete、およびUpdateメソッドを使用する代わりにExecuteメソッドを使用することもできる。1つ以上のUPDATE、INSERTまたはDELETEステートメントを含むSQLクエリーを実行すればRDOのメソッドを使用せずにデータベースを変更できる。
データソースの種類とそれが複雑なマルチステートメント操作をサポートできるかどうかに応じて、これらのSQLステートメントには'make-table'またはSELECT INTOクエリーを実行するロジックを含むことができる。新しい永久的なテーブルまたは一時的なテーブルを作成したり、そのほかの複雑な操作を実行することができる。データソースでサポートされるSQL構文を使用して1つ以上のアトミックセットで操作を連結するトランザクションステートメントを使用することもできる。
しかし、エラーと同時実行は自分で管理しなければならない。Executeメソッドは、行を戻すクエリーを実行するようには設計されていない。“アクション”操作と行を戻す操作が混在するストアードプロシージャを実行する場合には、OpenResultsetメソッドを使用し、生成される結果セットを解析しなければならない。
MaxRowsプロパティ
rdoPreparedStatementを作成し、MaxRowsプロパティを設定すれば1つのクエリーから戻される行数を制限できる。クエリープロセッサがMaxRowsによって指定された行を戻した後クエリーの処理は停止される。この後に続くレコードを取得することはできない。
QueryTimeoutプロパティ
rdoConnectionまたはrdoPreparedStatementでQueryTimeoutプロパティを設定すれば、クエリープロセッサがクエリーを処理する時間も制限できる。
RDOでは、複数のSQL文をセパレータを使って記述し、複数の結果セットを得ることができる。複数結果セットのセット数は動的であり、一つの結果セットをすべてフェッチすると、その結果セットはなくなる。
複数の結果セットを生成するには、次の2種類の方法がある。
1. 接続に対してOpenResultsetを直接実行する方法
2. rdoPreparedStatementに対してOpenResultsetを使用する方法
SQL文はSQLプロパティに設定するか、直接CreatePreparedStatementメソッドに渡す。クエリーの実行はOpenResultsetメソッドを使用する。また、カーソルはODBC側のものを使う必要がある。
結果セットの確認はEOFプロパティとBOFプロパティを使って行うことができる。個々の結果セットの取得はMoveNextメソッドとEOFプロパティを使ってフェッチすることが可能だ。
そこから次の結果セットを取得するには、MoreResultsメソッドを実行すればよい。MoreResultsメソッドの戻り値は結果セットの有無を返す。このとき影響を受ける行数は、RowsAffectedプロパティで知ることができる。MoreResultsメソッドがFalseを返すまで、クエリーを処理するのに必要なそのほかのリソースは解放されないので、注意が必要だ。
RDOでは、パラメータを渡すタイプのクエリーを実行することができる。これには、
CreatePreparedStatementメソッド使って作成したrdoPreparedStatementメソッドを使う。ここでもカーソルはODBC側のものを使う必要がある。
例
Set oPST = oDBC.CreatePreparedStatement("PST1", _
lblSQL.Caption)
oPST.rdoParameters(0).Direction = rdParamReturnValue
oPST.rdoParameters(1).Direction = rdParamInput
Set oSTMT = oPST.OpenResultset(rdOpenForwardOnly, _
rdConcurReadOnly)
入出力クエリーパラメータはプレースホルダとなるSQL文を用意する。このとき、プレースホルダ内のパラメータは?として記述しておく。
例
SELECT au_fname, au_fname FROM authors
WHERE state = ?
プレースホルダ内の呼出しはcallを使う。また、プレースホルダ内の文字列は{ }でくくる。
例
{ ? = call byroyalty (?) }
パラメータ操作はrdoParameterオブジェクトを使う。このとき、パラメータが入出力のいずれをするものであるかを決定するのはDirectionプロパティである。
例
Set oPST = oDBC.CreatePreparedStatement("PST1", _
lblSQL.Caption)
oPST.rdoParameters(0).Direction = rdParamReturnValue
oPST.rdoParameters(1).Direction = rdParamInput
Set oSTMT = oPST.OpenResultset(rdOpenForwardOnly, _
rdConcurReadOnly)
SELECTステートメント中でもパラメータクエリーを使う使うことができる。パラメータをSQLテートメントに渡すとき、出力パラメータの捕捉も同じ方法でおこなう
oPST.rdoParameters(1).Value = CLng(txtInput1.Text)
ReturnVarue = oPST.rdoParameters(0).Value
ReturnVarue = oPST.rdoParameters(0).Value

次のオブジェクトに、ODBC のハンドルを示すプロパティがある。この値を元にして、ODBC APIにアクセスすることが可能だ。
rdoEnvironment. hEnvプロパティ
rdoConnection. hDbcプロパティ
rdoConnection. hStmtプロパティ
rdoResultset. hStmtプロパティ
OpenResultsetメソッドのOptionsプロパティをrdAsyncEnableに設定すると非同期可能になる。
例
Set oRSET = oDBC.OpenResultset(SQLStatement, _
ResultsetType, _
LockType, _
rdAsyncEnable)
こうして作成された最初のデータ行が準備されたかは、rdoResultsetオブジェクトのStillExecutingプロパティで知ることができる。
このとき、AsyncCheckIntervalプロパティにはRDOがStillExecutingプロパティを更新する確認間隔を指定することができる。
非同期で実行中のクエリーを取り消すには、rdoResultsetのCancelメソッドを使う。
例
oSTMT.Cancel
RDOはオブジェクトであるから、非同期処理を通告するイベントは発生できない。したがって、
StillExecutingプロパティを監視し続けなくてはならない。
例
Do while
oSTMT.StillExecuting
DoEvents
なんらかの処理
Loop
エラーが生成されるとそれらのエラーはrdoErrorsコレクションに格納される。このようなエラーには、エラーが発生するときにトラップ可能なエラーも含まれる。このとき、エラーのしきい値を指定して、トラップ可能にするかを決定することができる。
エラーの「しきい値」はrdoDefaultErrorThresholdまたはErrorThresholdプロパティで設定することができる。情報メッセージはトラップ可能なエラーを発生しないので注意が必要だ。
また、ClearメソッドrdoErrorsコレクションのエラーを消去できる。
RDCはデータコントロールによく似ているが、RDCはRDOベースで動作している点が大きく異なる。
RDCを使えば、大部分のリモートデータアクセス操作はコードをまったく書かずに、バウンダリコントロールと組み合わせて実行することができる。
RDCはエラーの処理など多くの問題を自動的に取り扱うので、比較的面倒がない。しかし、高度なエラー処理は記述する必要がある。また、特定条件下ではErrorイベントが発生する。
データコントロールとDAOがそうだったように、RDCを利用してコード内でrdoResultsetを作成できる。
さらに、非同期処理終了通知としてQueryCompletedイベントがあるのも大きな特徴だ。
このとき、rdAsyncEnableを使用しないとインターフェースがブロックされるので、注意が必要である。
例

このように、RDOの機能は実に多彩であり、高速である。その上にかぶせてあるコントロールRDCも使いやすい。細かい処理をしたいときにはRDOが向いているが、マスターメンテナンスプログラム程度であれば、RDCを使って一瞬で作成することができる。
それにしても、RDOは高速である。実際に、DAOとRDOを使って同じデータソースにアクセスしてみると、その違いに驚かされることもしばしばであった。
実際に、DAO、DAO SQLPASSTHROUCH、RDO、OO4Oでパフォーマンスを比較してみた。図は、同一のデータソースでコネクションを確立してクエリーを発行するまでのOpen時間、最終行に飛ぶMoveLastメソッドの処理にかかった時間、すべての行データを取得する時間をそれぞれ比較してみた。RDOのカーソルタイプはキーセットで実行した。また、OO4Oはバージョン2.0α版を使ったので、ここでは結果は出していない。たったこれだけの比較では正確なことは言えないのだが、RDOにはかなりの期待ができそうである。

しかしながら、RDOにはODBCドライバによってはサポートされていない機能があったり、カーソルタイプとロックタイプの組み合わせでエラーが発生したり、パフォーマンスに重大な違いが出たりした。
RDOは現在のところ最良の選択には間違いなさそうではあるが、その前提としてしっかりとしたODBCドライバが絶対に必要不可欠である。