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
ouUndo
. 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
Na classe de conjunto de linhas, herda de
IRowsetChangeImpl
ouIRowsetUpdateImpl
. 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çãoBEGIN_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 paraIRowsetChangeImpl
.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)
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)
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
eVARIANT_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 deICommandProperties::SetProperties
.DBPROP_IRowsetChange
: essa configuração defineDBPROP_IRowsetChange
automaticamente.DBPROP_UPDATABILITY
: uma máscara de bits especificando os métodos com suporte emIRowsetChange
:SetData
,DeleteRows
ouInsertRow
.DBPROP_CHANGEINSERTEDROWS
: o consumidor pode chamarIRowsetChange::DeleteRows
ouSetData
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 paraIRowsetChangeImpl
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.