Condividi tramite


Creazione di un provider aggiornabile

Visual C++ supporta provider o provider aggiornabili in grado di aggiornare (scrivere in) l'archivio dati. In questo argomento viene illustrato come creare provider aggiornabili usando modelli OLE DB.

In questo argomento si presuppone che si inizi con un provider utilizzabile. La creazione di un provider aggiornabile prevede due passaggi. È prima necessario decidere come il provider apporta modifiche all'archivio dati; in particolare, se le modifiche devono essere eseguite immediatamente o posticipate fino a quando non viene eseguito un comando di aggiornamento. La sezione "Creazione di provider aggiornabili" descrive le modifiche e le impostazioni da eseguire nel codice del provider.

Successivamente, è necessario assicurarsi che il provider contenga tutte le funzionalità per supportare qualsiasi elemento che il consumer potrebbe richiederlo. Se il consumer vuole aggiornare l'archivio dati, il provider deve contenere codice che rende persistenti i dati nell'archivio dati. Ad esempio, è possibile usare la libreria di runtime C o MFC per eseguire tali operazioni sull'origine dati. La sezione "Scrittura nell'origine dati" descrive come scrivere nell'origine dati, gestire i valori NULL e predefiniti e impostare i flag di colonna.

Nota

UpdatePV è un esempio di provider aggiornabile. UpdatePV è uguale a MyProv, ma con supporto aggiornabile.

Creazione di provider aggiornabili

La chiave per rendere aggiornabile un provider è comprendere le operazioni che il provider deve eseguire nell'archivio dati e come si vuole che il provider esegua tali operazioni. In particolare, il problema principale è se gli aggiornamenti all'archivio dati devono essere eseguiti immediatamente o posticipati (in batch) fino a quando non viene eseguito un comando di aggiornamento.

È innanzitutto necessario decidere se ereditare da IRowsetChangeImpl o IRowsetUpdateImpl nella classe del set di righe. A seconda di quale di queste opzioni si sceglie di implementare, la funzionalità di tre metodi sarà interessata: SetData, InsertRowse DeleteRows.

  • Se si eredita da IRowsetChangeImpl, la chiamata a questi tre metodi modifica immediatamente l'archivio dati.

  • Se si eredita da IRowsetUpdateImpl, i metodi rinviano le modifiche all'archivio dati fino a quando non si chiama Update, GetOriginalDatao Undo. Se l'aggiornamento comporta diverse modifiche, vengono eseguite in modalità batch (si noti che l'invio in batch delle modifiche può comportare un notevole sovraccarico di memoria).

Si noti che IRowsetUpdateImpl deriva da IRowsetChangeImpl. Di conseguenza, IRowsetUpdateImpl offre funzionalità di modifica e funzionalità batch.

Per supportare l'updatabilità nel provider

  1. Nella classe del set di righe ereditare da IRowsetChangeImpl o IRowsetUpdateImpl. Queste classi forniscono interfacce appropriate per la modifica dell'archivio dati:

    Aggiunta di IRowsetChange

    Aggiungere IRowsetChangeImpl alla catena di ereditarietà usando questo modulo:

    IRowsetChangeImpl< rowset-name, storage-name >
    

    COM_INTERFACE_ENTRY(IRowsetChange) Aggiungere anche alla sezione nella BEGIN_COM_MAP classe del set di righe.

    Aggiunta di IRowsetUpdate

    Aggiungere IRowsetUpdate alla catena di ereditarietà usando questo modulo:

    IRowsetUpdateImpl< rowset-name, storage>
    

    Nota

    È necessario rimuovere la IRowsetChangeImpl linea dalla catena di ereditarietà. Questa eccezione alla direttiva menzionata in precedenza deve includere il codice per IRowsetChangeImpl.

  2. Aggiungere quanto segue alla mappa COM (BEGIN_COM_MAP ... END_COM_MAP):

    Se si implementa Aggiungi alla mappa COM
    IRowsetChangeImpl COM_INTERFACE_ENTRY(IRowsetChange)
    IRowsetUpdateImpl COM_INTERFACE_ENTRY(IRowsetUpdate)
    Se si implementa Aggiungi alla mappa del set di proprietà
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  3. Nel comando aggiungere quanto segue alla mappa del set di proprietà (BEGIN_PROPSET_MAP ... END_PROPSET_MAP):

    Se si implementa Aggiungi alla mappa del set di proprietà
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  4. Nella mappa del set di proprietà è necessario includere anche tutte le impostazioni seguenti, come appaiono di seguito:

    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)
    

    È possibile trovare i valori usati in queste chiamate di macro esaminando in Atldb.h gli ID e i valori delle proprietà (se Atldb.h differisce dalla documentazione online, Atldb.h sostituisce la documentazione).

    Nota

    Molte delle VARIANT_FALSE impostazioni e VARIANT_TRUE sono richieste dai modelli OLE DB. La specifica OLE DB indica che possono essere di lettura/scrittura, ma i modelli OLE DB possono supportare un solo valore.

    Se si implementa IRowsetChangeImpl

    Se si implementa IRowsetChangeImpl, è necessario impostare le proprietà seguenti nel provider. Queste proprietà vengono usate principalmente per richiedere interfacce tramite ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: l'impostazione di questa impostazione imposta DBPROP_IRowsetChangeautomaticamente .

    • DBPROP_UPDATABILITY: maschera di bit che specifica i metodi supportati in IRowsetChange: SetData, DeleteRowso InsertRow.

    • DBPROP_CHANGEINSERTEDROWS: il consumer può chiamare IRowsetChange::DeleteRows o SetData per le righe appena inserite.

    • DBPROP_IMMOBILEROWS: il set di righe non riordina le righe inserite o aggiornate.

    Se si implementa IRowsetUpdateImpl

    Se si implementa IRowsetUpdateImpl, è necessario impostare le proprietà seguenti nel provider, oltre a impostare tutte le proprietà per IRowsetChangeImpl elencate in precedenza:

    • DBPROP_IRowsetUpdate.

    • DBPROP_OWNINSERT: deve essere READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OWNUPDATEDELETE: deve essere READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OTHERINSERT: deve essere READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OTHERUPDATEDELETE: deve essere READ_ONLY AND VARIANT_TRUE.

    • DBPROP_REMOVEDELETED: deve essere READ_ONLY AND VARIANT_TRUE.

    • DBPROP_MAXPENDINGROWS.

    Nota

    Se si supportano le notifiche, potrebbero anche essere presenti altre proprietà; vedere la sezione su IRowsetNotifyCP per questo elenco.

Scrittura nell'origine dati

Per leggere dall'origine dati, chiamare la Execute funzione . Per scrivere nell'origine dati, chiamare la FlushData funzione . In generale, flush significa salvare le modifiche apportate a una tabella o un indice su disco.

FlushData(HROW, HACCESSOR);

Gli argomenti handle di riga (HROW) e handle di accesso (HACCESSOR) consentono di specificare l'area da scrivere. In genere, si scrive un singolo campo dati alla volta.

Il FlushData metodo scrive i dati nel formato in cui è stato originariamente archiviato. Se non si esegue l'override di questa funzione, il provider funzionerà correttamente, ma le modifiche non verranno scaricate nell'archivio dati.

Quando scaricare

I modelli di provider chiamano FlushData ogni volta che i dati devono essere scritti nell'archivio dati; questo in genere (ma non sempre) si verifica come risultato di chiamate alle funzioni seguenti:

  • IRowsetChange::DeleteRows

  • IRowsetChange::SetData

  • IRowsetChange::InsertRows (se sono presenti nuovi dati da inserire nella riga)

  • IRowsetUpdate::Update

Come funziona

Il consumer effettua una chiamata che richiede uno scaricamento (ad esempio Update) e questa chiamata viene passata al provider, che esegue sempre le operazioni seguenti:

  • Chiama SetDBStatus ogni volta che è associato un valore di stato.

  • Controlla i flag di colonna.

  • Chiama IsUpdateAllowed.

Questi tre passaggi consentono di garantire la sicurezza. Il provider chiama FlushDataquindi .

Come implementare FlushData

Per implementare FlushData, è necessario tenere conto di diversi problemi:

Assicurarsi che l'archivio dati possa gestire le modifiche.

Gestione dei valori NULL.

Gestione dei valori predefiniti.

Per implementare un metodo personalizzato FlushData , è necessario:

  • Passare alla classe del set di righe.

  • Nella classe rowset inserire la dichiarazione di:

    HRESULT FlushData(HROW, HACCESSOR)
    {
        // Insert your implementation here and return an HRESULT.
    }
    
  • Fornire un'implementazione di FlushData.

Una buona implementazione di FlushData archivia solo le righe e le colonne effettivamente aggiornate. È possibile usare i parametri HROW e HACCESSOR per determinare la riga e la colonna corrente archiviata per l'ottimizzazione.

In genere, la sfida principale consiste nell'usare un archivio dati nativo. Se possibile, provare a:

  • Mantenere il metodo di scrittura nell'archivio dati il più semplice possibile.

  • Gestire i valori NULL (facoltativo ma consigliato).

  • Gestire i valori predefiniti (facoltativo ma consigliato).

La cosa migliore da fare è avere valori specificati effettivi nell'archivio dati per valori NULL e predefiniti. È preferibile estrapolare questi dati. In caso contrario, è consigliabile non consentire valori NULL e predefiniti.

L'esempio seguente illustra come FlushData viene implementato nella RUpdateRowset classe nell'esempio UpdatePV (vedere Rowset.h nel codice di esempio):

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

Gestione delle modifiche

Affinché il provider gestisca le modifiche, è prima necessario assicurarsi che l'archivio dati (ad esempio un file di testo o un file video) disponga di funzionalità che consentono di apportare modifiche. In caso contrario, è necessario creare il codice separatamente dal progetto del provider.

Gestione dei dati NULL

È possibile che un utente finale invii dati NULL. Quando si scrivono valori NULL nei campi nell'origine dati, possono verificarsi potenziali problemi. Si immagini un'applicazione che accetta valori per la città e il codice postale; potrebbe accettare uno o entrambi i valori, ma non nessuno dei due, perché in questo caso il recapito sarebbe impossibile. È quindi necessario limitare determinate combinazioni di valori NULL nei campi che hanno senso per l'applicazione.

In qualità di sviluppatore del provider, è necessario considerare come archiviare tali dati, come leggerli dall'archivio dati e come specificarlo all'utente. In particolare, è necessario considerare come modificare lo stato dei dati dei set di righe nell'origine dati, ad esempio DataStatus = NULL. Si decide quale valore restituire quando un consumer accede a un campo contenente un valore NULL.

Esaminare il codice nell'esempio UpdatePV; illustra come un provider può gestire i dati NULL. In UpdatePV il provider archivia i dati NULL scrivendo la stringa "NULL" nell'archivio dati. Quando legge i dati NULL dall'archivio dati, viene visualizzata tale stringa e quindi svuota il buffer, creando una stringa NULL. Ha anche un override di IRowsetImpl::GetDBStatus in cui restituisce DBSTATUS_S_ISNULL se tale valore di dati è vuoto.

Contrassegno di colonne nullable

Se si implementano anche set di righe dello schema (vedere IDBSchemaRowsetImpl), l'implementazione deve specificare nel set di righe DBSCHEMA_COLUMNS (in genere contrassegnato nel provider da CxxxSchemaColSchemaRowset) che la colonna è nullable.

È anche necessario specificare che tutte le colonne nullable contengano il valore DBCOLUMNFLAGS_ISNULLABLE nella versione di GetColumnInfo.

Nell'implementazione dei modelli OLE DB, se non si riesce a contrassegnare le colonne come nullable, il provider presuppone che contenga un valore e non consentirà al consumer di inviarlo valori Null.

L'esempio seguente mostra come la CommonGetColInfo funzione viene implementata in CUpdateCommand (vedere UpProvRS.cpp) in UpdatePV. Si noti che le colonne hanno questo DBCOLUMNFLAGS_ISNULLABLE per le colonne nullable.

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

Valori predefiniti

Come per i dati NULL, si ha la responsabilità di gestire la modifica dei valori predefiniti.

Il valore predefinito di FlushData e Execute deve restituire S_OK. Pertanto, se non si esegue l'override di questa funzione, le modifiche sembrano avere esito positivo (S_OK verranno restituite), ma non verranno trasmesse all'archivio dati.

Nell'esempio UpdatePV (in Rowset.h), il SetDBStatus metodo gestisce i valori predefiniti come indicato di seguito:

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

Flag di colonna

Se si supportano i valori predefiniti nelle colonne, è necessario impostarlo usando i metadati nella <classe del provider SchemaRowset classe>. Impostare m_bColumnHasDefault = VARIANT_TRUE.

È anche responsabilità dell'utente impostare i flag di colonna, specificati usando il tipo enumerato DBCOLUMNFLAGS. I flag di colonna descrivono le caratteristiche delle colonne.

Ad esempio, nella CUpdateSessionColSchemaRowset classe in UpdatePV (in Session.h), la prima colonna viene configurata in questo modo:

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

Questo codice specifica, tra le altre cose, che la colonna supporta un valore predefinito pari a 0, che sia scrivibile e che tutti i dati nella colonna abbiano la stessa lunghezza. Se si desidera che i dati in una colonna abbiano una lunghezza variabile, non impostare questo flag.

Vedi anche

Creazione di un provider OLE DB