VB2010でADO.NET(SqlClient使用)での考え方

タグの編集
投稿者 acwg  (社会人) 投稿日時 2012/6/14 19:21:26
お世話になります。
今、VB2010 + SQL Server2008 R2 Express にて開発しているのですが、根本的な部分で確認させて
頂きたく、投稿させて頂きます。

今までVB6で開発してまして、今回がVB2010はお初なのですが、今までのプログラムの作りは、

1.プログラム起動時にDBへ接続(ADOです。)
2.各プログラムの処理では、Connectionは、使い回しで、RecordSetをオープンや、Excute
3.プログラムを終了する時に、接続解除

とやっていたので、
今回VB2010で作る際も、その流れで

1.プログラム起動時にDBへ接続(SqlClient使用)
2.各処理で、SqlCommandやSqlDataReaderを使用
3.プログラムを終了する時に、接続解除

としたのですが、SqlDataReaderを開いている最中に、サブルーチンをコールして、そのサブルーチン
の中で、SqlCommandを使用(ExecuteScalar())したら、

例外
この Command に関連付けられている DataReader が既に開かれています。このコマンドを最初に閉じる必要があります。

が出まして、いろいろネットで調べた所、1接続で同時に複数のSqlCommand、SqlDataReaderは開けないとの記事を見かけました。

ちなみに、自分のプログラムは、ConnectionのみPublicで、SqlCommandやSqlDataReaderは、各処理のローカル宣言なのですが、動きを見る限り、この複数使用NG制限にかかっているように見えます。

前置きが長くなりましたが、VB2010のSqlClientを使用したコーディングの場合、都度

1.DBに接続
2.SqlCommandやSqlDataReaderを開く
3.2で開いたものを閉じる
3.DB接続の解除

と、DBアクセスごとに、DBの接続~解除をするのがセオリーなのでしょうか?

みなさま、ご教授の程、宜しくお願い致します。
投稿者 るきお  (社会人) 投稿日時 2012/6/14 20:37:00
>DBアクセスごとに、DBの接続~解除をするのがセオリーなのでしょうか?
はい、そうです。

データベースとの接続をできるだけ短い時間ですませて同時実行性を上げる狙いです。
いちいちOpen、Closeを記述するのは面倒なのでDataAdapterのFillメソッドのようにそれを自動でやってくれるものもあります。

VB6のときADOのRecordSetでMoveNextやMoveFirstしながら行移動するような処理は、
VB2010ではSqlDataAdapter.Fillで取得したDataTableまたはDataSetに対して行うイメージになります。
DataTableまたはDataSetは一度クライアント側にデータをすべて持ってくるので接続を閉じた状態でも操作可能です。(ADOにもそのような切断状態で操作できるレコードセットもありましたが)
ただし、クライアント側にデータをすべて持ってくるので、巨大なテーブルを操作するような仕様がある場合、仕様を変更したほうが良いかもしれません。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/6/14 20:57:05
> 1.プログラム起動時にDBへ接続(ADOです。)
> 2.各プログラムの処理では、Connectionは、使い回しで、RecordSetをオープンや、Excute
> 3.プログラムを終了する時に、接続解除
同時接続数が 1人 2人 ぐらいならば、繋ぎっぱなしで運用するケースもありますが、
そうした使い方は、データの一括取込・変換処理といったバッチ処理向けの方法ですね。

ユーザーが画面操作などを行うアプリの場合、データの取得・更新時間よりも
データを見ている/入力している時間の方が長いため、通信していないのに
コネクションが張られたままになります。

ゆえに ADODB であっても、adUseClient カーソルを通じて「切断型Recordset」が用いられたりします。

Open 後に AcitveConnection を切り離した Recordset の場合、MoveNext の速度も向上しますし、
更新頻度が少ない読み込み専用なテーブルなどでは、Recordset 自体を Save メソッドで
ローカルに保存しておき、次回以降は、それをキャッシュとして利用するといった使い方もできます。

また、そうした Recordset をオフラインで更新しておき、それをサーバーに反映させる段階で
ActiveConnection を再セットして、UpdateBatch メソッドで一括反映させるという使い方も可能です。

ただし幾度も Open/Close を繰り返してしまうと、再接続にかかる
タイムラグが生じるため、コネクションプーリングの併用は必要です。


……って、ADO の話はどうでも良いですね。本題は ADO.NET でした。


> 1.プログラム起動時にDBへ接続(SqlClient使用)
> 2.各処理で、SqlCommandやSqlDataReaderを使用
> 3.プログラムを終了する時に、接続解除

画面に表示して編集し、それをサーバーに登録するようなアプリでは、
データを DataTable に蓄え、それを DataAdapter/TableAdapter 経由で
反映させるといった使い方をします。

ユーザー操作を伴わず、無人でバッチ更新するようなケースでは、
SqlCommand だけでも良いでしょう。ただ、処理内容次第では、
VB 側で処理させるのではなく、SQL Server でストアド処理することも
検討してみてください。


> としたのですが、SqlDataReaderを開いている最中に、
SqlDataReader は、ADO でいうところの adUseServer + adOpenForwardOnly に近いモードであり、
いわゆる『ファイアホースカーソルモード』と呼ばれる状態を作り出します。

データ取得のために最高のパフォーマンスを誇る一方、データ取得のために
コネクションを占有しますので、他のコマンドを受け付けることができません。

VB6 においても、ファイアホースを開いている状態においては、他の処理が
 ・ファイアホースモードの間はトランザクションを開始できません。
 ・手動または分散トランザクション モードのため、新規接続を作成できません。
 ・データ プロバイダまたはほかのサービスが E_FAIL 状態を返しました。
 ・ITransaction::Commit または ITransaction::Abort が呼び出されました。オブジェクトはゾンビ状態です。
などのエラーを生じさせ、実行できなかったりします。


対応策としては、
 (1) 別のコネクションを使って SQL を実行する。(トランザクション処理に注意)
 (2) DataReader を使わず、DataAdapter + DataTable 経由で取得・更新するようにする。
 (3) VB 側で処理しようとせず、ストアドを用いてサーバー側で処理させる。
などがありますが、個人的には 2 か 3 をお奨めしておきます。

第2案 のDataTable に格納するという行為は、ADODB でいうところの
adUseServer + adLockReadOnly に近い処理と言えます。
これはすなわち、SELECT した結果を全件ダウンロードする行為となりますので、
データ量が多い場合には向きません。その場合は第3案の利用を検討してみてください。


> DBアクセスごとに、DBの接続~解除をするのがセオリーなのでしょうか?
> みなさま、ご教授の程、宜しくお願い致します。
上記対応策のようにすれば、Connection 自体を Close せずとも処理できると思いますよ。

http://www.tt.rim.or.jp/~rudyard/torii009.html
投稿者 acwg  (社会人) 投稿日時 2012/6/15 08:54:00
毎々お世話になります。

るきおさん、魔界の仮面弁士さん、丁寧な説明ありがとうございます。

やっぱりつなぎっぱなしは、よくないのですね…。
今回のシステムは、最大で同時利用者数が20数人になる予定ですので、DBアクセス時の処理は、
都度接続の方向に修正してみようと思います。

また、現状、SqlCommandとSqlDataReaderのみで、DBからのデータ取得を行なっているので、機能単位では、全件一度に扱う処理や、そうでない処理もあるので、他の手段と使い分けたいと思います。

まだ、ADO.NETは、やり始めということもあって、かたよった知識で作ってしまっている部分があると思いますので、頂いたアドバイスを元に各処理を見直してみます。

>魔界の仮面弁士さん
ちょっと納期的にすこし調整が入り時間がとれたので、前回教えて頂きましたOleDBからSqlClientへの
乗り換えを実施してみました。

いつも、みなさま、ご助言ありがとうございます。
本件は、解決とさせて頂きます。