Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Visual C++ admite proveedores actualizables o proveedores que pueden actualizar el almacén de datos (escribir en él). En este tema se describe cómo crear proveedores actualizables mediante plantillas OLE DB.
En este tema se supone que está empezando por un proveedor aplicable. Hay dos pasos para crear un proveedor actualizable. Primero debe decidir cómo realizará el proveedor los cambios en el almacén de datos; en concreto, si se van a realizar cambios inmediatamente o si se aplazarán hasta que se emita un comando de actualización. En la sección "Creación de proveedores actualizables" se describen los cambios y la configuración que debe realizar en el código del proveedor.
A continuación, debe asegurarse de que el proveedor contiene toda la funcionalidad para admitir todo lo que el consumidor pueda solicitar. Si el consumidor quiere actualizar el almacén de datos, el proveedor debe contener código que conserve los datos en el almacén de datos. Por ejemplo, puede usar la biblioteca en tiempo de ejecución de C o MFC para realizar estas operaciones en el origen de datos. En la sección "Escribir en el origen de datos" se describe cómo escribir en el origen de datos, tratar con valores NULL y predeterminados y establecer marcas de columna.
Note
UpdatePV is an example of an updatable provider. UpdatePV es el mismo que MyProv, pero con compatibilidad actualizable.
Creación de proveedores actualizables
La clave para que un proveedor sea actualizable es comprender qué operaciones quiere que realice el proveedor en el almacén de datos y cómo desea que el proveedor realice esas operaciones. En concreto, el problema principal es si las actualizaciones del almacén de datos deben realizarse inmediatamente o aplazarse (por lotes) hasta que se emita un comando de actualización.
Primero debe decidir si hereda de IRowsetChangeImpl o IRowsetUpdateImpl en la clase de conjunto de filas. Dependiendo de cuál de estos elija implementar, la funcionalidad de tres métodos se verá afectada: SetData, InsertRows y DeleteRows.
If you inherit from IRowsetChangeImpl, calling these three methods immediately changes the data store.
If you inherit from IRowsetUpdateImpl, the methods defer changes to the data store until you call
Update,GetOriginalData, orUndo. Si la actualización implica varios cambios, se realizan en modo por lotes (tenga en cuenta que los cambios de procesamiento por lotes pueden agregar una sobrecarga considerable de memoria).
Observe que IRowsetUpdateImpl se deriva de IRowsetChangeImpl. Por lo tanto, IRowsetUpdateImpl proporciona funcionalidad de cambio más funcionalidad por lotes.
Para admitir la capacidad de actualización en el proveedor
En la clase del conjunto de filas, herede de
IRowsetChangeImploIRowsetUpdateImpl. Estas clases proporcionan interfaces adecuadas para cambiar el almacén de datos:Adding IRowsetChange
Agregue
IRowsetChangeImpla la cadena de herencia mediante este formulario:IRowsetChangeImpl< rowset-name, storage-name >Agregue también
COM_INTERFACE_ENTRY(IRowsetChange)a la secciónBEGIN_COM_MAPde la clase del conjunto de filas.Adding IRowsetUpdate
Agregue
IRowsetUpdatea la cadena de herencia mediante este formulario:IRowsetUpdateImpl< rowset-name, storage>Note
Debe quitar la línea
IRowsetChangeImplde la cadena de herencia. Esta única excepción a la directiva mencionada anteriormente debe incluir el código paraIRowsetChangeImpl.Agregue lo siguiente a la asignación COM (
BEGIN_COM_MAP ... END_COM_MAP):Si implementa Agregar a la asignación COM IRowsetChangeImplCOM_INTERFACE_ENTRY(IRowsetChange)IRowsetUpdateImplCOM_INTERFACE_ENTRY(IRowsetUpdate)Si implementa Agregar a la asignación del conjunto de propiedades IRowsetChangeImplPROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)IRowsetUpdateImplPROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)En el comando, agregue lo siguiente a la asignación del conjunto de propiedades (
BEGIN_PROPSET_MAP ... END_PROPSET_MAP):Si implementa Agregar a la asignación del conjunto de propiedades IRowsetChangeImplPROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)IRowsetUpdateImplPROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)En la asignación del conjunto de propiedades, también debe incluir todos los valores siguientes, como aparecen a continuación:
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)Puede encontrar los valores utilizados en estas llamadas a macros buscando en Atldb.h los identificadores y valores de las propiedades (si Atldb.h difiere de la documentación en línea, Atldb.h tiene prioridad sobre la documentación).
Note
Muchos de las configuraciones
VARIANT_FALSEyVARIANT_TRUElas requieren las plantillas de OLE DB; la especificación OLE DB dice que pueden ser de lectura/escritura, pero las plantillas OLE DB solo pueden admitir un valor.Si implementa IRowsetChangeImpl
Si implementa
IRowsetChangeImpl, debe establecer las siguientes propiedades en el proveedor. Estas propiedades se utilizan principalmente para solicitar interfaces a través deICommandProperties::SetProperties.DBPROP_IRowsetChange: al establecer esta propiedad, se establece automáticamente enDBPROP_IRowsetChange.DBPROP_UPDATABILITY: máscara de bits que especifica los métodos admitidos enIRowsetChange:SetData,DeleteRowsoInsertRow.DBPROP_CHANGEINSERTEDROWS: el consumidor puede llamar aIRowsetChange::DeleteRowsoSetDatapara las filas recién insertadas.DBPROP_IMMOBILEROWS: el conjunto de filas no reordenará las filas insertadas o actualizadas.
Si implementa IRowsetUpdateImpl
Si implementa
IRowsetUpdateImpl, debe establecer las siguientes propiedades en el proveedor, además de establecer todas las propiedades deIRowsetChangeImplenumeradas anteriormente:DBPROP_IRowsetUpdate.DBPROP_OWNINSERT: debe ser READ_ONLY Y VARIANT_TRUE.DBPROP_OWNUPDATEDELETE: debe ser READ_ONLY Y VARIANT_TRUE.DBPROP_OTHERINSERT: debe ser READ_ONLY Y VARIANT_TRUE.DBPROP_OTHERUPDATEDELETE: debe ser READ_ONLY Y VARIANT_TRUE.DBPROP_REMOVEDELETED: debe ser READ_ONLY Y VARIANT_TRUE.DBPROP_MAXPENDINGROWS.
Note
Si admite notificaciones, es posible que también tenga otras propiedades; consulte la sección de
IRowsetNotifyCPpara esta lista.
Escritura en el origen de datos
Para leer desde el origen de datos, llame a la función Execute. Para escribir en el origen de datos, llame a la función FlushData. (En un sentido general, el vaciado significa guardar las modificaciones que realice en una tabla o índice en el disco).
FlushData(HROW, HACCESSOR);
Los argumentos del manipulador de fila (HROW) y del descriptor de acceso (HACCESSOR) permiten especificar la región en la que se va a escribir. Normalmente, se escribe un único campo de datos a la vez.
El método FlushData escribe datos en el formato en el que se almacenó originalmente. Si no invalida esta función, el proveedor funcionará correctamente, pero los cambios no se vaciarán en el almacén de datos.
Cuándo vaciar
Las plantillas de proveedor llaman a FlushData cada vez que es necesario escribir datos en el almacén de datos; esto suele ocurrir (pero no siempre) como resultado de las llamadas a las funciones siguientes:
IRowsetChange::DeleteRowsIRowsetChange::SetDataIRowsetChange::InsertRows(si hay nuevos datos para insertar en la fila)IRowsetUpdate::Update
Cómo funciona
El consumidor realiza una llamada que requiere un vaciado (como Update) y esta llamada se pasa al proveedor, que siempre hace lo siguiente:
Llama a
SetDBStatuscada vez que tenga un valor de estado enlazado.Comprueba las marcas de columna.
Llama a
IsUpdateAllowed.
Estos tres pasos ayudan a proporcionar seguridad. A continuación, el proveedor llama a FlushData.
Implementación de FlushData
Para implementar FlushData, debe tener en cuenta varios problemas:
Asegúrese de que el almacén de datos puede controlar los cambios.
Tratamiento de los valores NULL
Control de valores predeterminados
Para implementar su propio método FlushData, debe:
Ir a la clase del conjunto de filas.
En la clase de conjunto de filas, coloque la declaración de:
HRESULT FlushData(HROW, HACCESSOR) { // Insert your implementation here and return an HRESULT. }Proporcionar una implementación de
FlushData.
Una buena implementación de FlushData almacena solo las filas y columnas que se actualizan realmente. Puede usar los parámetros HROW y HACCESSOR para determinar la fila y la columna actuales que se almacenan para la optimización.
Normalmente, el mayor desafío es trabajar con su propio almacén de datos nativo. Si es posible, intente lo siguiente:
Mantenga el método de escritura en el almacén de datos lo más sencillo posible.
Controle los valores NULL (opcional pero recomendado).
Controle los valores predeterminados (opcional pero recomendado).
Lo mejor es tener valores reales especificados en su almacén de datos para los valores NULL y predeterminados. Es mejor si puede extrapolar estos datos. Si no es así, se recomienda no permitir valores NULL y predeterminados.
En el ejemplo siguiente se muestra cómo FlushData se implementa en la clase RUpdateRowset del ejemplo UpdatePV (vea Rowset.h en el código de ejemplo):
///////////////////////////////////////////////////////////////////////////
// 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;
}
Handling Changes
Para que el proveedor controle los cambios, primero debe asegurarse de que el almacén de datos (por ejemplo, un archivo de texto o un archivo de vídeo) tiene disposiciones que le permiten realizar cambios en él. Si no es así, debe crear ese código por separado del proyecto de proveedor.
Tratamiento de datos NULL
Es posible que un usuario final envíe datos NULL. Al escribir valores NULL en campos del origen de datos, pueden producirse problemas. Imagínese una aplicación de toma de pedidos que acepte valores para la ciudad y el código postal; podría aceptar uno o ambos valores, pero no ninguno, porque en ese caso la entrega sería imposible. Por lo tanto, tiene que restringir ciertas combinaciones de valores NULL en los campos que tienen sentido para su aplicación.
Como desarrollador del proveedor, debe tener en cuenta cómo almacenará esos datos, cómo leerá esos datos desde el almacén de datos y cómo lo especificará para el usuario. En concreto, debe considerar cómo cambiar el estado de los datos del conjunto de filas en el origen de datos (por ejemplo, DataStatus = NULL). Decide qué valor devolver cuando un consumidor accede a un campo que contiene un valor NULL.
Examine el código en el ejemplo UpdatePV; muestra cómo un proveedor puede controlar los datos NULL. En UpdatePV, el proveedor almacena datos NULL escribiendo la cadena "NULL" en el almacén de datos. Cuando lee datos NULL del almacén de datos, ve esa cadena y, a continuación, vacía el búfer, creando una cadena NULL. También tiene una invalidación de IRowsetImpl::GetDBStatus en la que devuelve DBSTATUS_S_ISNULL si ese valor de datos está vacío.
Marcado de columnas que admiten un valor NULL
Si también implementa conjuntos de filas de esquema (consulte IDBSchemaRowsetImpl), la implementación debe especificar en el conjunto de filas DBSCHEMA_COLUMNS (normalmente marcado en el proveedor por CxxxSchemaColSchemaRowset) que la columna acepta valores NULL.
También debe especificar que todas las columnas que admiten valores NULL contengan el valor DBCOLUMNFLAGS_ISNULLABLE en la ersión de GetColumnInfo.
En la implementación de plantillas de OLE DB, si no se puede marcar las columnas como que admiten valores NULL, el proveedor asume que debe contener un valor y no permitirá al consumidor enviar valores NULL.
En el ejemplo siguiente se muestra cómo se implementa la función CommonGetColInfo en CUpdateCommand (vea UpProvRS.cpp) en UpdatePV. Observe cómo las columnas tienen este valor DBCOLUMNFLAGS_ISNULLABLE para las columnas que admiten valores 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;
}
Default Values
Al igual que con los datos NULL, tiene la responsabilidad de tratar con el cambio de valores predeterminados.
El valor predeterminado de FlushData y Execute es devolver S_OK. Por lo tanto, si no invalida esta función, los cambios parecen ser correctos (se devolverá S_OK), pero no se transmitirán al almacén de datos.
En el ejemplo UpdatePV (en Rowset.h), el método SetDBStatus controla los valores predeterminados de la siguiente manera:
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;
}
Column Flags
Si admite valores predeterminados en las columnas, debe establecerlos mediante metadatos en la clase SchemaRowset de la <clase de proveedor>. Establezca m_bColumnHasDefault = VARIANT_TRUE.
También tiene la responsabilidad de establecer las marcas de columna, que se especifican mediante el tipo enumerado DBCOLUMNFLAGS. Las marcas de columna describen las características de las columnas.
Por ejemplo, en la clase CUpdateSessionColSchemaRowset de UpdatePV (en Session.h), la primera columna se configura de esta manera:
// 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]);
Este código especifica, entre otras cosas, que la columna admite un valor predeterminado de 0, que se puede escribir y que todos los datos de la columna tienen la misma longitud. Si desea que los datos de una columna tengan una longitud variable, no establecería esta marca.