Partilhar via


Criando um provedor atualizável

O Visual C++ oferece suporte a provedores atualizáveis ou provedores 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ê esteja começando com um provedor viável. Há duas etapas para criar um provedor atualizável. Você deve primeiro 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 update seja emitido. A seção "Tornando os provedores atualizáveis" descreve as alterações e configurações que você precisa fazer no código do provedor.

Em seguida, deve certificar-se de que o seu fornecedor contém todas as funcionalidades para suportar qualquer coisa que o consumidor lhe possa solicitar. Se o consumidor quiser atualizar o armazenamento de dados, o provedor deve conter um código que persista os dados no armazenamento de dados. Por exemplo, você pode usar a Biblioteca de Run-Time 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.

Note

UpdatePV is an example of an updatable provider. 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 você deseja que o provedor realize essas operações. Especificamente, a principal questão é se as atualizações para o armazenamento de dados devem ser feitas imediatamente ou adiadas (em lote) até que um comando update seja emitido.

Primeiro, você deve decidir se deseja herdar de ou IRowsetUpdateImpl em sua classe de conjunto de IRowsetChangeImpl linhas. Dependendo de qual deles você escolher implementar, a funcionalidade de três métodos será afetada: SetData, InsertRows, e 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, or Undo. Se a atualização envolver várias alterações, elas serão executadas em modo de lote (observe que as alterações em lote podem adicionar uma sobrecarga de memória considerável).

Note que IRowsetUpdateImpl deriva de IRowsetChangeImpl. Assim, IRowsetUpdateImpl dá-lhe capacidade de alteração mais capacidade de lote.

Para oferecer suporte à updatability em seu provedor

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

    Adding IRowsetChange

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

    IRowsetChangeImpl< rowset-name, storage-name >
    

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

    Adding IRowsetUpdate

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

    IRowsetUpdateImpl< rowset-name, storage>
    

    Note

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

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

    Se implementar Adicionar ao mapa COM
    IRowsetChangeImpl COM_INTERFACE_ENTRY(IRowsetChange)
    IRowsetUpdateImpl COM_INTERFACE_ENTRY(IRowsetUpdate)
    Se 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 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 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 procurando em Atldb.h os IDs e valores de propriedade (se Atldb.h for diferente da documentação on-line, Atldb.h substituirá a documentação).

    Note

    Muitas das VARIANT_FALSE configurações e VARIANT_TRUE são exigidas pelos modelos OLE DB, a especificação OLE DB diz que eles podem ser lidos/gravados, mas os modelos OLE DB só podem suportar um valor.

    Se você implementar IRowsetChangeImpl

    Se você implementar IRowsetChangeImplo , deverá definir as seguintes propriedades em seu provedor. Essas propriedades são usadas principalmente para solicitar interfaces por meio do ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: Definir isso define DBPROP_IRowsetChangeautomaticamente .

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

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

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

    Se você implementar IRowsetUpdateImpl

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

    • DBPROP_IRowsetUpdate.

    • DBPROP_OWNINSERT: Deve ser READ_ONLY E VARIANT_TRUE.

    • DBPROP_OWNUPDATEDELETE: Deve ser READ_ONLY E VARIANT_TRUE.

    • DBPROP_OTHERINSERT: Deve ser READ_ONLY E VARIANT_TRUE.

    • DBPROP_OTHERUPDATEDELETE: Deve ser READ_ONLY E VARIANT_TRUE.

    • DBPROP_REMOVEDELETED: Deve ser READ_ONLY E VARIANT_TRUE.

    • DBPROP_MAXPENDINGROWS.

    Note

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

Gravando na fonte de dados

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

FlushData(HROW, HACCESSOR);

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

O FlushData método grava dados no formato em que foram originalmente armazenados. 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 lavar

Os modelos de 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 para inserir na linha)

  • IRowsetUpdate::Update

Como funciona

O consumidor faz uma chamada que requer uma descarga (como Update) e essa chamada é passada para o provedor, que sempre faz o seguinte:

  • Chama SetDBStatus sempre que você tem um valor de status vinculado.

  • Verifica sinalizadores de coluna.

  • Chamadas IsUpdateAllowed.

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

Como implementar o FlushData

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

Certificando-se de que o armazenamento de dados pode lidar com alterações.

Manipulação de valores NULL.

Manipulação de valores padrão

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

  • Vá para sua classe de conjunto de linhas.

  • Na classe rowset coloque a declaração de:

    HRESULT FlushData(HROW, HACCESSOR)
    {
        // Insert your implementation here and return an HRESULT.
    }
    
  • Fornecer uma implementação do 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:

  • Mantenha o método de gravação em seu armazenamento de dados o mais simples possível.

  • Manipule valores NULL (opcional, mas aconselhado).

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

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

O exemplo a UpdatePV seguir mostra como FlushData é implementado na RUpdateRowset classe no exemplo (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;
}

Handling Changes

Para que seu provedor lide com alterações, primeiro você precisa se certificar de que seu armazenamento de dados (como um arquivo de texto ou de vídeo) tenha recursos que permitam fazer alterações nele. Se isso não acontecer, você deve criar esse código separadamente do projeto do provedor.

Manipulando dados NULL

É possível que um usuário final envie dados NULL. Quando você grava valores NULL em campos na fonte de dados, pode haver problemas potenciais. Imagine um aplicativo de recebimento de pedidos que aceita valores para cidade e código postal; poderia aceitar um ou ambos os valores, mas não nenhum dos dois, porque nesse caso a entrega seria impossível. Portanto, você tem que restringir certas combinações de valores NULL em campos que fazem sentido para seu aplicativo.

Como desenvolvedor do provedor, você tem que considerar como armazenará esses dados, como lerá esses dados do armazenamento de dados e como especificá-los para o usuário. Especificamente, você deve considerar como alterar o status de 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.

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

Marcando colunas anuláveis

Se você também implementar conjuntos de linhas de esquema (consulte 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 contenham o valor DBCOLUMNFLAGS_ISNULLABLE em sua versão do GetColumnInfo.

Na implementação de modelos OLE DB, se você não marcar colunas como anuláveis, o provedor assume 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 é implementada CommonGetColInfo em CUpdateCommand (consulte UpProvRS.cpp) em UpdatePV. Observe como as colunas têm esse 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;
}

Default Values

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 parecem ter êxito (S_OK serão retornadas), mas não serão transmitidas para o armazenamento de dados.

UpdatePV No exemplo (em Rowset.h), o SetDBStatus método manipula 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;
}

Column Flags

Se você oferecer suporte a valores padrão em suas colunas, precisará defini-lo usando metadados na <classe>de provedor SchemaRowset classe. 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 CUpdateSessionColSchemaRowset classe em UpdatePV (em Session.h), a primeira coluna é configurada desta 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 suporta um valor padrão de 0, que ela pode ser gravada e que todos os dados na coluna têm o mesmo comprimento. Se desejar que os dados em uma coluna tenham comprimento variável, você não definiria esse sinalizador.

See also

Criando um provedor OLE DB