Criando um provedor atualizável
O Visual C++ 6.0 suporte somente provedores de somente leitura.O Visual C++ .NET oferece suporte a provedores atualizáveis ou provedores podem atualização (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 será fazer alterações para o armazenamento de dados; especificamente, se as alterações são a ser concluído 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ê precisará fazer no código do provedor.
Em seguida, você deve certificar-se de que seu provedor contém toda a funcionalidade para oferecer suporte a qualquer coisa que o consumidor pode solicitar dele.Se o consumidor deseja atualizar o armazenamento de dados, o provedor deve conter código que persiste os dados para o armazenamento de dados.Por exemplo, você pode usar a biblioteca de time de execução do C ou MFC para realizar tais operações em sua fonte de dados.A seção"Gravar para a fonte de dados"descreve como gravar na fonte de dados, lidar com NULOvalores padrão e e conjunto sinalizadores de coluna.
Observação: |
---|
UpdatePV é um exemplo de um provedor atualizável.UpdatePV é igual MyProv mas com atualizável suporte. |
Tornar provedores atualizável
O fundamental para que um provedor atualizável é entender as operações que você desejar que seu provedor para executar o 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 devem ser concluído 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 destes você optar por implementar, a funcionalidade dos três métodos será afetada: SetData, InsertRows, and 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 para o 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 lote (Observe que as alterações de processamento lote pode adicionar sobrecarga considerável de memória).
Observe que IRowsetUpdateImpl deriva da IRowsetChangeImpl. Assim, IRowsetUpdateImpl permite que você alterar o recurso mais capacidade de lote.
Para oferecer suporte a capacidade de atualização no seu provedor
Na sua classe de conjunto de linhas, herdam IRowsetChangeImpl ou IRowsetUpdateImpl. Essas classes fornecem interfaces apropriadas para alterar o armazenamento de dados:
Adicionando IRowsetChange
Add IRowsetChangeImpl para 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 na sua classe de conjunto de linhas.
Adicionando IRowsetUpdate
Add IRowsetUpdate para a sua cadeia de herança usando este formulário:
IRowsetUpdateImpl< rowset-name, storage>
Observação: Você deve remover o IRowsetChangeImpl linha da sua cadeia de herança. Essa exceção à diretiva mencionada anteriormente um deve incluir o código para IRowsetChangeImpl.
Adicione o seguinte para o MAP de COM (BEGIN_COM_MAP... END_COM_MAP ):
Se você implementar
Adicionar ao MAP de com.
IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetChange)COM_INTERFACE_ENTRY(IRowsetUpdate)
Em seu comando, adicione o seguinte ao MAP do conjunto de propriedades (BEGIN_PROPSET_MAP... END_PROPSET_MAP ):
Se você implementar
Adicionar ao MAP 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)
No MAP do conjunto de propriedades, você também deve incluir todas sistema autônomo configurações a seguintes em que 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 macro examinando Atldb.h para os IDs de propriedade e os valores (se Atldb.h for diferente da documentação online, Atldb.h substitui a documentação).
Observação: Muitos do VARIANT_FALSE and VARIANT_TRUE as configurações são necessárias pelos modelos OLE DB; a especificação OLE DB diz que eles podem ser de leitura/gravar, mas os modelos OLE DB só podem suportar um valor.
Se você implementar IRowsetChangeImpl
Se você implementar IRowsetChangeImpl, você deve conjunto as seguintes propriedades no seu provedor. Essas propriedades são usadas principalmente para solicitar interfaces através de ICommandProperties::SetProperties.
DBPROP_IRowsetChange: configuração Essa opção define automaticamente DBPROP_IRowsetChange.
DBPROP_UPDATABILITY: Uma máscara de bits especificar métodos com suporte de IRowsetChange: SetData, DeleteRows, ou InsertRow.
DBPROP_CHANGEINSERTEDROWS: Consumidor pode telefonar IRowsetChange::DeleteRows or SetData para linhas recém-inseridas.
DBPROP_IMMOBILEROWS: Conjunto de linhas não serão reordenar linhas inseridas ou atualizadas.
Se você implementar IRowsetUpdateImpl
Se você implementar IRowsetUpdateImpl, você deve conjunto as seguintes propriedades no seu provedor, além da todas as propriedades para conjunto IRowsetChangeImpl listados anteriormente:
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.
-
Observação: Se você oferecer suporte a notificações, você também pode ter algumas outras propriedades também; consulte o sistema autônomo ection em IRowsetNotifyCP para esta lista.
Por exemplo de como as propriedades são definidas, consulte a propriedade definir MAP em CUpdateCommand (em conjunto de linhas.h) em UpdatePV.
Gravar para a fonte de dados
Ler a partir da fonte de dados, telefonar o Executar função.Para gravar a fonte de dados, chamar o FlushData função. (De certa forma geral, liberar meios para salvar as modificações feitas em uma tabela ou índice para o disco.)
FlushData(HROW, HACCESSOR);
O identificador de linha (HROW) e o manipulador de acessador (HACCESSOR) argumentos permitem que você especifique a região de gravar.Normalmente, você deve escrever um único campo de dados ao mesmo time.
The FlushData método grava dados no formato em que foi originalmente armazenado. Se não substituem essa função, seu provedor funcionará corretamente, mas as alterações não serão liberadas para o armazenamento de dados.
Quando liberar
sistema autônomo modelos de provedor telefonar FlushData sempre que dados precisam ser gravadas em armazenamento de dados; isso ocorre normalmente (mas não sempre) sistema autônomo resultado de chamadas para sistema autônomo seguintes funções:
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows (se houver novos dados para inserir na linha)
IRowsetUpdate::atualização
Como funciona
O consumidor faz uma telefonar que requer uma liberação (por exemplo, Atualização) e essa telefonar é passada para o provedor, que sempre faz o seguinte:
ChamaSetDBStatus sempre que houver um valor de status limite (consulte Referência de programadores do OLE DB, O capítulo 6, Partes de dados: De status ).
Verifica os sinalizadores de coluna.
ChamaIsUpdateAllowed.
Estas três etapas ajudam na segurança.O provedor chama FlushData.
Como implementar FlushData
Para implementar FlushData, você precisará levar em consideração vários problemas:
Certificando-se de que o armazenamento de dados pode tratar de alterações.
Manipulação de 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 colocar a declaração de:
HRESULT FlushData(HROW, HACCESSOR)
{
// Insert your implementation here and return an HRESULT.
}
- Fornecer uma implementação de FlushData.
Uma mercadoria implementação de FlushData armazena apenas as linhas e colunas que na verdade, são atualizadas. Você pode usar o HROW and HACCESSOR parâmetros para determinar a linha e coluna corrente sendo armazenados para otimização.
Normalmente, o maior desafio está trabalhando com seu próprio armazenamento de dados nativo.Se possível, tente:
Manter o método de gravação para o armazenamento de dados sistema autônomo simples sistema autônomo possíveis.
Manipular NULOvalores de (opcional mas recomendado).
Lidar com valores padrão (opcional mas recomendado).
A melhor coisa a fazer é ter valores especificados real em seu armazenamento de dados para NULOvalores padrão e .É melhor se podem extrapolar esses dados.Caso contrário, você é aconselhado a não permitir que NULOvalores padrão e .
O exemplo a seguir mostra como FlushData é implementada na RUpdateRowset classe na UpdatePV amostra (consulte conjunto de linhas.h o 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 o provedor para tratar de alterações, você precisa primeiro certificar-se de que seu armazenamento de dados (sistema autônomo um arquivo de texto ou arquivo de vídeo) possui recursos que permitem que você fazer alterações nele.Se não tiver, você deve criar esse código separadamente do projeto provedor.
Manipulação de dados nulo
É possível que um usuário participante será enviar NULO dados.Quando você escreve NULO valores para campos da fonte de dados, pode haver problemas potenciais.Imagine um aplicativo de ordem colocando que aceita valores de cidade e CEP; ele pode aceitar um ou ambos os valores, mas não nenhuma porque nesse caso seria impossível entrega.Portanto, você deve restringir determinadas combinações de NULO valores nos campos que fazem sentido para o seu aplicativo.
sistema autônomo desenvolvedor provedor, você precisa considerar sistema autônomo irá armazenar sistema autônomo dados, sistema autônomo você irá ler esses dados do armazenamento de dados e sistema autônomo especificar que o usuário.Especificamente, você deve considerar como alterar o status de dados dos dados do conjunto de linhas na fonte de dados (por exemplo, DataStatus = NULO).Decidir qual valor para retornar quando um consumidor acessa um campo contendo um NULOvalor de .
Examine o código no UpdatePV exemplo; ele ilustra como um provedor pode manipular 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 de dos dados, ele vê essa cadeia de caracteres e esvazia o buffer, criando um NULOseqüência de caracteres .Ele também tem uma substituir de IRowsetImpl::GetDBStatus em que ele retorna DBSTATUS_S_ISNULL se esse valor de dados estiver vazio.
Marcação Nullable colunas
Se você implementar também conjuntos de linhas de esquema (consulte IDBSchemaRowsetImpl), sua implementação deverá especificar o DBSCHEMA_COLUMNS conjunto de linhas (geralmente marcado no seu provedor por Cxxx de do SchemaColSchemaRowset) que a coluna é anulável.
Você também precisará especificar que todas as colunas anuláveis contenham o DBCOLUMNFLAGS_ISNULLABLE valor de na sua versão do GetColumnInfo.
Na implementação de modelos OLE DB, se você não marcar colunas sistema autônomo anulável, o provedor pressupõe que eles devem conter um valor e não permitirá que o consumidor para enviá-lo valores nulo.
O exemplo a seguir mostra como a CommonGetColInfo função é implementada de CUpdateCommand (consulte UpProvRS.cpp) em UpdatePV.Observe como as colunas têm esta DBCOLUMNFLAGS_ISNULLABLE 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
sistema autônomo ocorre com NULO dados, você tem a responsabilidade para lidar com a alteração de valores padrão.
O padrão de FlushData e Executar é retornar S_OK. Portanto, se você não substituem essa função, as alterações aparecem seja bem-sucedida (S_OK será retornado), mas eles não serão transmitidos para o armazenamento de dados.
No UpdatePV exemplo (em conjunto de linhas.h), a SetDBStatus método trata 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ê dá suporte a valores padrão em suas colunas, você precisará defini-la usando metadados no **<**de classe de provedor de > SchemaRowset classe.conjunto m_bColumnHasDefault = VARIANT_TRUE.
Você também tem a responsabilidade para conjunto 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 na UpdatePV (em sessão.h), a primeira coluna é conjunto backup 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 oferecer suporte para um valor padrão de 0, que ser graváveis, e que todos os dados na coluna que tem o mesmo comprimento.Se você deseja que os dados em uma coluna de tamanho variável, não defina esse sinalizar.
Para obter uma lista completa de valores de sinalizar de coluna, consulte o "DBCOLUMNFLAGS tipo enumerado" emIColumnsInfo::GetColumnInfo.