建立可更新的提供者
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
CUpdateCommand 中實作函式(請參閱 UpdatePV 中的 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、可寫入的資料行,以及資料行中的所有資料都有相同的長度。 如果您希望資料行中的資料具有可變長度,則不會設定此旗標。
另請參閱
意見反映
https://aka.ms/ContentUserFeedback。
即將推出:我們會在 2024 年淘汰 GitHub 問題,並以全新的意見反應系統取代並作為內容意見反應的渠道。 如需更多資訊,請參閱:提交及檢視以下的意見反映: