建立可更新的提供者
Visual C++支援可更新的提供者或提供者,這些提供者可以更新(寫入)數據存放區。 本主題討論如何使用 OLE DB 範本建立可更新的提供者。
本主題假設您從可運作的提供者開始。 建立可更新的提供者有兩個步驟。 您必須先決定提供者對數據存放區進行變更的方式;具體來說,變更是要立即完成,還是延後,直到發出更新命令為止。 「讓提供者更新」一節描述您在提供者程式代碼中必須執行的變更和設定。
接下來,您必須確定提供者包含所有功能,以支持取用者可能要求的任何功能。 如果取用者想要更新資料存放區,提供者必須包含將數據保存到資料存放區的程序代碼。 例如,您可以使用 C 執行時間連結庫或 MFC 在數據源上執行這類作業。 「寫入數據源」一節說明如何寫入數據源、處理NULL和預設值,以及設定數據行旗標。
注意
UpdatePV 是可更新提供者的範例。 UpdatePV 與 MyProv 相同,但支援可更新。
讓提供者成為可更新的提供者
讓提供者更新的關鍵在於瞭解您希望提供者在資料存放區上執行的作業,以及您希望提供者如何執行這些作業。 具體來說,主要問題是數據存放區的更新要立即完成或延遲(批處理),直到發出更新命令為止。
您必須先決定要繼承自 IRowsetChangeImpl
資料列集類別或 IRowsetUpdateImpl
數據列集類別中。 根據您選擇實作哪一種方法,三種方法的功能將受到影響: SetData
、 InsertRows
和 DeleteRows
。
如果您繼承自 IRowsetChangeImpl,則呼叫這三種方法會立即變更資料存放區。
如果您繼承自 IRowsetUpdateImpl,方法會將變更延遲至資料存放區,直到您呼叫
Update
、GetOriginalData
或Undo
。 如果更新涉及數個變更,則會以批次模式執行它們(請注意,批處理變更可能會增加相當大的記憶體負荷)。
請注意, IRowsetUpdateImpl
衍生自 IRowsetChangeImpl
。 因此, IRowsetUpdateImpl
可讓您變更功能加上批次功能。
在您的提供者中支援可更新性
在您的資料欄集類別中,繼承自
IRowsetChangeImpl
或IRowsetUpdateImpl
。 這些類別提供適當的介面來變更資料存放區:新增 IRowsetChange
使用此形式新增
IRowsetChangeImpl
至繼承鏈結:IRowsetChangeImpl< rowset-name, storage-name >
此外,
BEGIN_COM_MAP
請將 新增COM_INTERFACE_ENTRY(IRowsetChange)
至數據列集類別中的 區段。新增 IRowsetUpdate
使用此形式新增
IRowsetUpdate
至繼承鏈結:IRowsetUpdateImpl< rowset-name, storage>
注意
您應該從繼承鏈結中移除
IRowsetChangeImpl
該行。 上述指示詞的這個例外狀況必須包含的程序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 中的屬性識別碼和值(如果 Atldb.h 與在線檔不同,Atldb.h 就會取代檔)。
注意
VARIANT_FALSE
OLE DB 範本需要許多 和VARIANT_TRUE
設定;OLE DB 規格表示它們可以讀取/寫入,但 OLE DB 範本只能支援一個值。如果您實作 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 AND VARIANT_TRUE。DBPROP_OWNUPDATEDELETE
:必須是 READ_ONLY AND VARIANT_TRUE。DBPROP_OTHERINSERT
:必須是 READ_ONLY AND VARIANT_TRUE。DBPROP_OTHERUPDATEDELETE
:必須是 READ_ONLY AND VARIANT_TRUE。DBPROP_REMOVEDELETED
:必須是 READ_ONLY AND VARIANT_TRUE。DBPROP_MAXPENDINGROWS
.
注意
如果您支援通知,您可能也會有一些其他屬性;如需此清單,請參閱 上的
IRowsetNotifyCP
區段。
寫入數據源
若要從數據源讀取,請呼叫 函 Execute
式。 若要寫入數據源,請呼叫 函 FlushData
式。 (一般來說,flush 表示儲存對數據表或索引所做的修改至磁碟。
FlushData(HROW, HACCESSOR);
數據列句柄 (HROW) 和存取子句柄 (HACCESSOR) 自變數可讓您指定要寫入的區域。 一般而言,您會一次寫入單一數據欄位。
方法會 FlushData
以原本儲存的數據格式寫入數據。 如果您未覆寫此函式,您的提供者將會正常運作,但不會將變更排清至數據存放區。
排清的時機
每當需要將數據寫入數據存放區時,提供者範本都會呼叫 FlushData;這通常是因為呼叫下列函式而發生,但並非一律發生:
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows
(如果有新資料要插入資料列)IRowsetUpdate::Update
運作方式
取用者進行呼叫,需要排清 (例如 Update),而且此呼叫會傳遞至提供者,此呼叫一律會執行下列動作:
每當您有狀態值系結時呼叫
SetDBStatus
。檢查數據行旗標。
呼叫
IsUpdateAllowed
。
這三個步驟有助於提供安全性。 然後提供者會呼叫 FlushData
。
如何實作 FlushData
若要實 FlushData
作 ,您必須考慮幾個問題:
確定數據存放區可以處理變更。
處理 NULL 值。
處理預設值。
若要實作您自己的 FlushData
方法,您需要:
移至您的資料列集類別。
在資料列集類別中,放置下列專案的宣告:
HRESULT FlushData(HROW, HACCESSOR) { // Insert your implementation here and return an HRESULT. }
提供的實作
FlushData
。
良好的實作 FlushData
只會儲存實際更新的數據列和數據行。 您可以使用 HROW 和 HACCESSOR 參數來判斷要儲存以進行優化的目前數據列和資料行。
一般而言,最大的挑戰是使用您自己的原生數據存放區。 可能的話,請嘗試:
盡可能將寫入數據存放區的方法保持簡單。
處理 NULL 值(選擇性但建議)。
處理預設值(選擇性但建議)。
最好的作法是將數據存放區中實際指定的值用於 NULL 和預設值。 如果可以推斷此數據,最好是這樣。 如果沒有,建議您不要允許NULL和預設值。
下列範例示範如何在FlushData
範例中的 UpdatePV
類別中RUpdateRowset
實作 (請參閱範例程序代碼中的 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
),您的實作應該在DBSCHEMA_COLUMNS數據列集中指定(通常由 CxxxSchemaColSchemaRowset 在提供者中標示為可為 Null 的數據行。
您也需要指定所有可為 Null 的數據行都包含 您 版本中 GetColumnInfo
的 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;
}
數據行旗標
如果您在數據行上支援預設值,則必須在提供者類別 SchemaRowset 類別>中使用<元數據加以設定。 設定 m_bColumnHasDefault = VARIANT_TRUE
。
您也有責任設定數據行旗標,這些旗標是使用 DBCOLUMNFLAGS 列舉型別所指定。 數據行旗標描述數據行特性。
例如,在 CUpdateSessionColSchemaRowset
中的類別 UpdatePV
中(在 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、可寫入的數據行,以及數據行中的所有數據都有相同的長度。 如果您希望數據行中的數據具有可變長度,則不會設定此旗標。