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 について紹介しよう。

酒井 法雄

クライアント/サーバー型RDBMSへの熱い眼差し

今、デベロッパーは、いろいろな点で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)であろう。

●ODBCとは何か

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についての知識が欲しいときには必須となるものだ。

●なぜDAOではダメなのか

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といったたいそうな仕組みなど必要ないということになってしまうのだ。

●RDOの特徴

このような、JetエンジンのオーバーヘッドのためODBC自体が酷評されることになってしまった。こうした事態に一番頭を悩ませたのは、ほかならぬMicrosoftである。「悪いのはODBCではなく、Jetを経由する以上いしかたないことなのだ」といった文章を作り、積極的にばらまいたのは記憶に新しい。しかし、だったらどうすればいいのかといった解決法は記述されていなかった。これではどうしようもない。しかし、クライアント/サーバー型RDBMS市場を取りこめるかは、Microsoftに取っても最重点課題の一つであったハズだ。

こうして、Visual Basic 4.0と共に発表されたのが、RDOである。その名も、Remote Data Objectsと、リモートデータベースにアクセスすることを前提としたオブジェクトであることを詠っている。RDOはODBCを使ってリモートデータベースにアクセスする。したがって、特徴や制限の多くは、ODBCドライバの性能に依存する。

RDOには、次のような特徴がある。

●Remote Data Control

さらに、RDOを手軽に使うためのデータコントロールライクなRDC(Remote Data Control)も用意されている。データコントロールはDAOを使うが、RDCはRDOを使う。すなわち、その機能やパフォーマンスも、DAOとRDOの差と同様である。

データコントロールはJETに依存するために多くの制限を受けるが、RDCはRDOと同様にODBCドライバの実装機能に依存する。このためデータベースサーバーの機能のインプリメントが低いレベルのODBCドライバでは低い機能しか実現できない。

しかし、ドライバの機能さえよければ、RDBMSRDCのパフォーマンスはRDOと同様にデータベースサーバーのパフォーマンスに依存するため、高性能なものとなる。

RDCはコントロールの形式をとるためにデータコントロールと同様にプログラム指向ではないが、データコントロールに比較するとより細かい処理ができるようになっている。特に、コントロールゆえにRDOでは実現できないイベントによる通知機能は特記に値する。

RDCには次のような特徴がある。

●RDOの構造

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.0DB 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.21aDB Libraryを使用してアクセスするため開発セットとしてマクロソフトはSQL Server programmer*s Tool Kitを販売している。Visual Basic 2.0用のVBXは用意されているが、32bitOCXは含まれていない。したがって、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のオブジェクト階層

RDOは、DAOと同様にオブジェクトの階層になっている。その構造は、図の通りだ。

ここからも分かるように、DAOの構造とかなり似ているところがある。いずれもRDBMSにアクセスするための手段であるから、当然のことだ。したがって、RDOはJETDAOと同じような手順や方法で使うことができる。

次の表はRDOオブジェクトとそれに対応するDAOオブジェクトを示している。ここからも分かるように、サーバーRDBMSを目的としたRDOと、あくまでもローカルデータベースを前提として作られて拡張されてきたDAOの違いがある。

RDOオブジェクトとコレクションは、リモートODBCデータベースシステムのコンポーネントを作成したり操作するためのプロパティやメソッドを提供する。次の図は、オブジェクトの階層構造に、各オブジェクトやコレクションごとのプロパティやメソッド、およびその関係を示したものだ。

●RDOでの処理手順

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の行ポインタの設定

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のレコード件数の取得

取得したrdoResultsetに含まれるレコード件数は、RowCountプロパティで知ることができる。ただし、カーソルタイプによっては、最後のレコードに移動しないと取得できないことがある。

●RDOのデータの変更

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メソッドを使用し、生成される結果セットを解析しなければならない。

●rdoResultset件数の制限の設定

MaxRowsプロパティ
rdoPreparedStatementを作成し、MaxRowsプロパティを設定すれば1つのクエリーから戻される行数を制限できる。クエリープロセッサがMaxRowsによって指定された行を戻した後クエリーの処理は停止される。この後に続くレコードを取得することはできない。

QueryTimeoutプロパティ
rdoConnectionまたはrdoPreparedStatementでQueryTimeoutプロパティを設定すれば、クエリープロセッサがクエリーを処理する時間も制限できる。

●RDOの複数の結果セットの処理

RDOでは、複数のSQL文をセパレータを使って記述し、複数の結果セットを得ることができる。複数結果セットのセット数は動的であり、一つの結果セットをすべてフェッチすると、その結果セットはなくなる。

複数の結果セットを生成するには、次の2種類の方法がある。

1. 接続に対してOpenResultsetを直接実行する方法

2. rdoPreparedStatementに対してOpenResultsetを使用する方法

SQL文はSQLプロパティに設定するか、直接CreatePreparedStatementメソッドに渡す。クエリーの実行はOpenResultsetメソッドを使用する。また、カーソルはODBC側のものを使う必要がある。

結果セットの確認はEOFプロパティとBOFプロパティを使って行うことができる。個々の結果セットの取得はMoveNextメソッドとEOFプロパティを使ってフェッチすることが可能だ。

そこから次の結果セットを取得するには、MoreResultsメソッドを実行すればよい。MoreResultsメソッドの戻り値は結果セットの有無を返す。このとき影響を受ける行数は、RowsAffectedプロパティで知ることができる。MoreResultsメソッドがFalseを返すまで、クエリーを処理するのに必要なそのほかのリソースは解放されないので、注意が必要だ。

●RDOのパラメータクエリーの作成

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 のハンドルを示すプロパティがある。この値を元にして、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

●RDOのエラーとメッセージの管理

エラーが生成されるとそれらのエラーはrdoErrorsコレクションに格納される。このようなエラーには、エラーが発生するときにトラップ可能なエラーも含まれる。このとき、エラーのしきい値を指定して、トラップ可能にするかを決定することができる。

エラーの「しきい値」はrdoDefaultErrorThresholdまたはErrorThresholdプロパティで設定することができる。情報メッセージはトラップ可能なエラーを発生しないので注意が必要だ。

また、ClearメソッドrdoErrorsコレクションのエラーを消去できる。

●RDC

RDCはデータコントロールによく似ているが、RDCはRDOベースで動作している点が大きく異なる。

RDCを使えば、大部分のリモートデータアクセス操作はコードをまったく書かずに、バウンダリコントロールと組み合わせて実行することができる。

RDCはエラーの処理など多くの問題を自動的に取り扱うので、比較的面倒がない。しかし、高度なエラー処理は記述する必要がある。また、特定条件下ではErrorイベントが発生する。

データコントロールとDAOがそうだったように、RDCを利用してコード内でrdoResultsetを作成できる。

さらに、非同期処理終了通知としてQueryCompletedイベントがあるのも大きな特徴だ。

このとき、rdAsyncEnableを使用しないとインターフェースがブロックされるので、注意が必要である。


●RDOはミドルウェアの決定打なのか

このように、RDOの機能は実に多彩であり、高速である。その上にかぶせてあるコントロールRDCも使いやすい。細かい処理をしたいときにはRDOが向いているが、マスターメンテナンスプログラム程度であれば、RDCを使って一瞬で作成することができる。

それにしても、RDOは高速である。実際に、DAOとRDOを使って同じデータソースにアクセスしてみると、その違いに驚かされることもしばしばであった。

実際に、DAO、DAO SQLPASSTHROUCH、RDO、OO4Oでパフォーマンスを比較してみた。図は、同一のデータソースでコネクションを確立してクエリーを発行するまでのOpen時間、最終行に飛ぶMoveLastメソッドの処理にかかった時間、すべての行データを取得する時間をそれぞれ比較してみた。RDOのカーソルタイプはキーセットで実行した。また、OO4Oはバージョン2.0α版を使ったので、ここでは結果は出していない。たったこれだけの比較では正確なことは言えないのだが、RDOにはかなりの期待ができそうである。


しかしながら、RDOにはODBCドライバによってはサポートされていない機能があったり、カーソルタイプとロックタイプの組み合わせでエラーが発生したり、パフォーマンスに重大な違いが出たりした。

RDOは現在のところ最良の選択には間違いなさそうではあるが、その前提としてしっかりとしたODBCドライバが絶対に必要不可欠である。