更新可能なプロバイダーの作成
Visual C++ は、更新可能なプロバイダー、すなわちデータ ストアの更新 (書き込み) を行えるプロバイダーをサポートしています。 このトピックでは、OLE DB テンプレートを使用して更新可能なプロバイダーを作成する方法について説明します。
このトピックでは、作業可能なプロバイダーを使用して開始することを想定しています。 更新可能なプロバイダーを作成するには、2 つの手順があります。 まず、プロバイダーがデータ ストアに変更を加える方法を決定する必要があります。具体的には、変更を直ちに行うのか、更新コマンドが発行されるまで延期するかです。 「プロバイダーを更新可能にする」セクションで、プロバイダー コードで行う必要がある変更と設定について説明しています。
次に、コンシューマーがプロバイダーに要求する可能性があるすべての機能をサポートするために、プロバイダーにすべての機能が含まれていることを確認する必要があります。 コンシューマーがデータ ストアを更新しようとする場合、データ ストアにデータを保持するコードをプロバイダーが含む必要があります。 たとえば、C ランタイム ライブラリまたは MFC を使用すると、そのような操作をデータ ソースに対して実行できます。 「データ ソースへの書き込み」セクションで、データ ソースへの書き込み、NULL および既定値の処理、列フラグの設定を行う方法について説明します。
Note
UpdatePV は、更新可能なプロバイダーの例です。 UpdatePV は MyProv と同じですが、更新可能なサポートがあります。
プロバイダーを更新可能にする
プロバイダーを更新可能にする際に重要となるのは、プロバイダーがデータ ストアに対して実行する操作と、プロバイダーがそれらの操作を実行する方法を理解することです。 具体的には、データ ストアの更新を直ちに行うのか、更新コマンドが発行されるまで延期するか (バッチ処理) が主な問題です。
最初に、行セット クラスで IRowsetChangeImpl
と IRowsetUpdateImpl
のどちらを継承するかを決める必要があります。 これらのどちらを実装するかによって、SetData
、InsertRows
、および DeleteRows
の 3 つのメソッドの機能が影響を受けます。
IRowsetChangeImpl から継承する場合、これら 3 つのメソッドを呼び出すとデータ ストアが直ちに変更されます。
IRowsetUpdateImpl から継承する場合、
Update
、GetOriginalData
、またはUndo
を呼び出すまで、メソッドはデータ ストアへの変更を延期します。 更新に複数の変更が含まれる場合は、バッチ モードで実行されます (変更をバッチ処理すると、かなりのメモリ オーバーヘッドが追加される可能性があります)。
IRowsetUpdateImpl
は IRowsetChangeImpl
から派生することに注意してください。 したがって、IRowsetUpdateImpl
では変更機能に加えてバッチ機能が提供されます。
プロバイダーで更新可能性をサポートするには
行セット クラスで、
IRowsetChangeImpl
またはIRowsetUpdateImpl
を継承します。 これらのクラスは、データ ストアを変更するための適切なインターフェイスを提供します。IRowsetChange の追加
次の形式を使用して、継承チェーンに
IRowsetChangeImpl
を追加します。IRowsetChangeImpl< rowset-name, storage-name >
また、行セット クラスの
COM_INTERFACE_ENTRY(IRowsetChange)
セクションにもBEGIN_COM_MAP
を追加します。IRowsetUpdate の追加
次の形式を使用して、継承チェーンに
IRowsetUpdate
を追加します。IRowsetUpdateImpl< rowset-name, storage>
Note
継承チェーンから
IRowsetChangeImpl
の行を削除する必要があります。 前述のディレクティブに対するこの 1 つの例外は、IRowsetChangeImpl
のコードを含む必要があります。以下を COM マップ (
BEGIN_COM_MAP ... END_COM_MAP
) に追加します。実装対象 COM マップに追加 IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetUpdate)
実装対象 プロパティ セット マップに追加 IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
コマンドで、プロパティ セット マップ (
BEGIN_PROPSET_MAP ... END_PROPSET_MAP
) に以下を追加します。実装対象 プロパティ セット マップに追加 IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
プロパティ セット マップには、次に示す設定も表示されているとおりにすべて含める必要があります。
PROPERTY_INFO_ENTRY_VALUE(UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE) PROPERTY_INFO_ENTRY_VALUE(CHANGEINSERTEDROWS, VARIANT_TRUE) PROPERTY_INFO_ENTRY_VALUE(IMMOBILEROWS, VARIANT_TRUE) PROPERTY_INFO_ENTRY_EX(OWNINSERT, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(OWNUPDATEDELETE, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(OTHERINSERT, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(OTHERUPDATEDELETE, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(REMOVEDELETED, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_FALSE, 0)
これらのマクロ呼び出しで使用される値は、Atldb.h でプロパティ ID と値を探して見つけることができます (Atldb.h がオンライン ドキュメントと異なる場合、Atldb.h がドキュメントよりも優先されます)。
Note
VARIANT_FALSE
およびVARIANT_TRUE
設定の多くは OLE DB テンプレートで必要です。OLE DB 仕様では、読み取り/書き込みにすることが可能と記述されていますが、OLE DB テンプレートでサポートできるのは 1 つの値のみです。IRowsetChangeImpl を実装する場合
IRowsetChangeImpl
を実装する場合は、プロバイダーに次のプロパティを設定する必要があります。 これらのプロパティは、ICommandProperties::SetProperties
を介してインターフェイスを要求するために主に使用されます。DBPROP_IRowsetChange
: これを設定するとDBPROP_IRowsetChange
が自動的に設定されます。DBPROP_UPDATABILITY
:IRowsetChange
でサポートされるメソッド (SetData
、DeleteRows
、またはInsertRow
) を指定するビットマスク。DBPROP_CHANGEINSERTEDROWS
: コンシューマーは新たに挿入された行に対してIRowsetChange::DeleteRows
またはSetData
を呼び出すことができます。DBPROP_IMMOBILEROWS
: 行セットでは、挿入または更新される行の順序が変更されません。
IRowsetUpdateImpl を実装する場合
IRowsetUpdateImpl
を実装する場合は、前に一覧表示したIRowsetChangeImpl
のすべてのプロパティを設定するだけでなく、プロバイダーに次のプロパティを設定する必要があります。DBPROP_IRowsetUpdate
=DBPROP_OWNINSERT
: READ_ONLY かつ VARIANT_TRUE である必要があります。DBPROP_OWNUPDATEDELETE
: READ_ONLY かつ VARIANT_TRUE である必要があります。DBPROP_OTHERINSERT
: READ_ONLY かつ VARIANT_TRUE である必要があります。DBPROP_OTHERUPDATEDELETE
: READ_ONLY かつ VARIANT_TRUE である必要があります。DBPROP_REMOVEDELETED
: READ_ONLY かつ VARIANT_TRUE である必要があります。DBPROP_MAXPENDINGROWS
=
Note
通知をサポートしている場合は、他にもいくつかのプロパティを使用する可能性があります。この一覧については、
IRowsetNotifyCP
の セクションを参照してください。
データ ソースへの書き込み
データ ソースを読み取るには、Execute
関数を呼び出します。 データ ソースに書き込むには、FlushData
関数を呼び出します。 (一般的には、フラッシュは、テーブルまたはインデックスに対して行った変更をディスクに保存することを意味します。)
FlushData(HROW, HACCESSOR);
行ハンドル (HROW) およびアクセサー ハンドル (HACCESSOR) 引数を使用すると、書き込む領域を指定できます。 通常、一度に 1 つのデータ フィールドを書き込みます。
FlushData
メソッドは、データが最初に格納されていた形式で書き込みます。 この関数をオーバーライドしなくても、プロバイダーは正しく機能しますが、変更内容はデータ ストアにフラッシュされません。
フラッシュする場合
プロバイダー テンプレートは、データをデータ ストアに書き込む必要がある場合は常に FlushData を呼び出します。通常 (ただし、常にではない)、これは次の関数の呼び出しの結果として発生します。
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows
(行に挿入する新しいデータがある場合)IRowsetUpdate::Update
動作のしくみ
コンシューマーが、フラッシュ (Update など) を必要とする呼び出しを行うと、この呼び出しはプロバイダーに渡されます。プロバイダーは常に次の処理を行います。
状態値がバインドされている場合は常に
SetDBStatus
を呼び出します。列フラグをチェックします。
IsUpdateAllowed
.
これら 3 つの手順は、セキュリティを提供するのに役立ちます。 その後、プロバイダーは FlushData
を呼び出します。
FlushData を実装する方法
FlushData
を実装するには、いくつかの問題を考慮する必要があります。
データ ストアで変更を処理できることを確認する。
NULL 値を処理する。
既定値を処理する。
独自の FlushData
メソッドを実装するには、次の処理を行う必要があります。
行セット クラスに移動します。
行セット クラスに、次の宣言を設定します。
HRESULT FlushData(HROW, HACCESSOR) { // Insert your implementation here and return an HRESULT. }
FlushData
の実装を用意します。
FlushData
を適切に実装すると、実際に更新された行と列のみが格納されます。 HROW パラメーターと HACCESSOR パラメーターを使用して、最適化のために格納されている現在の行と列を特定できます。
通常、最大の課題は、独自のネイティブ データ ストアを操作することです。 可能であれば、次の方法を試してください。
データ ストアへの書き込み方法は、可能な限り単純にしてください。
NULL 値を処理します (省略可能ですが、推奨します)。
既定値を処理します (省略可能ですが、推奨します)。
最善の方法は、NULL 値と既定値に対して実際に指定された値をデータ ストアに格納することです。 これは、このデータを引き渡す場合に最適です。 それ以外の場合は、NULL 値と既定値を許可しないことをお勧めします。
次の例は、UpdatePV
サンプルの RUpdateRowset
クラスに FlushData
を実装する方法を示します (サンプル コードの Rowset.h を参照してください)。
///////////////////////////////////////////////////////////////////////////
// class RUpdateRowset (in rowset.h)
...
HRESULT FlushData(HROW, HACCESSOR)
{
ATLTRACE2(atlTraceDBProvider, 0, "RUpdateRowset::FlushData\n");
USES_CONVERSION;
enum {
sizeOfString = 256,
sizeOfFileName = MAX_PATH
};
FILE* pFile = NULL;
TCHAR szString[sizeOfString];
TCHAR szFile[sizeOfFileName];
errcode err = 0;
ObjectLock lock(this);
// From a filename, passed in as a command text,
// scan the file placing data in the data array.
if (m_strCommandText == (BSTR)NULL)
{
ATLTRACE( "RRowsetUpdate::FlushData -- "
"No filename specified\n");
return E_FAIL;
}
// Open the file
_tcscpy_s(szFile, sizeOfFileName, OLE2T(m_strCommandText));
if ((szFile[0] == _T('\0')) ||
((err = _tfopen_s(&pFile, &szFile[0], _T("w"))) != 0))
{
ATLTRACE("RUpdateRowset::FlushData -- Could not open file\n");
return DB_E_NOTABLE;
}
// Iterate through the row data and store it.
for (long l=0; l<m_rgRowData.GetSize(); l++)
{
CAgentMan am = m_rgRowData[l];
_putw((int)am.dwFixed, pFile);
if (_tcscmp(&am.szCommand[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szCommand);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
if (_tcscmp(&am.szText[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szText);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
if (_tcscmp(&am.szCommand2[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szCommand2);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
if (_tcscmp(&am.szText2[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szText2);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
}
if (fflush(pFile) == EOF || fclose(pFile) == EOF)
{
ATLTRACE("RRowsetUpdate::FlushData -- "
"Couldn't flush or close file\n");
}
return S_OK;
}
変更の処理
プロバイダーで変更を処理するには、まず、データ ストア (テキスト ファイルやビデオ ファイルなど) に変更を加えるための機能が備わっている必要があります。 ない場合は、プロバイダー プロジェクトとは別にコードを作成する必要があります。
NULL データの処理
エンド ユーザーが NULL データを送信する可能性があります。 データ ソースのフィールドに NULL 値を書き込むと、問題が発生する可能性があります。 市町村と郵便番号の値を受け取る受注アプリケーションについて考えてみてください。どちらかの値または両方の値を受け取りますが、どちらも受け取らないということはありません。その場合には、配達が不可能になるためです。 つまり、アプリケーションに対応するように、複数のフィールドの NULL 値の特定の組み合わせを制限する必要があります。
プロバイダー開発者は、そのデータを格納する方法、データ ストアからそのデータを読み取る方法、およびそれをユーザーに指定する方法を検討する必要があります。 具体的には、データ ソース内の行セット データのデータ状態 (DataStatus = NULL など) を変更する方法を検討する必要があります。 コンシューマーが NULL 値を含むフィールドにアクセスしたときに、どのような値を返すかを決定します。
UpdatePV サンプルのコードを見てください。これには、プロバイダーが NULL データを処理する方法が示されています。 UpdatePV では、プロバイダーは文字列 "NULL" をデータ ストアに書き込むことで、NULL データを格納します。 データ ストアから NULL データを読み取るときは、その文字列を確認してから、バッファーを空にして、NULL 文字列を作成します。 これには IRowsetImpl::GetDBStatus
のオーバーライドも含まれます。そのデータ値が空の場合には これによって DBSTATUS_S_ISNULL が返されます。
Null 許容列のマーキング
スキーマ行セット (IDBSchemaRowsetImpl
を参照) も実装する場合は、実装によって、その列が Null 許容であることを DBSCHEMA_COLUMNS 行セット内に指定する必要があります (通常は、プロバイダー内で CxxxSchemaColSchemaRowset によってマーキングされます)。
また、ご使用バージョンの GetColumnInfo
で、すべての Null 許容列が DBCOLUMNFLAGS_ISNULLABLE 値を含むように指定することも必要です。
OLE DB テンプレート実装では、列を Null 許容であるとマーキングしないと、プロバイダーは列に値が含まれていると想定し、コンシューマーが列に Null 値を送信することを許可しません。
次の例では、CommonGetColInfo
関数が UpdatePV の CUpdateCommand (UpProvRS.cpp を参照) にどのように実装されるかを示します。 Null 許容列についてこの DBCOLUMNFLAGS_ISNULLABLE がどのように列に設定されるかに注意してください。
/////////////////////////////////////////////////////////////////////////////
// CUpdateCommand (in UpProvRS.cpp)
ATLCOLUMNINFO* CommonGetColInfo(IUnknown* pPropsUnk, ULONG* pcCols, bool bBookmark)
{
static ATLCOLUMNINFO _rgColumns[6];
ULONG ulCols = 0;
if (bBookmark)
{
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Bookmark"), 0,
sizeof(DWORD), DBTYPE_BYTES,
0, 0, GUID_NULL, CAgentMan, dwBookmark,
DBCOLUMNFLAGS_ISBOOKMARK)
ulCols++;
}
// Next set the other columns up.
// Add a fixed length entry for OLE DB conformance testing purposes
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Fixed"), 1, 4, DBTYPE_UI4,
10, 255, GUID_NULL, CAgentMan, dwFixed,
DBCOLUMNFLAGS_WRITE |
DBCOLUMNFLAGS_ISFIXEDLENGTH)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Command"), 2, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szCommand,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Text"), 3, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szText,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Command2"), 4, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szCommand2,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Text2"), 5, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szText2,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
if (pcCols != NULL)
{
*pcCols = ulCols;
}
return _rgColumns;
}
既定値
NULL データと同様に、既定値の変更に対処する必要があります。
既定の FlushData
および Execute
は、S_OK を返します。 したがって、この関数をオーバライドしないと、変更が正常に行われたように見えます (S_OK が返されます) が、データ ストアには送信されません。
UpdatePV
サンプル (Rowset.h) では、SetDBStatus
メソッドは既定値を次のように処理します。
virtual HRESULT SetDBStatus(DBSTATUS* pdbStatus, CSimpleRow* pRow,
ATLCOLUMNINFO* pColInfo)
{
ATLASSERT(pRow != NULL && pColInfo != NULL && pdbStatus != NULL);
void* pData = NULL;
char* pDefaultData = NULL;
DWORD* pFixedData = NULL;
switch (*pdbStatus)
{
case DBSTATUS_S_DEFAULT:
pData = (void*)&m_rgRowData[pRow->m_iRowset];
if (pColInfo->wType == DBTYPE_STR)
{
pDefaultData = (char*)pData + pColInfo->cbOffset;
strcpy_s(pDefaultData, "Default");
}
else
{
pFixedData = (DWORD*)((BYTE*)pData +
pColInfo->cbOffset);
*pFixedData = 0;
return S_OK;
}
break;
case DBSTATUS_S_ISNULL:
default:
break;
}
return S_OK;
}
列フラグ
列で既定値をサポートする場合は、<provider class>SchemaRowset クラスでメタデータを使用して設定する必要があります。 m_bColumnHasDefault = VARIANT_TRUE
を設定します。
また、DBCOLUMNFLAGS 列挙型を使用して指定される列フラグを設定する必要もあります。 列フラグによって、列の特性を指定します。
たとえば、UpdatePV
の CUpdateSessionColSchemaRowset
クラス (Session.h) では、最初の列はこのように設定されます。
// Set up column 1
trData[0].m_ulOrdinalPosition = 1;
trData[0].m_bIsNullable = VARIANT_FALSE;
trData[0].m_bColumnHasDefault = VARIANT_TRUE;
trData[0].m_nDataType = DBTYPE_UI4;
trData[0].m_nNumericPrecision = 10;
trData[0].m_ulColumnFlags = DBCOLUMNFLAGS_WRITE |
DBCOLUMNFLAGS_ISFIXEDLENGTH;
lstrcpyW(trData[0].m_szColumnDefault, OLESTR("0"));
m_rgRowData.Add(trData[0]);
このコードでは、列が既定値 0 をサポートすること、書き込み可能であること、列のすべてのデータの長さが同一であることなども指定されています。 列のデータの長さを可変にする場合には、このフラグは設定しません。