Partager via


Création d'un fournisseur actualisable

Visual C++ prend en charge les fournisseurs ou fournisseurs pouvant mettre à jour (écrire dans) le magasin de données. Cette rubrique explique comment créer des fournisseurs pouvant être mis à jour à l’aide de modèles OLE DB.

Cette rubrique part du principe que vous commencez par un fournisseur utilisable. Il existe deux étapes pour créer un fournisseur pouvant être mis à jour. Vous devez d’abord décider de la façon dont le fournisseur apporte des modifications au magasin de données ; plus précisément, si des modifications doivent être effectuées immédiatement ou différées jusqu’à ce qu’une commande de mise à jour soit émise. La section « Rendre les fournisseurs pouvant être mis à jour » décrit les modifications et les paramètres que vous devez effectuer dans le code du fournisseur.

Ensuite, vous devez vous assurer que votre fournisseur contient toutes les fonctionnalités permettant de prendre en charge tout ce que le consommateur peut demander. Si le consommateur souhaite mettre à jour le magasin de données, le fournisseur doit contenir du code qui conserve les données dans le magasin de données. Par exemple, vous pouvez utiliser la bibliothèque d’exécution C ou MFC pour effectuer ces opérations sur votre source de données. La section « Écriture dans la source de données » décrit comment écrire dans la source de données, traiter des valeurs NULL et par défaut et définir des indicateurs de colonne.

Remarque

UpdatePV est un exemple de fournisseur pouvant être mis à jour. UpdatePV est identique à MyProv, mais avec une prise en charge pouvant être mise à jour.

Mise à jour des fournisseurs

La clé de la mise à jour d’un fournisseur consiste à comprendre les opérations que vous souhaitez que votre fournisseur effectue sur le magasin de données et la façon dont vous souhaitez que le fournisseur effectue ces opérations. Plus précisément, le problème majeur est de savoir si les mises à jour du magasin de données doivent être effectuées immédiatement ou différées (lot) jusqu’à ce qu’une commande de mise à jour soit émise.

Vous devez d’abord décider s’il faut hériter ou non de votre classe d’ensemble de IRowsetChangeImplIRowsetUpdateImpl lignes. Selon les éléments que vous choisissez d’implémenter, les fonctionnalités de trois méthodes seront affectées : SetData, InsertRowset DeleteRows.

  • Si vous héritez de IRowsetChangeImpl, l’appel de ces trois méthodes modifie immédiatement le magasin de données.

  • Si vous héritez de IRowsetUpdateImpl, les méthodes reportent les modifications apportées au magasin de données jusqu’à ce que vous appeliez Update, GetOriginalDataou Undo. Si la mise à jour implique plusieurs modifications, elles sont effectuées en mode batch (notez que les modifications par lot peuvent ajouter une surcharge de mémoire considérable).

Notez que IRowsetUpdateImpl dérive de IRowsetChangeImpl. Ainsi, IRowsetUpdateImpl vous pouvez modifier la fonctionnalité ainsi que la fonctionnalité de traitement par lots.

Pour prendre en charge la mise à jour dans votre fournisseur

  1. Dans votre classe d’ensemble de lignes, héritez ou IRowsetChangeImplIRowsetUpdateImpl. Ces classes fournissent des interfaces appropriées pour modifier le magasin de données :

    Ajout d’IRowsetChange

    Ajoutez-y IRowsetChangeImpl votre chaîne d’héritage à l’aide de ce formulaire :

    IRowsetChangeImpl< rowset-name, storage-name >
    

    COM_INTERFACE_ENTRY(IRowsetChange) Ajoutez également à la BEGIN_COM_MAP section de votre classe d’ensemble de lignes.

    Ajout d’IRowsetUpdate

    Ajoutez-y IRowsetUpdate votre chaîne d’héritage à l’aide de ce formulaire :

    IRowsetUpdateImpl< rowset-name, storage>
    

    Remarque

    Vous devez supprimer la IRowsetChangeImpl ligne de votre chaîne d’héritage. Cette exception à la directive mentionnée précédemment doit inclure le code pour IRowsetChangeImpl.

  2. Ajoutez ce qui suit à votre carte COM (BEGIN_COM_MAP ... END_COM_MAP) :

    Si vous implémentez Ajouter à la carte COM
    IRowsetChangeImpl COM_INTERFACE_ENTRY(IRowsetChange)
    IRowsetUpdateImpl COM_INTERFACE_ENTRY(IRowsetUpdate)
    Si vous implémentez Ajouter à la carte du jeu de propriétés
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  3. Dans votre commande, ajoutez ce qui suit à votre mappage de jeu de propriétés (BEGIN_PROPSET_MAP ... END_PROPSET_MAP) :

    Si vous implémentez Ajouter à la carte du jeu de propriétés
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  4. Dans la carte de votre jeu de propriétés, vous devez également inclure tous les paramètres suivants, car ils apparaissent ci-dessous :

    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)
    

    Vous trouverez les valeurs utilisées dans ces appels de macro en examinant Atldb.h les ID de propriété et les valeurs (si Atldb.h diffère de la documentation en ligne, Atldb.h remplace la documentation).

    Remarque

    La plupart des paramètres et VARIANT_TRUE des VARIANT_FALSE modèles OLE DB sont requis ; la spécification OLE DB indique qu’elles peuvent être lues/écritures, mais les modèles OLE DB ne peuvent prendre en charge qu’une seule valeur.

    Si vous implémentez IRowsetChangeImpl

    Si vous implémentez IRowsetChangeImpl, vous devez définir les propriétés suivantes sur votre fournisseur. Ces propriétés sont principalement utilisées pour demander des interfaces via ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: la définition de ce paramètre définit DBPROP_IRowsetChangeautomatiquement .

    • DBPROP_UPDATABILITY: masque de bits spécifiant les méthodes prises en charge sur IRowsetChange: SetData, DeleteRowsou InsertRow.

    • DBPROP_CHANGEINSERTEDROWS: le consommateur peut appeler IRowsetChange::DeleteRows ou SetData pour les lignes nouvellement insérées.

    • DBPROP_IMMOBILEROWS: l’ensemble de lignes ne réorganise pas les lignes insérées ou mises à jour.

    Si vous implémentez IRowsetUpdateImpl

    Si vous implémentez IRowsetUpdateImpl, vous devez définir les propriétés suivantes sur votre fournisseur, en plus de définir toutes les propriétés pour IRowsetChangeImpl les propriétés précédemment répertoriées :

    • DBPROP_IRowsetUpdate.

    • DBPROP_OWNINSERT: doit être READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OWNUPDATEDELETE: doit être READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OTHERINSERT: doit être READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OTHERUPDATEDELETE: doit être READ_ONLY AND VARIANT_TRUE.

    • DBPROP_REMOVEDELETED: doit être READ_ONLY AND VARIANT_TRUE.

    • DBPROP_MAXPENDINGROWS.

    Remarque

    Si vous prenez en charge les notifications, vous pouvez également avoir d’autres propriétés ; consultez la section de IRowsetNotifyCP cette liste.

Écriture dans la source de données

Pour lire à partir de la source de données, appelez la Execute fonction. Pour écrire dans la source de données, appelez la FlushData fonction. (Dans un sens général, videz les moyens d’enregistrer les modifications que vous apportez à une table ou à un index sur disque.)

FlushData(HROW, HACCESSOR);

Les arguments de handle de ligne (HROW) et de handle d’accesseur (HACCESSOR) vous permettent de spécifier la région à écrire. En règle générale, vous écrivez un champ de données unique à la fois.

La FlushData méthode écrit des données au format dans lequel elle a été stockée à l’origine. Si vous ne remplacez pas cette fonction, votre fournisseur fonctionne correctement, mais les modifications ne sont pas vidées dans le magasin de données.

Quand vider

Les modèles de fournisseur appellent FlushData chaque fois que les données doivent être écrites dans le magasin de données ; cela se produit généralement (mais pas toujours) à la suite d’appels aux fonctions suivantes :

  • IRowsetChange::DeleteRows

  • IRowsetChange::SetData

  • IRowsetChange::InsertRows (s’il existe de nouvelles données à insérer dans la ligne)

  • IRowsetUpdate::Update

Fonctionnement

Le consommateur effectue un appel qui nécessite un vidage (par exemple, Update) et cet appel est passé au fournisseur, ce qui effectue toujours les opérations suivantes :

  • Appels SetDBStatus chaque fois que vous avez une valeur d’état liée.

  • Vérifie les indicateurs de colonne.

  • Appelle IsUpdateAllowed.

Ces trois étapes aident à assurer la sécurité. Ensuite, le fournisseur appelle FlushData.

Comment implémenter FlushData

Pour implémenter FlushData, vous devez prendre en compte plusieurs problèmes :

Assurez-vous que le magasin de données peut gérer les modifications.

Gestion des valeurs NULL.

Gestion des valeurs par défaut.

Pour implémenter votre propre FlushData méthode, vous devez :

  • Accédez à votre classe d’ensemble de lignes.

  • Dans la classe d’ensemble de lignes, placez la déclaration de :

    HRESULT FlushData(HROW, HACCESSOR)
    {
        // Insert your implementation here and return an HRESULT.
    }
    
  • Fournir une implémentation de FlushData.

Une bonne implémentation des FlushData magasins ne stocke que les lignes et les colonnes qui sont réellement mises à jour. Vous pouvez utiliser les paramètres HROW et HACCESSOR pour déterminer la ligne et la colonne actuelles stockées pour l’optimisation.

En règle générale, le plus grand défi consiste à utiliser votre propre magasin de données natif. Si possible, essayez de :

  • Conservez la méthode d’écriture dans votre magasin de données aussi simple que possible.

  • Gérer les valeurs NULL (facultatives, mais conseillées).

  • Gérer les valeurs par défaut (facultatives mais conseillées).

La meilleure chose à faire consiste à avoir des valeurs spécifiées réelles dans votre magasin de données pour les valeurs NULL et par défaut. Il est préférable d’extrapoler ces données. Si ce n’est pas le cas, il est conseillé de ne pas autoriser les valeurs NULL et par défaut.

L’exemple suivant montre comment FlushData implémenter la RUpdateRowset classe dans l’exemple UpdatePV (voir Rowset.h dans l’exemple de code) :

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

Gestion des modifications

Pour que votre fournisseur gère les modifications, vous devez d’abord vous assurer que votre magasin de données (tel qu’un fichier texte ou un fichier vidéo) dispose d’installations qui vous permettent d’y apporter des modifications. Si ce n’est pas le cas, vous devez créer ce code séparément du projet fournisseur.

Gestion des données NULL

Il est possible qu’un utilisateur final envoie des données NULL. Lorsque vous écrivez des valeurs NULL dans des champs de la source de données, il peut y avoir des problèmes potentiels. Imaginez une application de commande qui accepte des valeurs pour le code postal et la ville ; il pourrait accepter les deux valeurs, mais pas ni l’un ni l’autre, car dans ce cas la livraison serait impossible. Vous devez donc restreindre certaines combinaisons de valeurs NULL dans les champs qui sont logiques pour votre application.

En tant que développeur de fournisseurs, vous devez prendre en compte la façon dont vous allez stocker ces données, la façon dont vous allez lire ces données à partir du magasin de données et la façon dont vous spécifiez cela à l’utilisateur. Plus précisément, vous devez déterminer comment modifier l’état des données de l’ensemble de lignes dans la source de données (par exemple, DataStatus = NULL). Vous décidez de la valeur à retourner lorsqu’un consommateur accède à un champ contenant une valeur NULL.

Examinez le code dans l’exemple UpdatePV ; il illustre comment un fournisseur peut gérer les données NULL. Dans UpdatePV, le fournisseur stocke les données NULL en écrivant la chaîne « NULL » dans le magasin de données. Lorsqu’il lit des données NULL à partir du magasin de données, il voit cette chaîne, puis vide la mémoire tampon, créant une chaîne NULL. Il a également un remplacement dans IRowsetImpl::GetDBStatus lequel il retourne DBSTATUS_S_ISNULL si cette valeur de données est vide.

Marquage de colonnes nullables

Si vous implémentez également des ensembles de lignes de schéma (voir IDBSchemaRowsetImpl), votre implémentation doit spécifier dans l’ensemble de lignes DBSCHEMA_COLUMNS (généralement marqué dans votre fournisseur par CxxxSchemaColSchemaRowset) que la colonne est nullable.

Vous devez également spécifier que toutes les colonnes nullables contiennent la valeur DBCOLUMNFLAGS_ISNULLABLE dans votre version du GetColumnInfo.

Dans l’implémentation des modèles OLE DB, si vous ne parvenez pas à marquer les colonnes comme nullables, le fournisseur suppose qu’il doit contenir une valeur et ne permet pas au consommateur de l’envoyer.

L’exemple suivant montre comment la CommonGetColInfo fonction est implémentée dans CUpdateCommand (voir UpProvRS.cpp) dans UpdatePV. Notez comment les colonnes ont cette DBCOLUMNFLAGS_ISNULLABLE pour les colonnes nullables.

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

Valeurs par défaut

Comme pour les données NULL, vous avez la responsabilité de gérer la modification des valeurs par défaut.

La valeur par défaut FlushData est Execute de retourner S_OK. Par conséquent, si vous ne remplacez pas cette fonction, les modifications semblent réussir (S_OK seront retournées), mais elles ne seront pas transmises au magasin de données.

Dans l’exemple UpdatePV (dans Rowset.h), la SetDBStatus méthode gère les valeurs par défaut comme suit :

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

Indicateurs de colonne

Si vous prenez en charge les valeurs par défaut sur vos colonnes, vous devez la définir à l’aide de métadonnées dans la classe SchemaRowset de la <classe>de fournisseur. Définissez m_bColumnHasDefault = VARIANT_TRUE.

Vous avez également la responsabilité de définir les indicateurs de colonne, qui sont spécifiés à l’aide du type énuméré DBCOLUMNFLAGS. Les indicateurs de colonne décrivent les caractéristiques des colonnes.

Par exemple, dans la CUpdateSessionColSchemaRowset classe UpdatePV dans (dans Session.h), la première colonne est configurée de cette façon :

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

Ce code spécifie, entre autres, que la colonne prend en charge une valeur par défaut de 0, qu’elle est accessible en écriture et que toutes les données de la colonne ont la même longueur. Si vous souhaitez que les données d’une colonne aient une longueur variable, vous ne définissez pas cet indicateur.

Voir aussi

Création d’un fournisseur OLE DB