[Data Access] ODBC カーソルライブラリ非推奨に伴う Visual Studio 2012 以降のバージョンの MFC における実装変更とその動作について

横井 羽衣子 (よこい ういこ)
Microsoft SQL Developer Support Engineer

みなさんごきげんよう。本日は、Visual Studio 2012 の MFC CDatabase クラスにおける実装変更と、それに伴う挙動の変化についてご紹介します。この挙動は、レガシー テクノロジの一つである ODBC カーソル ライブラリが非推奨になったことに伴う変更です。
ODBC カーソル ライブラリをはじめ、いくつかの古くから存在するデータアクセス テクノロジには時代の変化に伴い、非推奨となった機能があります。今回の記事では、そうした非推奨となった機能についての情報を今後確認頂くための情報などもご紹介します。

【現象】
Visual Studio 2012 より前のバージョンの MFC の CDatabase クラスで以下の条件で処理を実行した場合と、Visual Studio 2012 以降の同クラスを用いた場合で接続先の SQL Server のバージョンに関わらず、処理の結果が異なる。
実装の流れは以下の通り。

1. CDatabase::OpenEx() のオプションに、CDatabase::useCursorLib を指定してデータベースをオープン
2. CRecordset::Open() でレコードセットタイプ (nOpenType) を明示的に指定しないか、あるいは CRecoredset::snapshot
(※ ) を指定して Edit() メソッドを実行する

結果は以下の通り。

Visual Studio 2012 よりも前の MFC 問題なく動作
Visual Studio 2012 以降の MFC Edit() メソッドで "レコードセットは読み取り専用です" エラーが発生

(※) CRecoredset::snapshot は既定値であり、レコードセットタイプを指定しない場合はこの値が適用される。

【解説】
概要
ODBC カーソル ライブラリが非推奨になったため、Visual Studio 2012 以降はサーバー カーソルを使用するように MFC の実装が変更されたことが要因となります。CRecordset クラスを使用して SQL Server でサーバー カーソルを開く場合、既定値でもある CRecoredset::snapshot が適用される場合、読み取り専用の静的カーソルとなるため、CRecorset を通しての更新は許可されません。この結果、Edit() メソッドで「更新ができない」旨のエラーが発生します。
ODBC カーソル ライブラリが非推奨となったために動作が変更されたことを踏まえた場合、対処方法は以下のいずれかです。

A. ODBC カーソル ライブラリを使う派生クラスを実装して使用する。
B. CRecordset クラスにオプション指定することで更新可能なカーソルを使用するようにする。

しかし、MFC の実装変更理由が ODBC カーソル ライブラリが非推奨になったためであることを考慮した場合、将来性の観点では A よりも B のほうが望ましい方法であると言えます。従って、弊社としては、このパターンの場合は B の対処を推奨しています。

詳細
なぜ CRecordSet::Edit() で更新しようとしてエラーになるか
Visual Studio 2012 以前のバージョンでは、SQL_CUR_USE_ODBC (ODBC カーソル ライブラリ:クライアント カーソル) が指定されていましたが、Visual Studio 2012 以降は既定で SQL_CUR_USE_DRIVER (サーバーカーソル) が使用されるように変更されています。
MFC 側での変更箇所は、Visual Studio 付属の MFC のソースコード中の dbcore.cpp 内の CDatabase::AllocConnect() で確認できます。
(MFC のソースは、C:\Program Files (x86)\Microsoft Visual Studio <バージョン>\VC\atlmfc\src\mfc にあります。例 : VS2012 / C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\atlmfc\src\mfc)

■ VS2012 以前のバージョン付属 dbcore.cpp (CDatabase::AllocConnect() )
----------<抜粋 ここから>----------
// Turn on cursor lib support
if (dwOptions & useCursorLib)
{
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC));
// With cursor library added records immediately in result set
m_bIncRecordCountOnAdd = TRUE;
}
----------<抜粋 ここまで>----------

■ VS 2012 以降のバージョン付属 dbcore.cpp (CDatabase::AllocConnect() )
----------<抜粋 ここから>----------
// Turn on cursor lib support
if (dwOptions & useCursorLib)
{

                        AFX_SQL_SYNC(::SQLSetConnectAttr(m_hdbc, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_DRIVER, 0));
// With cursor library added records immediately in result set
m_bIncRecordCountOnAdd = TRUE;
}
----------<抜粋 ここまで>----------

CRecordset::Open() でレコードセットタイプ (nOpenType) を明示的に指定されていないと、既定値 CRecoredset::snapshot が適用されることになります。(あるいは、明示的に CRecoredset::snapshot を指定していても同じです) これは静的カーソルを開くことを意味します。SQL Server では、静的カーソルは読み取り専用となります。このため、更新できずにエラーが発生します。

参考 :
CRecordset::Open
https://msdn.microsoft.com/ja-jp/library/1hkkwdf0.aspx

CRecordset::snapshot 双方向スクロールが行われる静的レコードセット。 レコードのメンバーシップと順序は、レコードセットを開いたときに決定されます。データ値は、レコードをフェッチしたときに決定されます。 他のユーザーが行った変更は、レコードセットを閉じてから再度開くまで表示されません。
CRecordset の既定値は CRecordset::snapshot です。 既定値の機構により、Visual C++ ウィザードで既定値の異なる ODBC CRecordset と DAO CDaoRecordset 両方を操作できます。

静的カーソル (データベース エンジン)
https://technet.microsoft.com/ja-jp/library/ms191286(v=sql.90).aspx

Microsoft SQL Server 2005 の静的カーソルは常に読み取り専用です。

なお、今回の現象は Microsoft Connect サイトで、実際に MFC の開発エンジニアが、ODBC カーソル ライブラリは非推奨の旨のコメントを表明しています。

CDatabase::useCursorLib broken in MFC11
https://connect.microsoft.com/VisualStudio/feedback/details/760374/cdatabase-usecursorlib-broken-in-mfc11

投稿者: Microsoft 、投稿日時: 2013/01/10 13:38

Hello, Thanks for the report. We have investigated and found that this change was made because the SQL_CUR_USE_ODBC value was deprecated, so the MFC library build failed if that option was used. If this is causing problems in your application's behavior, I suggest you check with the SQL Server forum at https://social.msdn.microsoft.com/Forums/en-US/category/sqlserver/ to ask about solutions. You also may be able to set the option on your HDBC yourself, but this may not be supported. Pat Brenner Visual C++ Libraries Development

従いまして、今後のバージョンにおいても、VS2012 以降の変更をベースとした対応になる方向です。

対処方法について
弊社が製品利用において、今後将来を踏まえて検討した場合と、対処方法の方向性は以下の二つが挙げられます。

A. ODBC クライアントカーソルを使用するよう派生クラスを作成する。
B. サーバーカーソルで更新可能なカーソルを使用するようにする。

<対処 A について>
ODBC カーソル ライブラリを使用するためには、自分が使用するために、 VS2012 以前のバージョン付属 dbcore.cpp をベースに派生クラスを作成し、OpenEx メソッドをオーバーライドしたものを参照するようにします。

<対処 B について>
対処 A は、上記の通り、ODBC カーソルライブラリを使用するように明示的に実装したクラスを代替利用しますため、事実上以前と同じ動作結果を得ることが可能です。
しかしながら、MFC の実装が変更された理由は、ODBC カーソルが非推奨となったためであることを踏まえると、対処 A については技術面では、想定通りの結果が得られるという点で対処可能ですが、今後の将来性を考えると妥当とは言えません。
このため、今回を機会に、サーバー カーソルのうち更新可能なカーソル タイプを利用いただくようにしていただくことをお勧めします。
CRecordset クラスを使う場合には、CRecordset::Open() において nOpenType に CRecordset::dynaset もしくは CRecordset::dynamic を指定する方法で更新可能なカーソルタイプを使用することが可能です。

データアクセスコンポーネントの今後を確認するための情報について

ODBC カーソルライブラリも含め、データアクセステクノロジの今後については、以下のページを随時更新しています。

データアクセスコンポーネントは、予告なく突然サポートを打ち切ったり、同梱しなくなるといったようなことはありませんが、必ず数年前から非推奨になった旨が告知され、いずれ終了する形を取っています。こちらのページは日本語版もありますが、内容が古く、英語版が随時更新されますので、今後方向性を検討される際は都度ご一読ください。

Data Access Technologies Road Map
https://msdn.microsoft.com/en-us/library/ms810810.aspx
→ "has been deprecated. " = 推奨しない

ODBC カーソル ライブラリが非推奨であることは以下に記載があります。

ODBC Cursor Library: ODBC Cursor Library (ODBCCR32.dll) provides limited client-side data cursors. ODBC Cursor Library has been deprecated; your application can use server side cursor implementations as a replacement.

ODBC カーソル ライブラリの扱い(今後 OS 同梱されなくなるか)
ODBC カーソル ライブラリはもともと、SQL Server などのデータベースでカーソルという概念がない時代、アプリケーション側でデータを読み出す際に順次読みだす実装を実現するために作成された機能です。ただし、サーバー カーソルが実装された後も、互換性のために残され続けたという歴史的な経緯があるコンポーネントです。今後において、少なくとも現時点で廃止の明確な時期は決まっていませんが今後の OS に搭載されることは保証しません。 また、一般的に、クライアントカーソルは、少量データなどを使用する場合に適しており、大量のデータを必要とする場合ではデータ転送負荷、およびクライアント環境への依存度(マシンスペック、領域等)といった観点で推奨されません。また、実装設計も古い時代のままで(※) あることなどを踏まえ、弊社においては、ODBC カーソル ライブラリをどうしても使用せざるを得ないご事情がある場合を除き、基本的にカーソル タイプの変更を推奨しております。

(※) たとえば、ODBC カーソル ライブラリはカーソル動作を実現するために、データソースから受け取ったデータを一時フォルダ(環境変数 TEMP)にファイルを作成し、アプリケーション側でそのファイルからデータを読み出しています。現在では、SQL Server 側でサーバー カーソルの機能を持つことから、実装や設計も古く(たとえば、一時ファイルのサイズ上限として 2GB であることなど)、現在に至るまで機能更新もない状況である ODBC カーソル ライブラリを必ずしも使用する必要は無くなっています。 また、上記のように、ネットワーク伝送だけでなく、ファイル作成、読み取りのオーバーヘッドがあることから、パフォーマンスについてもギガビットネットワークが主流の現在においては、サーバーカーソルに優位性が十分あります。