Compartilhar via


Criar um provedor atualizável

Visual C++ 6.0 oferece suporte somente os provedores de somente leitura. Visual C++.NET oferece suporte a provedores atualizáveis ou os provedores que podem atualizar (gravar) o armazenamento de dados. Este tópico discute como criar provedores atualizáveis, usando modelos de banco de dados OLE.

Este tópico pressupõe que você estiver 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 estão a ser feito imediatamente ou adiada até que um comando de atualização é emitido. A seção "Tornando atualizável de provedores de" Descreve as alterações e configurações que você precisa fazer no código do provedor.

Em seguida, verifique se o que seu provedor contém todas as funcionalidades para dar suporte a qualquer coisa que o consumidor pode solicitar dele. Se o consumidor quiser atualizar o armazenamento de dados, o provedor deve conter código persiste os dados para o armazenamento de dados. Por exemplo, pode usar a biblioteca de tempo de execução c ou a MFC para realizar tais operações em sua fonte de dados. A seção "escrita à fonte de dados" Descreve como gravar na fonte de dados, lidar com Nulo e valores padrão e definir os sinalizadores de coluna.

ObservaçãoObservação

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

Tornando os provedores atualizável

Para fazer com que um provedor atualizável é entender as operações que você deseja que o seu provedor para executar no armazenamento de dados e como deseja que o provedor para executar essas operações. Especificamente, a questão mais importante é que se as atualizações para o armazenamento de dados estão a ser feito imediatamente ou adiada (em lote) até que um comando de atualização é emitido.

Primeiro você deve decidir se herdar de IRowsetChangeImpl ou IRowsetUpdateImpl em sua turma de conjunto de linhas. Dependendo de qual deles você optar por implementar, a funcionalidade dos três métodos será afetada: SetData, InsertRows, and DeleteRows.

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

  • Se você herdar de IRowsetUpdateImpl, os métodos adiar alterações ao armazenamento de dados até que você chame atualização, GetOriginalData, ou Desfazer. Se a atualização envolve várias alterações, elas são executadas no modo de lotes (Observe que o lote de alterações pode adicionar a sobrecarga considerável de memória).

Observe que IRowsetUpdateImpl deriva de IRowsetChangeImpl. Assim, IRowsetUpdateImpl permite que você alterar o recurso mais capacidade de lote.

Para oferecer suporte a capacidade de atualização no seu provedor.

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

    Adicionando IRowsetChange

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

    IRowsetChangeImpl< rowset-name, storage-name >
    

    Também adicionar COM_INTERFACE_ENTRY(IRowsetChange) para o BEGIN_COM_MAP seção sua turma de conjunto de linhas.

    Adicionando IRowsetUpdate

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

    IRowsetUpdateImpl< rowset-name, storage>
    
    ObservaçãoObservação

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

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

    Se você implementar

    Adicionar ao mapa de COM

    IRowsetChangeImpl

    COM_INTERFACE_ENTRY(IRowsetChange)

    IRowsetUpdateImpl

    COM_INTERFACE_ENTRY(IRowsetChange)COM_INTERFACE_ENTRY(IRowsetUpdate)

  3. Em seu comando, adicione o seguinte ao mapa do conjunto de propriedade (BEGIN_PROPSET_MAP... END_PROPSET_MAP):

    Se você implementar

    Adicionar ao mapa do conjunto de propriedade

    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 seu mapa do conjunto de propriedade, você também deve incluir todas as configurações a seguintes como 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 por essas chamadas de macro procurando no Atldb.h os valores e as identificações de propriedade (se o Atldb.h difere da documentação online, Atldb.h substitui a documentação).

    ObservaçãoObservação

    Muitos da VARIANT_FALSE e VARIANT_TRUE as configurações são necessárias pelos modelos OLE DB; a especificação OLE DB diz que eles podem ser leitura/gravação, mas os modelos OLE DB só podem suportar um valor.

    Se você implementar IRowsetChangeImpl

    Se você implementar IRowsetChangeImpl, você deve definir as propriedades a seguir no seu provedor. Essas propriedades são usadas principalmente para solicitar as interfaces através de ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: Isso automaticamente configuração define DBPROP_IRowsetChange.

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

    • DBPROP_CHANGEINSERTEDROWS: Consumidor pode chamar IRowsetChange::DeleteRows ou SetData para recentemente inserido linhas.

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

    Se você implementar IRowsetUpdateImpl

    Se você implementar IRowsetUpdateImpl, você deve definir as propriedades a seguir no seu provedor, além de definir todas as propriedades de IRowsetChangeImpl listados anteriormente:

    • DBPROP_IRowsetUpdate.

    • DBPROP_OWNINSERT: Deve ser VARIANT_TRUE E de READ_ONLY.

    • DBPROP_OWNUPDATEDELETE: Deve ser VARIANT_TRUE E de READ_ONLY.

    • DBPROP_OTHERINSERT: Deve ser VARIANT_TRUE E de READ_ONLY.

    • DBPROP_OTHERUPDATEDELETE: Deve ser VARIANT_TRUE E de READ_ONLY.

    • DBPROP_REMOVEDELETED: Deve ser VARIANT_TRUE E de READ_ONLY.

    • DBPROP_MAXPENDINGROWS.

      ObservaçãoObservação

      Se o suporte a notificações, você também pode ter algumas outras propriedades também; Consulte a seção sobre IRowsetNotifyCP para esta lista.

    Por exemplo de como as propriedades são definidas, consulte a propriedade conjunto mapa CUpdateCommand (em Rowset.h) em UpdatePV.

Escrita à fonte de dados

Para ler a partir da fonte de dados, chamar o Execute função. Para gravar a fonte de dados, chamar o FlushData função. (Em termos gerais, liberar os meios para salvar as modificações feitas a uma tabela ou índice para o disco).

FlushData(HROW, HACCESSOR);

O identificador de linha (HROW) e o identificador do acessador (HACCESSOR) argumentos permitem que você especifique a região para escrever. Normalmente, você deve escrever um único campo de dados ao mesmo tempo.

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

Quando você liberar

A chamada de modelos de provedor FlushData sempre que precisarem de dados a serem gravados para o armazenamento de dados; Isso normalmente (mas não 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 ele funciona

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

  • Chamadas SetDBStatus sempre que você tiver um valor de status vinculado (consulte OLE DB Programmers Reference, capítulo 6, partes de dados: Status).

  • Verifica os sinalizadores de coluna.

  • Calls IsUpdateAllowed.

Estas três etapas ajudam a fornecer a segurança. E depois chama o provedor FlushData.

Como implementar FlushData

Para implementar FlushData, você precisa levar em conta vários problemas:

  • Certificando-se de que o armazenamento de dados pode tratar de alterações.

  • Tratamento Nulo valores.

  • Manipulação de valores padrão.

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

  • Vá para a sua classe de conjunto de linhas.

  • No conjunto de linhas a classe coloca 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 realmente estão atualizadas. Você pode usar o HROW e HACCESSOR parâmetros para determinar a linha e coluna atuais sendo armazenados para otimização.

Normalmente, o maior desafio está trabalhando com seu próprio armazenamento de dados nativos. Se possível, tente:

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

  • Lidar com Nulo valores (opcionais mas recomendado).

  • Manipule valores de padrão (opcionais mas recomendado).

A melhor coisa a fazer é ter especificados de valores reais no seu armazenamento de dados para Nulo e valores padrão. É melhor se você pode extrapolar esses dados. Se não, você é avisado para não permitir Nulo e valores padrão.

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

Manipulação de alterações

De seu provedor para tratar de alterações, primeiro você precisa certificar-se de que seu armazenamento de dados (como, por exemplo, um arquivo de texto ou arquivo de vídeo) possui recursos que permitem que você faça alterações nele. Se não tiver, você deve criar o código separadamente do projeto do provedor.

Manipulação de dados NULL

É possível que um usuário final enviará Nulo dados. Quando você escreve Nulo valores para os campos na fonte de dados, pode haver problemas potenciais. Imagine um aplicativo de ordens que aceita valores para a cidade e CEP; ela pode aceitar valores de uma ou ambas, mas não nenhuma porque nesse caso, entrega seria impossível. Portanto, você precisa restringir determinadas combinações de Nulo valores nos campos que fazem sentido para o seu aplicativo.

Como desenvolvedor de provedor, você deve considerar como irá armazenar os dados, como você irá ler esses dados do armazenamento de dados e como você especificar que o usuário. Especificamente, você deve considerar como alterar o status de dados do conjunto de linhas de dados na fonte de dados (por exemplo, DataStatus = Nulo). Decidir qual valor para retornar quando um consumidor acessa um campo contendo um Nulo valor.

Examine o código da UpdatePV de exemplo; ele ilustra como um provedor pode lidar com Nulo dados. UpdatePV, o provedor armazena Nulo dados escrevendo a cadeia de caracteres "NULL" no armazenamento de dados. Quando ele lê Nulo armazenamento de dados a partir dos dados, ele vê essa cadeia de caracteres e esvazia o buffer, criando uma Nulo seqüência de caracteres. Ele também tem uma substituição do IRowsetImpl::GetDBStatus em que ele retorna DBSTATUS_S_ISNULL se esse valor de dados está vazio.

Colunas anuláveis de marcação.

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

Você também precisará especificar que todas as colunas anuláveis contenham a DBCOLUMNFLAGS_ISNULLABLE o valor em sua versão do GetColumnInfo.

Na implementação de modelos OLE DB, se você não marcar as colunas como anulável, o provedor pressupõe que eles devem conter um valor e não permitirá que o consumidor para enviá-lo em valores nulos.

A exemplo a seguir mostra como o CommonGetColInfo função é implementada no 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

Como com Nulo dados, você tem a responsabilidade para lidar com a alteração de valores padrão.

O padrão de FlushData e Execute é retornar S_OK. Portanto, se você não substitui essa função, as alterações aparecem para o sucesso (S_OK será retornado), mas eles não serão transmitidos para o armazenamento de dados.

No UpdatePV exemplo (em Rowset.h), o SetDBStatus método 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, você precisa configurá-lo usando os metadados na <a classe de provedor> SchemaRowset classe. Definir m_bColumnHasDefault = VARIANT_TRUE.

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

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

// 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, a coluna oferece suporte a um valor padrão de 0, que ser graváveis, e que todos os dados na coluna tem o mesmo comprimento. Se desejar que os dados em uma coluna de tamanho variável, não defina esse sinalizador.

Consulte também

Referência

Criando um provedor do OLE DB