Sdílet prostřednictvím


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, InsertRowsa 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, GetOriginalDatanebo Undo. 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

  1. Ve třídě sady řádků dědí z IRowsetChangeImpl nebo IRowsetUpdateImpl. 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ílu BEGIN_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 pro IRowsetChangeImpl.

  2. Do mapyBEGIN_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)
  3. 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)
  4. 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 a VARIANT_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ím ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: Toto nastavení se automaticky nastaví DBPROP_IRowsetChange.

    • DBPROP_UPDATABILITY: Bitová maska určující podporované metody : IRowsetChangeSetData, DeleteRowsnebo InsertRow.

    • DBPROP_CHANGEINSERTEDROWS: Příjemce může volat IRowsetChange::DeleteRows nebo SetData 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í pro IRowsetChangeImpl 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 FlushDataje 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 GetColumnInfohodnotou 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.

Viz také

Vytvoření zprostředkovatele OLE DB