さてさて、つらつらとさっき Web サイトを巡回してたら、ADO.NET では非接続型データアクセスと呼ばれるデータアクセス手法が新規に導入されたので、ADO 時代のデータアクセスとは考え方が変わっている……という話を見かけたのですが、いやいやそれは違いますよ~;、とツッコミを入れたくなったり。実は ADO の時代には、
の 2 パターンがちゃんとサポートされていた上に、さらに ADO.NET ではできない
までサポートしていたりします。でも、この辺の話って重要な割にはわかりやすくまとめられている資料がない、という問題があって、VB6 や ASP で開発している人でも知らない人の方が多かったりするんですよね。というわけで、このエントリではちょっと昔を振り返って、
を説明してみたいと思います。
[ADO Recordset の基本的なコーディングパターン ]
ADO を使う場合、Recordset の開き方は実に多種多様で、様々な「ショートカット的なオープン」が可能になっていました。ところがこの多種多様さゆえに Recordset の正しい開き方がわからなくなっている側面があり、結果として Recordset の誤用が多発していました。
まず、万能かつ基本的な Recordset の開き方は以下のコーディングパターンになります。(他にもさまざまなオープン方法がありますが、それらはいずれもこのコーディングパターンでカバーができます。)
Dim con As ADODB.Connection
Dim cmd As ADODB.Command
Dim rs As ADODB.Recordset
Set con = CreateObject("ADODB.Connection")
Set cmd = CreateObject("ADODB.Command")
Set rs = CreateObject("ADODB.Recordset")
con.Open "Provider=SQLOLEDB;Data Source=sqlsrv00;Initial Catalog=pubs;Trusted_Connection=yes"
Set cmd.ActiveConnection = con
cmd.CommandType = adCmdText
cmd.CommandText = "SELECT * FROM authors"
rs.CursorLocation = adUseClient
rs.CursorType = adOpenStatic
rs.LockType = adBatchOptimistic
rs.Open cmd
このコードで最も重要なのが、Recordset のオープン直前に行っている 3 つのオプション設定です。
3 つのオプションの意味と設定可能な値は以下の通りです。
そして、ADO の Recordset オブジェクトは、この 3 つのオプションの組み合わせ次第で、内部の挙動が変化するように設計・実装されています。代表的な使い方は以下の 3 つです。
そして、ADO と ADO.NET との対応関係は以下の通りになっています。
気を付けなければいけないのは、うかつに Recordset を開くと②のサーバカーソルが開く可能性がある、という点です。実は ADO の時代でも、Web アプリケーションなどでは一番下に書かれた方式(非接続型データアクセス)でアプリケーションを開発するのが望ましいのです。しかし、カーソルオプションを正しく指定しないとこの方式が利用できないことや、そもそもこの方式の存在が知られていないといった問題があり、多くのアプリケーションで誤ったサーバカーソルが利用されていました(そしてこれが各種の性能問題を引き起こす例がかなり見られました)。ちなみに ADO クライアントカーソルを使った非接続型データアクセスの正しいコーディング方法は、以下の通りになります。
Set cmd.ActiveConnection = Nothing
Set cmd = Nothing
Set rs.ActiveConnection = Nothing
con.Close
Set con = Nothing
※ ここまで来ると、ADO Recordset はデータベース接続から切り離された DataSet オブジェクトのような状態になるので、プロセス間などでの引き渡しもできるようになる。
このように、非接続型データアクセスの手法を利用し、Connection オブジェクトから切り離された状態で存在する ADO Recordset オブジェクトのことを、当時は切断型レコードセット(Disconnected Recordset)と呼んでいました。(DataSet オブジェクトは、この切断型レコードセットの設計を元にして、これを進化・発展する形で作られています。)
※ なお、ADO Recordset オブジェクトには、カーソルオプションの自動調整機能が備わっており、指定されたカーソルオプションは、カーソルオープン時にテーブルの状況(一意なインデックスの存在)を勘案して自動的に調整されます。テーブルがプライマリキーを持つ場合の自動調整結果を以下に示しますが、これは特に覚える必要はありません。というのは、実際に業務アプリケーションで利用するのは①と③のパターンだけだからです。
[CCE(Client Cursor Engine) と QBU(Query-based Update)]
引き続き、切断型レコードセットを利用した場合の、データベースに対するデータ更新メカニズムについて解説します。
前述したように、ADO カーソルオープン時に adUseClient を利用すると、一括してクライアント側にデータが取り込まれます。このとき、クライアント側の ADO 内部で動作するエンジンのことを CCE (Client Cursor Engine)と呼んでおり、ADO.NET で言うところの DataSet や DataView 的な機能がこれにより実現できるようになっています。CCE が持つ代表的な可能としては、以下のようなものがあります。
さらには更新再同期機能(これは DataAdapter にもあります)や更新矛盾時のハンドリング、さらには階層化レコードセットなど(これらはない)などの機能もありますが、解説しだすとキリがないのでやめます;。実際に CCE/QBU によりデータ更新を行うためには、以下のようなコードを書きますが、これは現在の DataSet/DataAdapter に非常によく似た挙動になっています。
con.Open "Provider=SQLOLEDB;Data Source=win2ksv\sql2k;Initial Catalog=Northwind;Trusted_Connection=yes"
rs.CursorLocation = adUseClient ' CCE利用を指定
rs.CursorType = adOpenStatic ' adOpenStatic以外を指定してもこの値に変更される
rs.LockType = adLockBatchOptimistic ' adLockReadOnly以外はこの値に変更される
strSQL = "SELECT CustomerID, BalanceDue FROM Customers(UPDLOCK) WHERE CustomerID='7'"
' データベース上に更新ロックを残すことで、ロック制御を簡単にします。
rs.Open strSQL, con, , , adCmdText
Set rs.ActiveConnection = Nothing ' 切断レコードセットにする
---- (この切断レコードセットをコンポーネント間で受け渡しするなどして変更)
rs.Fields("BalanceDue") = rs.Fields("BalanceDue") + 50 ' ここではUpdateしない
---- (この切断レコードセットをコンポーネント間で受け渡しするなどする)
Set rs.ActiveConnection = con ' 再びデータベースに接続
rs.UpdateBatch
' この処理により、以下のSQL文がデータベースに送られる
' UPDATE Customers SET BalanceDue=150 WHERE CustomerID='7' AND BalanceDue=50
rs.Close
Set rs = Nothing
ここでは難しすぎるので深堀はしませんが、ADO 切断型 Recordset によるデータ更新機能は、現在の DataSet や DataAdapter によるデータ更新に比べて(実は)かなりリッチでした。具体的には、以下のようなこともできました。
がしかし、正直なところ、これらを正確に使いこなすことは極端に難しい、というのが実際のところです。たとえば生成される WHERE 句の形式を変更するためには、
rs.Properties("Update Criteria") = adCriteriaUpdCols
というコードを記述するのですが……普通に考えてそんなの無茶です;。また、複数のテーブルから JOIN して持ってきたデータの更新書き戻しの際には、親/子テーブルに対してどのような反映の仕方をすべきかの細かい判断や制御のための設定も必要で、「機能は充実しているけれども誰も使いこなせない」というライブラリになってしまっていたのが実際かと思います。
※ ちなみに ADO については、David Sceppa 氏の "Progarmming ADO" という名著があり、それにこれらの解説すべてが書かれているのですが、残念ながら邦訳されていないのですよね....
[ADO から ADO.NET へ]
ADO から ADO.NET へ移行する際、.NET Framework の開発チームは ADO 時代の反省を踏まえて ADO.NET のライブラリを設計したそうなのですが、特に大きなポイントとしては以下のものがあったらしいです。(すいません、元の文献が見つからないので記憶を手繰ります。なので間違っているかも。)
※ 最後のポイントについて少し捕捉しますと、サーバカーソルは「使ってはいけないもの」ではなく、「間違って使うととんでもないトラブルを引き起こしやすいもの」です。実際、たとえば 100 万件のデータを逐次処理しようと思うと、サーバカーソルなしでは非常に処理が難しいのも事実であり、.NET Framework 2.0 では .NET ストアドプロシージャのサポートと関連して、Resultset オブジェクトと呼ばれるサーバカーソル機能の導入が検討されていました。しかし、β2 のときには存在していた(しかもちゃんと動いていた)この機能、結果的には RTM (製品出荷時)には取り除かれることになりました。(正直なところ、個人的にはかなりほっとした話ではあったりします。)
[まとめ]
というわけでつらつらといろいろ書いてきましたが、ADO.NET 時代を理解してから ADO 時代にさかのぼると、ADO の時代も相当先進的なことをやってたんだなぁと改めて思うのですが(楽観同時実行制御メカニズムが ADO/OLE DB はおろか DAO/ODBC 時代から存在していたということに驚かれる人もいるのでは^^)、ライブラリの進化という観点から見た場合、ADO.NET ライブラリの割り切りは非常にすぐれている、と思います。ADO → ADO.NET は、単なる .NET への変化という以上に、開発者にとって使いやすい API を提供する、という目標があり、それに合致しているからこそ優れたライブラリになっているのではないか、と個人的には思っています。
にしても……アメリカ本社の Microsoft の開発チームの人ってホントすごいと思います。私も Microsoft の人間なのでこういうことを書くと自作自演っぽく見えるかもしれないのですが、一人のエンジニアとしてやはり US の人たちはすごいなと思わずにはいられません。彼らがどんなことを考えてそれを作ったのか、を考えるだけでもいろいろ勉強になります、はい。