Criando um provedor atualizável

O Visual C++ dá suporte a provedores ou provedores atualizáveis que podem atualizar (gravar em) o armazenamento de dados. Este tópico discute como criar provedores atualizáveis usando modelos OLE DB.

Este tópico pressupõe que você está começando com um provedor viável. Há duas etapas para criar um provedor atualizável. Primeiro, você deve decidir como o provedor fará alterações no armazenamento de dados; especificamente, se as alterações devem ser feitas imediatamente ou adiadas até que um comando de atualização seja emitido. A seção "Tornando os provedores atualizáveis" descreve as alterações e as configurações que você precisa fazer no código do provedor.

Em seguida, você deve verificar se o provedor contém toda a funcionalidade para dar suporte a qualquer coisa que o consumidor possa solicitar. Se o consumidor quiser atualizar o armazenamento de dados, o provedor precisará conter o código que persiste os dados no armazenamento de dados. Por exemplo, você pode usar a Biblioteca de Runtime C ou MFC para executar essas operações em sua fonte de dados. A seção "Gravando na Fonte de Dados" descreve como gravar na fonte de dados, lidar com valores NULL e padrão e definir sinalizadores de coluna.

Observação

UpdatePV é um exemplo de um provedor atualizável. UpdatePV é o mesmo que MyProv, mas com suporte atualizável.

Tornando os provedores atualizáveis

A chave para tornar um provedor atualizável é entender quais operações você deseja que seu provedor execute no armazenamento de dados e como deseja que o provedor realize essas operações. Especificamente, o principal problema é se as atualizações no armazenamento de dados devem ser feitas imediatamente ou adiadas (em lote) até que um comando de atualização seja emitido.

Primeiro, você deve decidir se deseja herdar de IRowsetChangeImpl ou IRowsetUpdateImpl na classe de conjunto de linhas. Dependendo de qual deles você optar por implementar, a funcionalidade de três métodos será afetada: SetData, InsertRows e DeleteRows.

  • Se você herdar de IRowsetChangeImpl, chamar esses três métodos alterará imediatamente o armazenamento de dados.

  • Se você herdar de IRowsetUpdateImpl, os métodos adiarão as alterações no armazenamento de dados até que você chame Update, GetOriginalData ou Undo. Se a atualização envolver várias alterações, elas serão executadas no modo de lote (observe que as alterações em lote podem adicionar uma sobrecarga de memória considerável).

Observe que IRowsetUpdateImpl deriva de IRowsetChangeImpl. Portanto, IRowsetUpdateImpl fornece a capacidade de alteração mais a capacidade do lote.

Para dar suporte à capacidade de atualização em seu provedor

  1. Na classe de conjunto de linhas, herda de IRowsetChangeImpl ou IRowsetUpdateImpl. Essas classes fornecem interfaces apropriadas para alterar o armazenamento de dados:

    Adicionando IRowsetChange

    Adicione IRowsetChangeImpl à cadeia de herança usando este formulário:

    IRowsetChangeImpl< rowset-name, storage-name >
    

    Adicione também COM_INTERFACE_ENTRY(IRowsetChange) à seção BEGIN_COM_MAP na classe de conjunto de linhas.

    Adicionando IRowsetUpdate

    Adicione IRowsetUpdate à cadeia de herança usando este formulário:

    IRowsetUpdateImpl< rowset-name, storage>
    

    Observação

    Você deve remover a linha IRowsetChangeImpl da cadeia de herança. Esta exceção à diretiva mencionada anteriormente deve incluir o código para IRowsetChangeImpl.

  2. Adicione o seguinte ao mapa COM (BEGIN_COM_MAP ... END_COM_MAP):

    Se você implementar Adicionar ao mapa COM
    IRowsetChangeImpl COM_INTERFACE_ENTRY(IRowsetChange)
    IRowsetUpdateImpl COM_INTERFACE_ENTRY(IRowsetUpdate)
    Se você implementar Adicionar ao mapa do conjunto de propriedades
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  3. No comando, adicione o seguinte ao mapa do conjunto de propriedades (BEGIN_PROPSET_MAP ... END_PROPSET_MAP):

    Se você implementar Adicionar ao mapa do conjunto de propriedades
    IRowsetChangeImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
    IRowsetUpdateImpl PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
  4. No mapa do conjunto de propriedades, você também deve incluir todas as seguintes configurações conforme elas aparecem abaixo:

    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)
    

    Você pode encontrar os valores usados nessas chamadas de macro examinando Atldb.h para as IDs e valores da propriedade (se Atldb.h for diferente da documentação online, Atldb.h substituirá a documentação).

    Observação

    Muitas das configurações VARIANT_FALSE e VARIANT_TRUE são exigidas pelos modelos OLE DB; a especificação OLE DB diz que elas podem ser de leitura/gravação, mas os modelos OLE DB só podem dar suporte a um valor.

    Se você implementar IRowsetChangeImpl

    Se você implementar IRowsetChangeImpl, deverá definir as propriedades a seguir em seu provedor. Essas propriedades são usadas para solicitar interfaces por meio de ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: essa configuração define DBPROP_IRowsetChangeautomaticamente.

    • DBPROP_UPDATABILITY: uma máscara de bits especificando os métodos com suporte em IRowsetChange: SetData, DeleteRows ou InsertRow.

    • DBPROP_CHANGEINSERTEDROWS: o consumidor pode chamar IRowsetChange::DeleteRows ou SetData para linhas recém-inseridas.

    • DBPROP_IMMOBILEROWS: o conjunto de linhas não reorganizará as linhas inseridas ou atualizadas.

    Se você implementar IRowsetUpdateImpl

    Se você implementar IRowsetUpdateImpl, deverá definir as seguintes propriedades em seu provedor, além de definir todas as propriedades para IRowsetChangeImpl listadas anteriormente:

    • DBPROP_IRowsetUpdate.

    • DBPROP_OWNINSERT: deve ser READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OWNUPDATEDELETE: deve ser READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OTHERINSERT: deve ser READ_ONLY AND VARIANT_TRUE.

    • DBPROP_OTHERUPDATEDELETE: deve ser READ_ONLY AND VARIANT_TRUE.

    • DBPROP_REMOVEDELETED: deve ser READ_ONLY AND VARIANT_TRUE.

    • DBPROP_MAXPENDINGROWS.

    Observação

    Se você der suporte a notificações, também poderá ter outras propriedades; consulte a seção IRowsetNotifyCP para esta lista.

Gravando na fonte de dados

Para ler a partir da fonte de dados, chame a função Execute. Para gravar na fonte de dados, chame a função FlushData. (Em um sentido geral, liberar significa salvar modificações feitas em uma tabela ou índice no disco.)

FlushData(HROW, HACCESSOR);

Os argumentos HROW (identificador de linha) e HACCESSOR (identificador de acessador) permitem que você especifique a região a ser gravada. Normalmente, você grava um único campo de dados por vez.

O método FlushData grava dados no formato em que foi armazenado originalmente. Se você não substituir essa função, seu provedor funcionará corretamente, mas as alterações não serão liberadas para o armazenamento de dados.

Quando liberar

Os modelos do provedor chamam FlushData sempre que os dados precisam ser gravados no armazenamento de dados; isso geralmente (mas nem sempre) ocorre como resultado de chamadas para as seguintes funções:

  • IRowsetChange::DeleteRows

  • IRowsetChange::SetData

  • IRowsetChange::InsertRows (se houver novos dados a serem inseridos na linha)

  • IRowsetUpdate::Update

Como funciona

O consumidor faz uma chamada que requer uma liberação (como Atualizar) e essa chamada é passada para o provedor, o que sempre faz o seguinte:

  • Chama SetDBStatus sempre que você tiver um valor de status limitado.

  • Verifica os sinalizadores de coluna.

  • Chama IsUpdateAllowed.

Essas três etapas ajudam a fornecer segurança. Em seguida, o provedor chama FlushData.

Como implementar o FlushData

Para implementar FlushData, você precisa levar em conta várias questões:

Verificar se o armazenamento de dados pode lidar com alterações.

Manipular valores NULL.

Manipular valores padrão.

Para implementar seu próprio método FlushData, você precisa:

  • Ir para a classe de conjunto de linhas.

  • Na classe de conjunto de linhas, coloque a declaração de:

    HRESULT FlushData(HROW, HACCESSOR)
    {
        // Insert your implementation here and return an HRESULT.
    }
    
  • Fornecer uma implementação de FlushData.

Uma boa implementação de FlushData armazena apenas as linhas e colunas que são realmente atualizadas. Você pode usar os parâmetros HROW e HACCESSOR para determinar a linha e a coluna atuais que estão sendo armazenadas para otimização.

Normalmente, o maior desafio é trabalhar com seu próprio armazenamento de dados nativo. Se possível, tente:

  • Manter o método de gravação no armazenamento de dados o mais simples possível.

  • Manipular valores NULL (opcional, mas aconselhado).

  • Manipular valores padrão (opcional, mas aconselhado).

A melhor coisa a fazer é ter valores especificados reais no armazenamento de dados para valores NULL e padrão. É melhor se puder extrapolar esses dados. Caso contrário, você será aconselhado a não permitir valores NULL ou padrão.

O exemplo a seguir mostra como FlushData é implementado na classe RUpdateRowset na amostra UpdatePV (consulte Rowset.h no código de exemplo):

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

Manipulação de alterações

Para que seu provedor manipule as alterações, primeiro você precisa verificar se o armazenamento de dados (como um arquivo de texto ou arquivo de vídeo) tem instalações que permitem que você faça alterações nele. Se não tiver, você deverá criar esse código separadamente do projeto do provedor.

Manipulando dados NULL

É possível que um usuário final envie dados NULL. Quando você gravar valores NULL em campos na fonte de dados, poderá haver possíveis problemas. Imagine um aplicativo de tomada de pedidos que aceita valores para a cidade e o código postal; ele poderia aceitar somente um ou ambos os valores, mas não nenhum dos dois, porque nesse caso a entrega seria impossível. Portanto, você precisa restringir determinadas combinações de valores NULL em campos que façam sentido para seu aplicativo.

Como desenvolvedor do provedor, você deve considerar como armazenar esses dados, como ler esses dados do armazenamento de dados e como especificar isso para o usuário. Especificamente, você deve considerar como alterar o status dos dados do conjunto de linhas na fonte de dados (por exemplo, DataStatus = NULL). Você decide qual valor retornar quando um consumidor acessa um campo que contém um valor NULL.

Examine o código na amostra UpdatePV; ele ilustra como um provedor pode lidar com dados NULL. No UpdatePV, o provedor armazena dados NULL escrevendo a cadeia de caracteres "NULL" no armazenamento de dados. Quando lê dados NULL do armazenamento de dados, ele vê essa cadeia de caracteres e esvazia o buffer, criando uma cadeia de caracteres NULL. Ele também tem uma substituição de IRowsetImpl::GetDBStatus na qual retorna DBSTATUS_S_ISNULL se esse valor de dados estiver vazio.

Marcando colunas anuláveis

Se você também implementar conjuntos de linhas de esquema (veja IDBSchemaRowsetImpl), sua implementação deverá especificar no conjunto de linhas DBSCHEMA_COLUMNS (geralmente marcado em seu provedor por CxxxSchemaColSchemaRowset) que a coluna é anulável.

Você também precisa especificar que todas as colunas anuláveis contêm o valor DBCOLUMNFLAGS_ISNULLABLE na sua versão do GetColumnInfo.

Na implementação de modelos OLE DB, se você não marcar colunas como anuláveis, o provedor pressupõe que elas devem conter um valor e não permitirá que o consumidor envie valores nulos.

O exemplo a seguir mostra como a função CommonGetColInfo é implementada em CUpdateCommand (consulte UpProvRS.cpp) no UpdatePV. Observe como as colunas têm o DBCOLUMNFLAGS_ISNULLABLE para colunas anuláveis.

/////////////////////////////////////////////////////////////////////////////
// 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 padrão

Assim como acontece com os dados NULL, você tem a responsabilidade de lidar com a alteração de valores padrão.

O padrão de FlushData e Execute é retornar S_OK. Portanto, se você não substituir essa função, as alterações parecerão bem-sucedidas (S_OK serão retornadas), mas elas não serão transmitidas para o armazenamento de dados.

Na amostra UpdatePV (em Rowset.h), o método SetDBStatus manipula os valores padrão da seguinte maneira:

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

Sinalizadores de Coluna

Se você oferecer suporte a valores padrão em suas colunas, precisará defini-los usando metadados na classe <provider class>SchemaRowset. Defina m_bColumnHasDefault = VARIANT_TRUE.

Você também tem a responsabilidade de definir os sinalizadores de coluna, que são especificados usando o tipo enumerado DBCOLUMNFLAGS. Os sinalizadores de coluna descrevem as características da coluna.

Por exemplo, na classe CUpdateSessionColSchemaRowset em UpdatePV (em Session.h), a primeira coluna é configurada dessa forma:

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

Esse código especifica, entre outras coisas, que a coluna dá suporte a um valor padrão de 0, que ela pode ser gravável e que todos os dados na coluna têm o mesmo comprimento. Se quiser que os dados em uma coluna tenham comprimento variável, você não definirá esse sinalizador.

Confira também

Criando um provedor do OLE DB