Vytváření aktualizovatelného zprostředkovatele
Jazyk Visual C++ 6.0 podporuje pouze zprostředkovatelé jen pro čtení. Jazyk Visual C++ .NET podporuje aktualizovatelné zprostředkovatele nebo zprostředkovatele, které mohou aktualizovat (zapisovat na) úložiště dat. Toto téma popisuje, jak vytvořit aktualizovatelné zprostředkovatele pomocí šablon technologie OLE DB.
V tomto tématu se předpokládá, že spouštíte funkčního zprostředkovatele. Existují dva kroky k vytvoření aktualizovatelného zprostředkovatele. Musíte se nejprve rozhodnout, jak bude zprostředkovatel provádět změny v úložišti dat; konkrétně zda se mají změny provést okamžitě nebo mají být odloženy až do vydání aktualizačního příkazu. Oddíl "Tvorba aktualizovatelných zprostředkovatelnů" popisuje změny a nastavení, které je třeba provést v kódu zprostředkovatele.
Dále je nutné zajistit, aby Váš zprostředkovatel obsahoval všechny funkce pro podporu všech požadavků, které může vydat příjemce. Pokud chce příjemce aktualizovat úložiště dat, musí zprostředkovatel obsahovat kód, který přenese data do úložiště dat. Například můžete používat běhovou knihovnu jazyka C nebo knihovnu MFC k provádění těchto operací ve vašem zdroji dat. Oddíl "Zapisování do zdroje dat" popisuje, jak zapisovat do zdroje dat, řeší hodnotu NULL a výchozí hodnoty a nastavení příznaku sloupce.
Poznámka
UpdatePV je příkladem aktualizovatelného zprostředkovatele. UpdatePV je stejná jako MyProv, ale s podporou aktualizace.
Tvorba aktualizovatelných zprostředkovatelnů
Klíčem k tvorbě aktualizovatelného zprostředkovatele je pochopení, které operace má Váš zprostředkovatel provádět na úložišti dat a jakým způsobem chcete, aby zprostředkovatel prováděl tyto operace. Konkrétně závažný problém je určení, zda se má provádět aktualizace úložiště dat okamžitě nebo se má odložit (dávkově) až do vydání aktualizačního příkazu.
Je třeba nejprve rozhodnout, zda dědit ve Vaší třídě řádků z IRowsetChangeImpl nebo IRowsetUpdateImpl. Podle toho, kterou z těchto dvou zvolíte pro implementaci, bude ovlivněna funkcionalita tří metod: SetData, InsertRows a DeleteRows.
Pokud dědíte z IRowsetChangeImpl, volání těchto tří metod změní okamžitě úložiště dat.
Pokud dědíte z IRowsetUpdateImpl, odloží metody změny v úložišti dat dokud nezavoláte funkci Update, GetOriginalData nebo funkci Undo. Pokud aktualizace vyžaduje několik změn, provádějí se v dávkovém režimu (všimněte si, že dávkování změn může přidat značnou paměťovou režii).
Všimněte si, že IRowsetUpdateImpl je odvozena z IRowsetChangeImpl. Proto IRowsetUpdateImpl poskytuje možnost změny plus možnost dávky.
Chcete-li podporovat aktualizaci ve Vašem zprostředkovateli
Ve vaší třídě řádků, budete dědit z IRowsetChangeImpl nebo z IRowsetUpdateImpl. Tyto třídy poskytují příslušná rozhraní pro změnu úložiště dat:
Přidání IRowsetChange
Přidejte IRowsetChangeImpl do Vašeho řetězce dědičnosti pomocí tohoto způsobu:
IRowsetChangeImpl< rowset-name, storage-name >
Přidejte také COM_INTERFACE_ENTRY(IRowsetChange) do sekce BEGIN_COM_MAP ve Vaší třídě řádků.
Přidání IRowsetUpdate
Přidejte IRowsetUpdate do Vašeho řetězce dědičnosti pomocí tohoto způsobu:
IRowsetUpdateImpl< rowset-name, storage>
Poznámka
Je třeba odebrat řádek IRowsetChangeImpl z Vašeho řetězce dědičnosti. Jedinou výjimkou je to, že výše zmíněné pokyny musí obsahovat kód pro IRowsetChangeImpl.
Přidejte následující do mapy modelu COM (BEGIN_COM_MAP... END_COM_MAP):
Pokud implementujete
Přidání do mapy modelu COM
IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetChange)COM_INTERFACE_ENTRY(IRowsetUpdate)
Do příkazu přidejte následující do Vaší mapy sady vlastností (BEGIN_PROPSET_MAP... END_PROPSET_MAP):
Pokud implementujete
Přidání 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)
Ve Vaší mapě sady vlastností byste měli také zahrnout všechna následující nastavení tak, jak jsou zobrazeny 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)
Můžete najít hodnoty použité v těchto makro voláních, podíváním se do Atldb.h pro vlastnosti ID a hodnoty (pokud se Atldb.h liší od online dokumentace, nahrazuje Atldb.h dokumentaci).
Poznámka
Mnoho nastavení VARIANT_FALSE a VARIANT_TRUE je vyžadováno šablonami technologie OLE DB; specifikace technologie OLE DB říká, že mohou být pro čtení/zápis, ale šablony technologie OLE DB mohou podporovat pouze jednu hodnotu.
Pokud implementujete IRowsetChangeImpl
Pokud implementujete IRowsetChangeImpl, musíte nastavit následující vlastnosti Vašemu zprostředkovateli. Tyto vlastnosti slouží hlavně k žádostem rozhraní prostřednictvím ICommandProperties::SetProperties.
DBPROP_IRowsetChange: Nastavení v tomto automaticky nastaví DBPROP_IRowsetChange.
DBPROP_UPDATABILITY: Bitová maska podporovaných metod určení na IRowsetChange: SetData, DeleteRows nebo InsertRow.
DBPROP_CHANGEINSERTEDROWS: Spotřebitel může volat IRowsetChange::DeleteRows nebo SetData pro nově vložené řádky.
DBPROP_IMMOBILEROWS: Sada řádků bude pořadí vloženého nebo aktualizované řádky.
Pokud implementujete IRowsetUpdateImpl
Pokud implementujete IRowsetUpdateImpl, je nutné nastavit následující vlastnosti Vašemu zprostředkovateli, navíc nastavte všechny vlastnosti pro IRowsetChangeImpl uvedeny dříve:
DBPROP_IRowsetUpdate.
DBPROP_OWNINSERT: Musí být READ_ONLY A hodnotu VARIANT_TRUE.
DBPROP_OWNUPDATEDELETE: Musí být READ_ONLY A hodnotu VARIANT_TRUE.
DBPROP_OTHERINSERT: Musí být READ_ONLY A hodnotu VARIANT_TRUE.
DBPROP_OTHERUPDATEDELETE: Musí být READ_ONLY A hodnotu VARIANT_TRUE.
DBPROP_REMOVEDELETED: Musí být READ_ONLY A hodnotu VARIANT_TRUE.
DBPROP_MAXPENDINGROWS.
Poznámka
Pokud poskytujete podporu pro oznámení, můžete mít také některé další vlastnosti; tento seznam naleznete v oddílu IRowsetNotifyCP.
Například jak jsou nastaveny vlastnosti, viz mapa sady vlastností v CUpdateCommand (v Rowset.h) v UpdatePV.
Zapisování do zdroje dat
Chcete-li číst ze zdroje dat, zavolejte funkci Execute. Chcete-li zapisovat do zdroje dat, zavolejte funkci FlushData. (V obecném smyslu vyprázdnění (FlushData) znamená uložení změn, které jste provedli, do tabulky nebo indexovat na disk.)
FlushData(HROW, HACCESSOR);
Argumenty popisovač řádku (HROW) a popisovač přístupu (HACCESSOR) umožňují zadat oblast pro zápis. Obvykle zapisujete najednou jedno datové pole .
Metoda FlushData zapisuje data ve formátu, ve kterém byly původně uloženy. Pokud nepřepíšete tuto funkci, bude Váš zprostředkovatel fungovat správně, ale změny nebudou vyprázdňovány do úložiště dat.
Kdy vyprazdňovat
Šablony zprostředkovatele volají FlushData vždy, když je potřeba zapsat data do úložiště dat; k tomu obvykle (ale ne vždy) dochází v důsledku volání následujících funkcí:
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows (jsou-li k dispozici nová data pro vložení řádku)
IRowsetUpdate::Update
Jak to funguje
Příjemce provede volání vyžadující vyprázdnění (například Update) a toto volání je předáno zprostředkovateli, který vždy provádí následující:
Zavolá SetDBStatus vždy, když máte vázanou hodnotu stavu (viz Programátorská reference technologie OLE DB, Kapitola 6, Části dat: Stav).
Zkontrolujte příznaky sloupce.
Volání IsUpdateAllowed.
Tyto tři kroky pomáhají zajistit zabezpečení. Poté zavolá zprostředkovatel metodu FlushData.
Jak implementovat FlushData
K implementaci FlushData, je třeba vzít v úvahu několik problémů:
Ujistěte se, že úložiště dat může zpracovat změny.
Zpracování hodnot NULL.
Zpracování výchozích hodnot.
K implementaci vlastní metody FlushData, je třeba:
Přejděte do Vaší třídy řádků.
Ve třídě rádků umístěte deklaraci:
HRESULT FlushData(HROW, HACCESSOR)
{
// Insert your implementation here and return an HRESULT.
}
- Zajistěte implementaci FlushData.
Správná implementace FlushData ukládá pouze řádky a sloupce, které jsou skutečně aktualizovány. Můžete použít parametry HROW a HACCESSOR k zjištění optimalizace uložení aktuálního řádku a sloupce.
Obvykle největší výzvou je práce s Vašim vlastním nativním úložištěm dat. Pokud je to možné pokuste se o to:
Ponechejte metodu zápisu do Vašeho úložiště co nejjednodušší.
Zpracování hodnot NULL (nepovinné ale doporučené).
Zpracování výchozích hodnot (nepovinné ale doporučené).
Nejlepším krokem je mít skutečné stanovené hodnoty ve Vašem úložišti dat pro hodnotu NULL a výchozí hodnoty. Je nejvhodnější v případě, že můžete odvodit data. Pokud tomu tak není, je doporučeno nepovolit hodnotu NULL a výchozí hodnoty.
Následující příklad ukazuje jak je implementována metoda FlushData ve třídě RUpdateRowset ukázky 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
Ke zpracování změn Vaším zprostředkovatelem, musíte nejprve zkontrolovat, zda má Vaše úložiště dat (například textový soubor nebo videosoubor) prostředky, které v něm umožňují provádět změny. Pokud ne, měli byste vytvořit tento kód odděleně od projektu zprostředkovatele.
Zpracování NULL dat
Je možné, že bude chtít koncový uživatel odeslat NULL data. Když zapíšete hodnoty NULL do polí ve zdroji dat, mohou nastat potenciální problémy. Představte si aplikaci pro objednávky, která přijímá hodnoty pro město a PSČ; aplikace může přijmout buď jednu hodnotu nebo obě dvě hodnoty, ale ne žádnou, protože v takovém případě by nebylo možné dodání. Je proto nutné omezit určité kombinace hodnot NULL v polích, které dávají smysl pro Vaší aplikaci.
Jako vývojář zprostředkovatele musíte zvážit, jak budete ukládat data, jak budete číst tato data z úložiště dat a jak to specifikujete užitaveli. Konkrétně je nutné zvážit, jak změnit stav dat v řádku dat ve zdroji dat (například DataStatus = NULL). Rozhodněte, jakou hodnotu vrátíte, když příjemce přistupuje k poli, obsahující hodnotu NULL.
Podívejte se na kód v ukázce UpdatePV; ukazuje, jak může zprostředkovatel zpracovávat NULL data. V UpdatePV zprostředkovatel ukládá NULL data zápisem řetězce "NULL" v úložišti dat. Když načte NULL data z úložiště dat, uvidí tento řetězec a poté vyprázdní vyrovnávací paměť, vytvořením řetězce NULL. Přepisuje také IRowsetImpl::GetDBStatus ve kterém vrátí DBSTATUS_S_ISNULL, pokud je tato data hodnota prázdná.
Označení nepovinných sloupců
Pokud také implementujete schéma sady řádků (viz IDBSchemaRowsetImpl), měla by Vaše implementace určit v sadě řádků DBSCHEMA_COLUMNS (obvykle označený ve zprostředkovateli CxxxSchemaColSchemaRowset), že je sloupec nepovinný.
Musíte také určit, že všechny nepovinné sloupce obsahují hodnotu DBCOLUMNFLAGS_ISNULLABLE ve vaší verzi GetColumnInfo.
Pokud nedodržíte označení nepovinného sloupce v implementaci šablon technologie OLE DB, zprostředkovatel předpokládá, že musí obsahovat hodnotu a neumožní příjemci zaslání hodnot null.
Následující příklad ukazuje jak je implementována funkce CommonGetColInfo v CUpdateCommand (viz UpProvRS.cpp) v UpdatePV. Všimněte si, označení DBCOLUMNFLAGS_ISNULLABLE u nepovinných sloupců.
/////////////////////////////////////////////////////////////////////////////
// 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
Jako s NULL daty, máte odpovědnost zabývat se změnou výchozích hodnot.
Výchozím pro FlushData a Execute je vrátit S_OK. Proto pokud nepokryjete tuto funkci, změny se zobrazí, že proběhly úspěšně (bude vráceno S_OK), ale nebudou přenášeny do úložiště dat.
Příklad UpdatePV (v Rowset.h), metoda SetDBStatus zpracovává výchozí hodnoty takto:
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 sloupce
Pokud Vaše sloupce podporují výchozí hodnoty, je nutné nastavit použití metadat ve třídě <třída zprostředkovatele>SchemaRowset. Nastavte m_bColumnHasDefault = VARIANT_TRUE.
Musíte také nastavit příznaky sloupce, které jsou určeny pomocí výčtového typu DBCOLUMNFLAGS. Příznaky sloupce popisují charakteristiku sloupce.
Například ve třídě CUpdateSessionColSchemaRowset v UpdatePV (v Session.h) je první sloupec nastaven takto:
// 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 určuje, mimo jiné, ž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ěly různou délku, pak nenastavujte tento příznak.