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 IRowsetChangeImpl
IRowsetUpdateImpl
lignes. Selon les éléments que vous choisissez d’implémenter, les fonctionnalités de trois méthodes seront affectées : SetData
, InsertRows
et 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
,GetOriginalData
ouUndo
. 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
Dans votre classe d’ensemble de lignes, héritez ou
IRowsetChangeImpl
IRowsetUpdateImpl
. 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 à laBEGIN_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 pourIRowsetChangeImpl
.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)
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)
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
desVARIANT_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 viaICommandProperties::SetProperties
.DBPROP_IRowsetChange
: la définition de ce paramètre définitDBPROP_IRowsetChange
automatiquement .DBPROP_UPDATABILITY
: masque de bits spécifiant les méthodes prises en charge surIRowsetChange
:SetData
,DeleteRows
ouInsertRow
.DBPROP_CHANGEINSERTEDROWS
: le consommateur peut appelerIRowsetChange::DeleteRows
ouSetData
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 pourIRowsetChangeImpl
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.