Tworzenie aktualizowalnego dostawcy

Program Visual C++ obsługuje aktualizowalnych dostawców lub dostawców, którzy mogą aktualizować (zapisywać) magazyn danych. W tym temacie omówiono sposób tworzenia aktualizowalnych dostawców przy użyciu szablonów OLE DB.

W tym temacie założono, że zaczynasz od dostawcy możliwego do działania. Istnieją dwa kroki tworzenia możliwego do zaktualizowania dostawcy. Najpierw musisz zdecydować, w jaki sposób dostawca wprowadzi zmiany w magazynie danych; niezależnie od tego, czy zmiany mają być wykonywane natychmiast, czy odroczone do momentu wydania polecenia aktualizacji. W sekcji "Making Providers Updatable" opisano zmiany i ustawienia, które należy wykonać w kodzie dostawcy.

Następnie należy upewnić się, że dostawca zawiera wszystkie funkcje obsługi wszystkich elementów, których odbiorca może zażądać. Jeśli użytkownik chce zaktualizować magazyn danych, dostawca musi zawierać kod, który utrwala dane w magazynie danych. Na przykład możesz użyć biblioteki czasu wykonywania języka C lub MFC do wykonywania takich operacji na źródle danych. W sekcji "Zapisywanie w źródle danych" opisano, jak zapisywać w źródle danych, zajmować się wartościami NULL i wartościami domyślnymi oraz ustawiać flagi kolumn.

Uwaga

UpdatePV jest przykładem możliwego do zaktualizowania dostawcy. Protokół UpdatePV jest taki sam jak MyProv, ale z obsługą aktualizowalną.

Tworzenie dostawców z możliwością aktualizowania

Kluczem do aktualizowania dostawcy jest zrozumienie, jakie operacje mają być wykonywane przez dostawcę w magazynie danych oraz sposób wykonywania tych operacji przez dostawcę. W szczególności głównym problemem jest to, czy aktualizacje magazynu danych mają być wykonywane natychmiast, czy odroczone (wsadowe) do momentu wydania polecenia aktualizacji.

Najpierw musisz zdecydować, czy dziedziczyć z IRowsetChangeImpl klasy zestawu wierszy, czy IRowsetUpdateImpl też z nich. W zależności od tego, które z tych metod należy zaimplementować, działanie trzech metod będzie miało wpływ na: SetData, InsertRowsi DeleteRows.

  • Jeśli dziedziczysz z IRowsetChangeImpl, wywołanie tych trzech metod natychmiast zmienia magazyn danych.

  • Jeśli dziedziczysz z IRowsetUpdateImpl, metody odroczy zmiany w magazynie danych do momentu wywołania Updatemetody , GetOriginalDatalub Undo. Jeśli aktualizacja obejmuje kilka zmian, są one wykonywane w trybie wsadowym (należy pamiętać, że zmiany wsadowe mogą zwiększyć obciążenie pamięci).

Należy pamiętać, że IRowsetUpdateImpl pochodzi z klasy IRowsetChangeImpl. IRowsetUpdateImpl W związku z tym zapewnia możliwość zmiany oraz możliwości wsadowe.

Aby zapewnić aktualność dostawcy

  1. W klasie zestawu wierszy dziedzicz z IRowsetChangeImpl klasy lub IRowsetUpdateImpl. Te klasy zapewniają odpowiednie interfejsy do zmiany magazynu danych:

    Dodawanie elementu IRowsetChange

    Dodaj IRowsetChangeImpl do łańcucha dziedziczenia przy użyciu tego formularza:

    IRowsetChangeImpl< rowset-name, storage-name >
    

    COM_INTERFACE_ENTRY(IRowsetChange) Dodaj również do BEGIN_COM_MAP sekcji w klasie zestawu wierszy.

    Dodawanie elementu IRowsetUpdate

    Dodaj IRowsetUpdate do łańcucha dziedziczenia przy użyciu tego formularza:

    IRowsetUpdateImpl< rowset-name, storage>
    

    Uwaga

    Należy usunąć IRowsetChangeImpl wiersz z łańcucha dziedziczenia. Ten jeden wyjątek od wspomnianej wcześniej dyrektywy musi zawierać kod .IRowsetChangeImpl

  2. Dodaj następujące elementy do mapy MODELU COM (BEGIN_COM_MAP ... END_COM_MAP):

    W przypadku implementacji Dodaj do mapy MODELU COM
    IRowsetChangeImpl COM_INTERFACE_ENTRY(IRowsetChange)
    IRowsetUpdateImpl COM_INTERFACE_ENTRY(IRowsetUpdate)
    W przypadku implementacji Dodaj do mapy zestawu właściwości
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  3. W poleceniu dodaj następujące elementy do mapy zestawu właściwości (BEGIN_PROPSET_MAP ... END_PROPSET_MAP):

    W przypadku implementacji Dodaj do mapy zestawu właściwości
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  4. Na mapie zestawu właściwości należy również uwzględnić wszystkie następujące ustawienia, jak pokazano poniżej:

    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)
    

    Wartości używane w tych wywołaniach makr można znaleźć w pliku Atldb.h dla identyfikatorów właściwości i wartości (jeśli atldb.h różni się od dokumentacji online, atldb.h zastępuje dokumentację).

    Uwaga

    VARIANT_FALSE Wiele ustawień i VARIANT_TRUE jest wymaganych przez szablony OLE DB. Specyfikacja OLE DB mówi, że mogą być odczytywane/zapisywane, ale szablony OLE DB mogą obsługiwać tylko jedną wartość.

    W przypadku implementacji interfejsu IRowsetChangeImpl

    W przypadku implementacji IRowsetChangeImplprogramu należy ustawić następujące właściwości dostawcy. Te właściwości są używane głównie do żądania interfejsów za pośrednictwem .ICommandProperties::SetProperties

    • DBPROP_IRowsetChange: Ustawienie tego ustawienia automatycznie ustawia wartość DBPROP_IRowsetChange.

    • DBPROP_UPDATABILITY: Maska bitów określająca obsługiwane metody na : IRowsetChangeSetData, DeleteRowslub InsertRow.

    • DBPROP_CHANGEINSERTEDROWS: Użytkownik może wywołać IRowsetChange::DeleteRows lub SetData dla nowo wstawionych wierszy.

    • DBPROP_IMMOBILEROWS: Zestaw wierszy nie zmieni kolejności wstawionych ani zaktualizowanych wierszy.

    W przypadku implementowania interfejsu IRowsetUpdateImpl

    Jeśli zaimplementujesz IRowsetUpdateImplprogram , musisz ustawić następujące właściwości u dostawcy, oprócz ustawienia wszystkich właściwości dla IRowsetChangeImpl poprzednio wymienionych:

    • DBPROP_IRowsetUpdate.

    • DBPROP_OWNINSERT: musi być READ_ONLY i VARIANT_TRUE.

    • DBPROP_OWNUPDATEDELETE: musi być READ_ONLY i VARIANT_TRUE.

    • DBPROP_OTHERINSERT: musi być READ_ONLY i VARIANT_TRUE.

    • DBPROP_OTHERUPDATEDELETE: musi być READ_ONLY i VARIANT_TRUE.

    • DBPROP_REMOVEDELETED: musi być READ_ONLY i VARIANT_TRUE.

    • DBPROP_MAXPENDINGROWS.

    Uwaga

    Jeśli obsługujesz powiadomienia, możesz również mieć inne właściwości; zobacz sekcję na IRowsetNotifyCP tej liście.

Zapisywanie w źródle danych

Aby odczytać ze źródła danych, wywołaj Execute funkcję . Aby zapisać w źródle danych, wywołaj FlushData funkcję . (Ogólnie rzecz biorąc, opróżnianie oznacza zapisanie modyfikacji w tabeli lub indeksie na dysku).

FlushData(HROW, HACCESSOR);

Argumenty uchwytu wiersza (HROW) i uchwytu dostępu (HACCESSOR) umożliwiają określenie regionu do zapisu. Zazwyczaj jedno pole danych jest zapisywane jednocześnie.

Metoda FlushData zapisuje dane w formacie, w którym pierwotnie był przechowywany. Jeśli ta funkcja nie zostanie zastąpiona, dostawca będzie działać poprawnie, ale zmiany nie zostaną opróżnione do magazynu danych.

Kiedy należy opróżnić

Szablony dostawców wywołają funkcję FlushData za każdym razem, gdy dane muszą być zapisywane w magazynie danych; zwykle (ale nie zawsze) występuje w wyniku wywołań do następujących funkcji:

  • IRowsetChange::DeleteRows

  • IRowsetChange::SetData

  • IRowsetChange::InsertRows (jeśli istnieją nowe dane do wstawienia w wierszu)

  • IRowsetUpdate::Update

Zasady działania

Użytkownik wykonuje wywołanie, które wymaga opróżnienia (takiego jak aktualizacja), a to wywołanie jest przekazywane do dostawcy, co zawsze wykonuje następujące czynności:

  • Wywołuje wywołania SetDBStatus za każdym razem, gdy masz powiązaną wartość stanu.

  • Sprawdza flagi kolumn.

  • Wywołuje IsUpdateAllowed.

Te trzy kroki pomagają zapewnić bezpieczeństwo. Następnie dostawca wywołuje metodę FlushData.

How to Implement FlushData

Aby zaimplementować usługę FlushData, należy wziąć pod uwagę kilka problemów:

Upewnij się, że magazyn danych może obsługiwać zmiany.

Obsługa wartości NULL.

Obsługa wartości domyślnych.

Aby zaimplementować własną FlushData metodę, musisz:

  • Przejdź do klasy zestawu wierszy.

  • W klasie zestawu wierszy umieść deklarację:

    HRESULT FlushData(HROW, HACCESSOR)
    {
        // Insert your implementation here and return an HRESULT.
    }
    
  • Podaj implementację elementu FlushData.

Dobra implementacja FlushData magazynów zawiera tylko wiersze i kolumny, które są rzeczywiście aktualizowane. Możesz użyć parametrów HROW i HACCESSOR, aby określić bieżący wiersz i kolumnę przechowywaną do optymalizacji.

Zazwyczaj największym wyzwaniem jest praca z własnym natywnym magazynem danych. Jeśli to możliwe, spróbuj wykonać:

  • Zachowaj metodę zapisywania w magazynie danych tak proste, jak to możliwe.

  • Obsługa wartości NULL (opcjonalne, ale zalecane).

  • Obsługa wartości domyślnych (opcjonalne, ale zalecane).

Najlepszym rozwiązaniem jest posiadanie rzeczywistych określonych wartości w magazynie danych dla wartości NULL i wartości domyślnych. Najlepszym rozwiązaniem jest ekstrapolowanie tych danych. Jeśli nie, zaleca się, aby nie zezwalać na wartości NULL i wartości domyślne.

Poniższy przykład przedstawia sposób FlushData implementacji RUpdateRowset w klasie w przykładzie UpdatePV (zobacz Rowset.h w przykładowym kodzie):

///////////////////////////////////////////////////////////////////////////
// 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;
}

Obsługa zmian

Aby dostawca obsługiwał zmiany, należy najpierw upewnić się, że magazyn danych (taki jak plik tekstowy lub plik wideo) ma narzędzia umożliwiające wprowadzanie w nim zmian. Jeśli tak nie jest, należy utworzyć ten kod oddzielnie od projektu dostawcy.

Obsługa danych NULL

Istnieje możliwość, że użytkownik końcowy wyśle dane o wartości NULL. Podczas zapisywania wartości NULL w polach w źródle danych mogą występować potencjalne problemy. Wyobraź sobie aplikację do przyjmowania zamówień, która akceptuje wartości dla miasta i kodu pocztowego; może zaakceptować albo obie wartości, ale nie, ponieważ w takim przypadku dostawa byłaby niemożliwa. W związku z tym należy ograniczyć pewne kombinacje wartości NULL w polach, które mają sens dla aplikacji.

Jako deweloper dostawcy musisz wziąć pod uwagę sposób przechowywania tych danych, sposobu odczytywania tych danych z magazynu danych oraz sposobu określania ich użytkownikowi. W szczególności należy rozważyć zmianę stanu danych zestawu wierszy w źródle danych (na przykład DataStatus = NULL). Decydujesz, jaka wartość ma być zwracana, gdy użytkownik uzyskuje dostęp do pola zawierającego wartość NULL.

Przyjrzyj się kodowi w przykładzie UpdatePV; Ilustruje to, jak dostawca może obsługiwać dane NULL. W usłudze UpdatePV dostawca przechowuje dane o wartości NULL, zapisując ciąg "NULL" w magazynie danych. Gdy odczytuje dane o wartości NULL z magazynu danych, zobaczy ten ciąg, a następnie opróżni bufor, tworząc ciąg NULL. Ma również przesłonięcia IRowsetImpl::GetDBStatus , w którym zwraca DBSTATUS_S_ISNULL, jeśli ta wartość danych jest pusta.

Oznaczanie kolumn dopuszczanych do wartości null

Jeśli implementujesz również zestawy wierszy schematu (zobacz IDBSchemaRowsetImpl), implementacja powinna być określona w zestawie wierszy DBSCHEMA_COLUMNS (zwykle oznaczonym u dostawcy przez CxxxSchemaColSchemaRowset), że kolumna ma wartość null.

Należy również określić, że wszystkie kolumny dopuszczające wartość null zawierają wartość DBCOLUMNFLAGS_ISNULLABLE w wersji elementu GetColumnInfo.

W implementacji szablonów OLE DB, jeśli nie można oznaczyć kolumn jako dopuszczających wartość null, dostawca zakłada, że musi zawierać wartość i nie zezwoli użytkownikowi na wysyłanie jej wartości null.

W poniższym przykładzie pokazano, jak CommonGetColInfo funkcja jest implementowana w CUpdateCommand (zobacz UpProvRS.cpp) w updatePV. Zwróć uwagę, jak kolumny mają tę DBCOLUMNFLAGS_ISNULLABLE dla kolumn dopuszczanych do wartości 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;
}

Wartości domyślne

Podobnie jak w przypadku danych NULL, ponosisz odpowiedzialność za zmianę wartości domyślnych.

Wartością domyślną FlushData i Execute jest zwracanie S_OK. W związku z tym, jeśli ta funkcja nie zostanie zastąpiona, zmiany pojawią się pomyślnie (S_OK zostaną zwrócone), ale nie zostaną przesłane do magazynu danych.

W przykładzie (w pliku UpdatePV Rowset.h) SetDBStatus metoda obsługuje wartości domyślne w następujący sposób:

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;
}

Flagi kolumn

Jeśli w kolumnach są obsługiwane wartości domyślne, należy ustawić je przy użyciu metadanych w <klasie>provider Class SchemaRowset. Ustaw wartość m_bColumnHasDefault = VARIANT_TRUE.

Masz również obowiązek ustawić flagi kolumn, które są określone przy użyciu wyliczonego typu DBCOLUMNFLAGS. Flagi kolumn opisują charakterystykę kolumn.

Na przykład w CUpdateSessionColSchemaRowset klasie w ( UpdatePV w session.h) pierwsza kolumna jest skonfigurowana w następujący sposób:

// 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]);

Ten kod określa między innymi, że kolumna obsługuje domyślną wartość 0, którą można zapisywać, a wszystkie dane w kolumnie mają taką samą długość. Jeśli chcesz, aby dane w kolumnie miały zmienną długość, nie ustawisz tej flagi.

Zobacz też

Tworzenie dostawcy OLE DB