Crear un proveedor actualizable
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.
Nota:
UpdatePV es un ejemplo de un proveedor actualizable. 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
.
Si hereda de IRowsetChangeImpl, al llamar a estos tres métodos cambia inmediatamente el almacén de datos.
Si hereda de IRowsetUpdateImpl, los métodos aplazan los cambios en el almacén de datos hasta que se llama a
Update
,GetOriginalData
oUndo
. 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
IRowsetChangeImpl
oIRowsetUpdateImpl
. Estas clases proporcionan interfaces adecuadas para cambiar el almacén de datos:Adición de IRowsetChange
Agregue
IRowsetChangeImpl
a la cadena de herencia mediante este formulario:IRowsetChangeImpl< rowset-name, storage-name >
Agregue también
COM_INTERFACE_ENTRY(IRowsetChange)
a la secciónBEGIN_COM_MAP
de la clase del conjunto de filas.Adición de IRowsetUpdate
Agregue
IRowsetUpdate
a la cadena de herencia mediante este formulario:IRowsetUpdateImpl< rowset-name, storage>
Nota:
Debe quitar la línea
IRowsetChangeImpl
de 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 IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetUpdate)
Si implementa Agregar a la asignación del conjunto de propiedades IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_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 IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_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).
Nota:
Muchos de las configuraciones
VARIANT_FALSE
yVARIANT_TRUE
las 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
,DeleteRows
oInsertRow
.DBPROP_CHANGEINSERTEDROWS
: el consumidor puede llamar aIRowsetChange::DeleteRows
oSetData
para 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 deIRowsetChangeImpl
enumeradas 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
.
Nota:
Si admite notificaciones, es posible que también tenga otras propiedades; consulte la sección de
IRowsetNotifyCP
para 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::DeleteRows
IRowsetChange::SetData
IRowsetChange::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
SetDBStatus
cada 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
Tratamiento de los 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;
}
Tratamiento de los cambios
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;
}
Valores predeterminados
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;
}
Marcas de columna
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.