Compartilhar via


Criando um provedor atualizável

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

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 "Tornar provedores atualizável" 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 toda a funcionalidade para suportar qualquer coisa que o consumidor pode solicitar dele.Se o consumidor quiser atualizar o armazenamento de dados, o provedor deve conter código persiste dados para o armazenamento de dados.Por exemplo, use a biblioteca de tempo de execução c ou MFC para realizar operações em sua fonte de dados.A seção "a gravação da fonte de dados" descreve como escrever para a fonte de dados, lidar com Nulo e valores padrão e definir sinalizadores de coluna.

ObservaçãoObservação

UpdatePV é um exemplo de um provedor atualizável.UpdatePV é igual MyProv mas com suporte atualizável.

Tornar provedores atualizável

Para fazer um provedor atualizável é entender as operações que você deseja que o provedor para executar no armazenamento de dados e como deseja que o provedor para executar essas operações.Especificamente, a questão mais importante é 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 na sua classe de conjunto de linhas.Dependendo de qual deles você optar por implementar, a funcionalidade dos três métodos será afetada: SetData, InsertRows, e DeleteRows.

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

  • Se você herdar de IRowsetUpdateImpl, os métodos adiar alterações ao armazenamento de dados até que você chamar atualização, GetOriginalData, ou Desfazer.Se a atualização envolve várias alterações, elas são executadas no modo em lotes (Observe que o lote de alterações pode adicionar 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 à capacidade de atualização no seu provedor

  1. Em 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 em sua classe 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 linha de sua cadeia de herança.Essa exceção à diretiva mencionada anteriormente deve incluir o código para 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 a propriedade conjunto mapa

    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 propriedade, você também deve incluir todas as configurações a seguintes como eles 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 essas chamadas de macro examinando Atldb.h identificações de propriedade e valores (se Atldb.h for diferente da documentação on-line, Atldb.h substitui a documentação).

    ObservaçãoObservação

    Muitos do VARIANT_FALSE e VARIANT_TRUE configurações são exigidas por modelos OLE DB; a especificação OLE DB diz podem ser leitura/gravação, mas os modelos OLE DB suporta apenas 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 interfaces através de ICommandProperties::SetProperties.

    • DBPROP_IRowsetChange: Configuração automaticamente define DBPROP_IRowsetChange.

    • DBPROP_UPDATABILITY: Uma bitmask especificando os métodos com suporte no IRowsetChange: SetData, DeleteRows, ou InsertRow.

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

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

    Se você implementar IRowsetUpdateImpl

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

    • 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.

      ObservaçãoObservação

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

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

Gravação da fonte de dados

A leitura da fonte de dados, chamar o Executar função.Para gravar a fonte de dados, chamar o FlushData função.(Em um sentido geral, liberar meios para salvar as modificações feitas em uma tabela ou índice para o disco.)

FlushData(HROW, HACCESSOR);

Identificador de linha (HROW) e a alça de acessador (HACCESSOR) argumentos permitem que você especifique a região para gravar.Normalmente, você escrever um único campo de dados por vez.

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

1cs8bsah.collapse_all(pt-br,VS.110).gifQuando liberar

A chamada de modelos de provedor FlushData sempre que dados precisam ser gravados no armazenamento de dados; Isso normalmente (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

1cs8bsah.collapse_all(pt-br,VS.110).gifComo 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 houver um valor de status ligado (consulte OLE DB programadores referência, capítulo 6, partes de dados: Status).

  • Verifica os sinalizadores de coluna.

  • Calls IsUpdateAllowed.

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

1cs8bsah.collapse_all(pt-br,VS.110).gifComo implementar FlushData

Para implementar FlushData, você precisa levar em conta diversos problemas:

  • Certificando-se de que o armazenamento de dados pode manipular as 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 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 atual e a coluna sendo armazenados para otimização.

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

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

  • Tratar Nulo valores (opcionais mas recomendado).

  • Lidar com valores padrão (opcionais mas recomendado).

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

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

1cs8bsah.collapse_all(pt-br,VS.110).gifManipulação de alterações

Seu provedor tratar de alterações, primeiro você precisa certificar-se de que seu armazenamento de dados (como 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 código separadamente do projeto de provedor.

1cs8bsah.collapse_all(pt-br,VS.110).gifManipulação de dados nula

É possível que um usuário final enviará Nulo dados.Quando você escreve Nulo valores para campos na fonte de dados, pode haver problemas potenciais.Imagine um aplicativo de ordens que aceita valores de cidade e CEP; ela pode aceitar valores de uma ou ambas, mas não nenhuma porque nesse caso seria impossível entrega.Portanto, você precisará 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ê lerá os dados do armazenamento de dados e como 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 do UpdatePV de exemplo; Ele ilustra como um provedor pode tratar Nulo dados.UpdatePV, o provedor armazena Nulo dados escrevendo a cadeia de caracteres "NULL" no armazenamento de dados.Quando ele lê Nulo armazenam dados dos dados, ele vê essa cadeia de caracteres e esvazia o buffer, criando um 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.

1cs8bsah.collapse_all(pt-br,VS.110).gifMarcação colunas anuláveis

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

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

Na implementação de modelos OLE DB, se você não marcar colunas como anulável, o provedor pressupõe que deve conter um valor e não permitirá que o consumidor envie 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;
}

1cs8bsah.collapse_all(pt-br,VS.110).gifValores padrão

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

O padrão de FlushData e Executar é retornar S_OK.Portanto, se você não substituir dessa função, as alterações aparecem ser bem-sucedida (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 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;
}

1cs8bsah.collapse_all(pt-br,VS.110).gifSinalizadores de coluna

Se suporta valores padrão em suas colunas, você precisará defini-la usando metadados de <classe do provedor> SchemaRowset classe.Definir m_bColumnHasDefault = VARIANT_TRUE.

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

Por exemplo, no CUpdateSessionColSchemaRowset classe no 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, a coluna oferece suporte a um valor padrão de 0, que pode ser graváveis, e que todos os dados na coluna tem o mesmo comprimento.Se você deseja que os dados em uma coluna de comprimento variável, não defina esse sinalizador.

Consulte também

Referência

Criando um provedor OLE DB