Compartir a través de


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 o Undo. 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

  1. En la clase del conjunto de filas, herede de IRowsetChangeImpl o IRowsetUpdateImpl. 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ón BEGIN_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 para IRowsetChangeImpl.

  2. 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)
  3. 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)
  4. 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 y VARIANT_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 de ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: al establecer esta propiedad, se establece automáticamente en DBPROP_IRowsetChange.

    • DBPROP_UPDATABILITY: máscara de bits que especifica los métodos admitidos en IRowsetChange: SetData, DeleteRows o InsertRow.

    • DBPROP_CHANGEINSERTEDROWS: el consumidor puede llamar a IRowsetChange::DeleteRows o SetData 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 de IRowsetChangeImpl 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.

Consulte también

Crear un proveedor OLE DB