Vytvoření aktualizovatelného zprostředkovatele
Visual C++ podporuje aktualizovatelné poskytovatele nebo zprostředkovatele, kteří můžou aktualizovat (zapisovat do) úložiště dat. Toto téma popisuje, jak vytvářet aktualizovatelné zprostředkovatele pomocí šablon OLE DB.
V tomto tématu se předpokládá, že začínáte s funkčním poskytovatelem. Vytvoření aktualizovatelného zprostředkovatele je možné provést dvěma kroky. Nejprve se musíte rozhodnout, jak poskytovatel provede změny v úložišti dat; konkrétně, zda mají být změny provedeny okamžitě nebo odloženy, dokud nebude vydán příkaz aktualizace. V části "Nastavení zprostředkovatelů aktualizovatelné" jsou popsané změny a nastavení, které musíte udělat v kódu zprostředkovatele.
Dále se musíte ujistit, že váš poskytovatel obsahuje všechny funkce pro podporu všeho, co si o něj příjemce může vyžádat. Pokud chce příjemce aktualizovat úložiště dat, musí poskytovatel obsahovat kód, který uchovává data do úložiště dat. K provádění takových operací ve zdroji dat můžete například použít knihovnu runtime jazyka C nebo mfc. Část Zápis do zdroje dat popisuje, jak zapisovat do zdroje dat, řešit hodnoty NULL a výchozí hodnoty a nastavit příznaky sloupců.
Poznámka:
UpdatePV je příkladem aktualizovatelného poskytovatele. UpdatePV je stejný jako MyProv, ale s aktualizovatelnou podporou.
Zpřístupnění zprostředkovatelů jako aktualizovatelných
Klíčem k tomu, aby byl poskytovatel aktualizovatelný, je pochopit, jaké operace má váš poskytovatel provádět v úložišti dat a jak chcete, aby poskytovatel tyto operace prováděl. Konkrétně hlavním problémem je to, jestli se aktualizace úložiště dat mají provést okamžitě nebo odložit (dávkově), dokud se nevystaví příkaz aktualizace.
Nejprve se musíte rozhodnout, jestli se má dědit z IRowsetChangeImpl
třídy sady řádků nebo IRowsetUpdateImpl
z této třídy. V závislosti na tom, které z těchto možností se rozhodnete implementovat, budou ovlivněny funkce tří metod: SetData
, InsertRows
a DeleteRows
.
Pokud dědíte z IRowsetChangeImpl, volání těchto tří metod okamžitě změní úložiště dat.
Pokud dědíte z IRowsetUpdateImpl, metody odkládají změny do úložiště dat, dokud nevoláte
Update
,GetOriginalData
neboUndo
. Pokud aktualizace zahrnuje několik změn, provádějí se v dávkovém režimu (mějte na paměti, že změny dávkování můžou znamenat značné režijní náklady na paměť).
Všimněte si, že IRowsetUpdateImpl
odvozuje z IRowsetChangeImpl
. IRowsetUpdateImpl
Díky tomu získáte možnost změny a dávkové schopnosti.
Podpora aktualizovatelnosti ve vašem poskytovateli
Ve třídě sady řádků dědí z
IRowsetChangeImpl
neboIRowsetUpdateImpl
. Tyto třídy poskytují vhodná rozhraní pro změnu úložiště dat:Přidání IRowsetChange
Přidejte
IRowsetChangeImpl
do řetězu dědičnosti pomocí tohoto formuláře:IRowsetChangeImpl< rowset-name, storage-name >
COM_INTERFACE_ENTRY(IRowsetChange)
Přidejte také do oddíluBEGIN_COM_MAP
ve třídě sady řádků.Přidání IRowsetUpdate
Přidejte
IRowsetUpdate
do řetězu dědičnosti pomocí tohoto formuláře:IRowsetUpdateImpl< rowset-name, storage>
Poznámka:
Řádek byste měli odebrat z řetězu
IRowsetChangeImpl
dědičnosti. Tato jedna výjimka z dříve zmíněné direktivy musí obsahovat kód proIRowsetChangeImpl
.Do mapy
BEGIN_COM_MAP ... END_COM_MAP
modelu COM přidejte následující:Pokud implementujete Přidat do mapy MODELU COM IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetUpdate)
Pokud implementujete Přidat do mapy sady vlastností IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
Do příkazu přidejte následující položky do mapy sady vlastností (
BEGIN_PROPSET_MAP ... END_PROPSET_MAP
):Pokud implementujete Přidat do mapy sady vlastností IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
V mapě sady vlastností byste měli zahrnout také všechna následující nastavení, jak se zobrazí níže:
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)
Hodnoty použité v těchto voláních maker najdete v atldb.h pro ID a hodnoty vlastnosti (pokud se Atldb.h liší od online dokumentace, Atldb.h nahrazuje dokumentaci).
Poznámka:
Mnoho nastavení
VARIANT_FALSE
aVARIANT_TRUE
nastavení jsou vyžadovány šablonami OLE DB; specifikace OLE DB říká, že mohou být pro čtení a zápis, ale šablony OLE DB mohou podporovat pouze jednu hodnotu.Pokud implementujete IRowsetChangeImpl
Pokud implementujete
IRowsetChangeImpl
, musíte u svého poskytovatele nastavit následující vlastnosti. Tyto vlastnosti se primárně používají k vyžádání rozhraní prostřednictvímICommandProperties::SetProperties
.DBPROP_IRowsetChange
: Toto nastavení se automaticky nastavíDBPROP_IRowsetChange
.DBPROP_UPDATABILITY
: Bitová maska určující podporované metody :IRowsetChange
SetData
,DeleteRows
neboInsertRow
.DBPROP_CHANGEINSERTEDROWS
: Příjemce může volatIRowsetChange::DeleteRows
neboSetData
pro nově vložené řádky.DBPROP_IMMOBILEROWS
: Sada řádků nepřeuspořádá vložené ani aktualizované řádky.
Pokud implementujete IRowsetUpdateImpl
Pokud implementujete
IRowsetUpdateImpl
, musíte u svého poskytovatele nastavit následující vlastnosti, kromě nastavení všech vlastností proIRowsetChangeImpl
dříve uvedené vlastnosti:DBPROP_IRowsetUpdate
.DBPROP_OWNINSERT
: Musí být READ_ONLY AND VARIANT_TRUE.DBPROP_OWNUPDATEDELETE
: Musí být READ_ONLY AND VARIANT_TRUE.DBPROP_OTHERINSERT
: Musí být READ_ONLY AND VARIANT_TRUE.DBPROP_OTHERUPDATEDELETE
: Musí být READ_ONLY AND VARIANT_TRUE.DBPROP_REMOVEDELETED
: Musí být READ_ONLY AND VARIANT_TRUE.DBPROP_MAXPENDINGROWS
.
Poznámka:
Pokud podporujete oznámení, můžete mít také některé další vlastnosti; podívejte se na část tohoto
IRowsetNotifyCP
seznamu.
Zápis do zdroje dat
Pokud chcete číst ze zdroje dat, zavolejte Execute
funkci. Pokud chcete zapisovat do zdroje dat, zavolejte FlushData
funkci. (Obecně platí, že vyprázdnění znamená uložení změn provedených v tabulce nebo indexu na disk.)
FlushData(HROW, HACCESSOR);
Argumenty úchytu řádku (HROW) a přístupového úchytu (HACCESSOR) umožňují zadat oblast pro zápis. Obvykle zapíšete jedno datové pole najednou.
Metoda FlushData
zapisuje data ve formátu, ve kterém byla původně uložena. Pokud tuto funkci nepřepíšete, poskytovatel bude fungovat správně, ale změny nebudou vyprázdněné do úložiště dat.
Kdy se má vyprázdnit
Šablony zprostředkovatele volají FlushData při každém zápisu dat do úložiště dat; k tomu obvykle dochází (ale ne vždy) v důsledku volání následujících funkcí:
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows
(pokud existují nová data, která se mají vložit do řádku)IRowsetUpdate::Update
Jak to funguje
Příjemce provede volání, které vyžaduje vyprázdnění (například aktualizaci) a toto volání se předá poskytovateli, který vždy provede následující akce:
Volání
SetDBStatus
vždy, když máte vázanou hodnotu stavu.Kontroluje příznaky sloupců.
Zavolá metodu
IsUpdateAllowed
.
Tyto tři kroky pomáhají zajistit zabezpečení. Pak zprostředkovatele zavolá FlushData
.
Implementace FlushData
K implementaci FlushData
je potřeba vzít v úvahu několik problémů:
Ujistěte se, že úložiště dat dokáže zpracovat změny.
Zpracování hodnot NULL
Zpracování výchozích hodnot
Pokud chcete implementovat vlastní FlushData
metodu, musíte:
Přejděte do třídy sady řádků.
Do třídy sady řádků vložte deklaraci:
HRESULT FlushData(HROW, HACCESSOR) { // Insert your implementation here and return an HRESULT. }
Poskytnout implementaci
FlushData
.
Dobrá implementace FlushData
ukládá pouze řádky a sloupce, které se skutečně aktualizují. Pomocí parametrů HROW a HACCESSOR můžete určit aktuální řádek a sloupec, které se ukládají pro optimalizaci.
Největší výzvou je obvykle práce s vlastním nativním úložištěm dat. Pokud je to možné, zkuste:
Udržujte metodu zápisu do úložiště dat co nejjednodušší.
Zpracování hodnot NULL (volitelné, ale doporučeno).
Zpracování výchozích hodnot (volitelné, ale doporučené).
Nejlepší věcí je mít ve vašem úložišti dat skutečné zadané hodnoty pro hodnoty NULL a výchozí hodnoty. Nejlepší je, pokud můžete tato data extrapolovat. Pokud ne, doporučujeme nepovolit hodnoty NULL a výchozí hodnoty.
Následující příklad ukazuje, jak FlushData
se implementuje ve RUpdateRowset
třídě v ukázce UpdatePV
(viz Rowset.h v ukázkovém kódu):
///////////////////////////////////////////////////////////////////////////
// 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;
}
Zpracování změn
Aby váš poskytovatel mohl zpracovávat změny, musíte nejprve zajistit, aby vaše úložiště dat (například textový soubor nebo videosoubor) mělo zařízení, která vám umožní provádět změny. Pokud ne, měli byste tento kód vytvořit odděleně od projektu zprostředkovatele.
Zpracování dat NULL
Je možné, že koncový uživatel odešle data NULL. Při zápisu hodnot NULL do polí ve zdroji dat může dojít k potenciálním problémům. Představte si aplikaci pro pořizování objednávek, která přijímá hodnoty pro město a PSČ; může přijmout buď nebo obě hodnoty, ale ne ani jedno, protože v takovém případě by doručení nebylo možné. Proto je nutné omezit určité kombinace hodnot NULL v polích, která mají smysl pro vaši aplikaci.
Jako vývojář zprostředkovatele musíte zvážit, jak budete tato data ukládat, jak budete tato data číst z úložiště dat a jak je zadáte uživateli. Konkrétně je nutné zvážit, jak změnit stav dat sady řádků ve zdroji dat (například DataStatus = NULL). Rozhodnete se, jaká hodnota se má vrátit, když příjemce přistupuje k poli obsahujícímu hodnotu NULL.
Podívejte se na kód v ukázce UpdatePV; ukazuje, jak může zprostředkovatel zpracovávat data NULL. V updatePV poskytovatel ukládá data NULL zápisem řetězce NULL do úložiště dat. Když načte data NULL z úložiště dat, uvidí tento řetězec a pak vyprázdní vyrovnávací paměť a vytvoří řetězec NULL. Má také přepsání IRowsetImpl::GetDBStatus
, ve kterém vrací DBSTATUS_S_ISNULL, pokud je tato datová hodnota prázdná.
Označení sloupců s možnou hodnotou Null
Pokud také implementujete sady řádků schématu (viz IDBSchemaRowsetImpl
), měla by implementace zadat v sadě řádků DBSCHEMA_COLUMNS (obvykle označená ve vašem poskytovateli CxxxSchemaColSchemaRowset), že sloupec má hodnotu null.
Musíte také určit, že všechny sloupce s možnou GetColumnInfo
hodnotou null obsahují hodnotu DBCOLUMNFLAGS_ISNULLABLE ve vaší verzi .
V implementaci šablon OLE DB, pokud se nepodaří označit sloupce jako null, poskytovatel předpokládá, že musí obsahovat hodnotu a nepovolí příjemci odeslat hodnoty null.
Následující příklad ukazuje, jak CommonGetColInfo
je funkce implementována v CUpdateCommand (viz UpProvRS.cpp) v UpdatePV. Všimněte si, že sloupce mají tento DBCOLUMNFLAGS_ISNULLABLE pro sloupce s možnou hodnotou null.
/////////////////////////////////////////////////////////////////////////////
// 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;
}
Výchozí hodnoty
Stejně jako u dat s hodnotou NULL máte zodpovědnost za to, abyste se zabývali změnou výchozích hodnot.
Výchozí hodnota FlushData
a Execute
je vrátit S_OK. Proto pokud tuto funkci nepřepíšete, změny se zobrazí jako úspěšné (S_OK budou vráceny), ale nepřenesou se do úložiště dat.
V ukázce UpdatePV
(v Rowset.h) SetDBStatus
metoda zpracovává výchozí hodnoty následujícím způsobem:
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;
}
Příznaky sloupců
Pokud ve sloupcích podporujete výchozí hodnoty, musíte ji nastavit pomocí metadat ve <třídě zprostředkovatele>SchemaRowset třídy. Nastavit m_bColumnHasDefault = VARIANT_TRUE
.
Máte také odpovědnost za nastavení příznaků sloupců, které jsou určeny pomocí výčtu DBCOLUMNFLAGS. Příznaky sloupce popisují charakteristiky sloupců.
Například ve CUpdateSessionColSchemaRowset
třídě v UpdatePV
relaci (v Session.h) je první sloupec nastaven tímto způsobem:
// 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]);
Tento kód mimo jiné určuje, že sloupec podporuje výchozí hodnotu 0, že je zapisovatelný a že všechna data ve sloupci mají stejnou délku. Pokud chcete, aby data ve sloupci měla proměnnou délku, nenastavili byste tento příznak.