Создание поставщика с возможностью записи
Visual C++ поддерживает обновляемых поставщиков или поставщиков, которые могут обновлять (записывать в) хранилище данных. В этом разделе описывается создание обновляемых поставщиков с помощью шаблонов OLE DB.
В этом разделе предполагается, что вы начинаете работу с работолюбивым поставщиком. Существует два шага для создания обновляемого поставщика. Сначала необходимо решить, как поставщик будет вносить изменения в хранилище данных; в частности, следует ли выполнять изменения немедленно или отложить до выдачи команды обновления. В разделе "Создание обновляемых поставщиков" описаны изменения и параметры, которые необходимо выполнить в коде поставщика.
Затем необходимо убедиться, что поставщик содержит все функциональные возможности для поддержки всего, что потребитель может запросить его. Если потребитель хочет обновить хранилище данных, поставщик должен содержать код, сохраняющий данные в хранилище данных. Например, можно использовать библиотеку времени выполнения C или MFC для выполнения таких операций в источнике данных. В разделе "Запись в источник данных" описывается запись в источник данных, обработка значений NULL и значений по умолчанию и установка флагов столбцов.
Примечание.
UpdatePV является примером обновляемого поставщика. UpdatePV совпадает с MyProv, но с обновляемой поддержкой.
Создание обновляемых поставщиков
Ключом к обновлению поставщика является понимание операций, которые требуется выполнить поставщику в хранилище данных и как поставщик будет выполнять эти операции. В частности, основная проблема заключается в том, следует ли немедленно выполнять обновления хранилища данных или отложить (пакетную) до выдачи команды обновления.
Сначала необходимо решить, следует ли наследовать от класса набора строк или IRowsetUpdateImpl
от IRowsetChangeImpl
него. В зависимости от того, какие из них вы решили реализовать, функциональные возможности трех методов будут затронуты: SetData
, InsertRows
и DeleteRows
.
Если вы наследуете от IRowsetChangeImpl, вызов этих трех методов немедленно изменяет хранилище данных.
Если вы наследуете от IRowsetUpdateImpl, методы откладывают изменения в хранилище данных до вызова
Update
илиGetOriginalData
Undo
. Если обновление включает несколько изменений, они выполняются в пакетном режиме (обратите внимание, что пакетные изменения могут добавить значительные затраты на память).
Обратите внимание, что IRowsetUpdateImpl
производный от IRowsetChangeImpl
. Таким образом, IRowsetUpdateImpl
вы можете изменить возможности и пакетную функцию.
Поддержка обновления в поставщике
В классе набора строк наследуется от
IRowsetChangeImpl
илиIRowsetUpdateImpl
. Эти классы предоставляют соответствующие интерфейсы для изменения хранилища данных:Добавление IRowsetChange
Добавьте
IRowsetChangeImpl
в цепочку наследования с помощью следующей формы:IRowsetChangeImpl< rowset-name, storage-name >
Кроме того,
BEGIN_COM_MAP
добавьтеCOM_INTERFACE_ENTRY(IRowsetChange)
раздел в класс набора строк.Добавление IRowsetUpdate
Добавьте
IRowsetUpdate
в цепочку наследования с помощью следующей формы:IRowsetUpdateImpl< rowset-name, storage>
Примечание.
Вы должны удалить
IRowsetChangeImpl
строку из цепочки наследования. Это одно исключение из директивы, упоминаемой ранее, должно включать код дляIRowsetChangeImpl
.Добавьте следующую команду на карту COM (
BEGIN_COM_MAP ... END_COM_MAP
):При реализации Добавление на карту COM IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetUpdate)
При реализации Добавление в карту набора свойств IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
В команде добавьте следующее в карту набора свойств (
BEGIN_PROPSET_MAP ... END_PROPSET_MAP
):При реализации Добавление в карту набора свойств IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)
На карте набора свойств необходимо также включить все следующие параметры, как они отображаются ниже:
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)
Значения, используемые в этих вызовах макросов, можно найти в Atldb.h для идентификаторов свойств и значений (если Atldb.h отличается от веб-документации, Atldb.h заменяет документацию).
Примечание.
Многие из
VARIANT_FALSE
шаблонов OLE DB иVARIANT_TRUE
параметров требуются. Спецификация OLE DB говорит, что они могут быть прочитаны и записаны, но шаблоны OLE DB могут поддерживать только одно значение.При реализации IRowsetChangeImpl
При реализации
IRowsetChangeImpl
необходимо задать следующие свойства в поставщике. Эти свойства в основном используются для запроса интерфейсовICommandProperties::SetProperties
через .DBPROP_IRowsetChange
: установка этих автоматических наборовDBPROP_IRowsetChange
.DBPROP_UPDATABILITY
: битовая маска, указывающая поддерживаемые методы вIRowsetChange
:SetData
,DeleteRows
илиInsertRow
.DBPROP_CHANGEINSERTEDROWS
: потребитель может вызыватьIRowsetChange::DeleteRows
илиSetData
запрашивать только что вставленные строки.DBPROP_IMMOBILEROWS
: набор строк не будет изменять порядок вставленных или обновленных строк.
При реализации IRowsetUpdateImpl
Если вы реализуете
IRowsetUpdateImpl
, необходимо задать следующие свойства в поставщике, а также задать все свойства дляIRowsetChangeImpl
ранее перечисленных:DBPROP_IRowsetUpdate
.DBPROP_OWNINSERT
: должно быть READ_ONLY И VARIANT_TRUE.DBPROP_OWNUPDATEDELETE
: должно быть READ_ONLY И VARIANT_TRUE.DBPROP_OTHERINSERT
: должно быть READ_ONLY И VARIANT_TRUE.DBPROP_OTHERUPDATEDELETE
: должно быть READ_ONLY И VARIANT_TRUE.DBPROP_REMOVEDELETED
: должно быть READ_ONLY И VARIANT_TRUE.DBPROP_MAXPENDINGROWS
.
Примечание.
Если вы поддерживаете уведомления, у вас также могут быть некоторые другие свойства. См. раздел
IRowsetNotifyCP
для этого списка.
Запись в источник данных
Чтобы прочитать из источника данных, вызовите функцию Execute
. Чтобы записать данные в источник данных, вызовите функцию FlushData
. (В общем смысле, очистка означает сохранение изменений, внесенных в таблицу или индекс на диск.)
FlushData(HROW, HACCESSOR);
Аргументы дескриптора строк (HROW) и дескриптора доступа (HACCESSOR) позволяют указать регион для записи. Как правило, за раз вы записываете одно поле данных.
Метод FlushData
записывает данные в формат, в котором он был первоначально сохранен. Если вы не переопределяете эту функцию, поставщик будет работать правильно, но изменения не будут промыты в хранилище данных.
Когда требуется очистка
Шаблоны поставщиков вызывают FlushData всякий раз, когда данные должны записываться в хранилище данных; обычно это происходит (но не всегда) в результате вызовов следующих функций:
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows
(если в строке есть новые данные для вставки)IRowsetUpdate::Update
Принцип работы
Потребитель вызывает запрос, требующий очистки (например, обновления), и этот вызов передается поставщику, который всегда выполняет следующие действия:
Вызывает
SetDBStatus
каждый раз, когда у вас есть привязка значения состояния.Проверяет флаги столбцов.
Вызывает
IsUpdateAllowed
.
Эти три шага помогут обеспечить безопасность. Затем поставщик вызывает FlushData
.
Реализация FlushData
Для реализации FlushData
необходимо учитывать несколько проблем:
Убедитесь, что хранилище данных может обрабатывать изменения.
Обработка значений NULL.
Обработка значений по умолчанию.
Чтобы реализовать собственный FlushData
метод, необходимо выполнить следующие действия.
Перейдите к классу набора строк.
В классе набора строк поместите объявление:
HRESULT FlushData(HROW, HACCESSOR) { // Insert your implementation here and return an HRESULT. }
Предоставьте реализацию
FlushData
.
Хорошая реализация FlushData
сохраняет только строки и столбцы, которые фактически обновляются. Параметры HROW и HACCESSOR можно использовать для определения текущей строки и столбца, хранящегося для оптимизации.
Как правило, самая большая проблема заключается в работе с собственным хранилищем данных. Если это возможно, попробуйте:
Как можно проще сохранить метод записи в хранилище данных.
Обработка значений NULL (необязательно, но рекомендуется).
Обработка значений по умолчанию (необязательно, но рекомендуется).
Лучше всего иметь фактические указанные значения в хранилище данных для значений NULL и по умолчанию. Лучше всего экстраполировать эти данные. Если нет, рекомендуется не разрешать значения NULL и значения по умолчанию.
В следующем примере показано, как FlushData
реализуется в RUpdateRowset
классе в UpdatePV
примере (см. раздел Rowset.h в примере кода):
///////////////////////////////////////////////////////////////////////////
// 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;
}
Обработка изменений
Для обработки изменений поставщику сначала необходимо убедиться, что хранилище данных (например, текстовый файл или видеофайл) имеет средства, позволяющие вносить изменения в него. Если это не так, следует создать этот код отдельно от проекта поставщика.
Обработка данных NULL
Возможно, конечный пользователь отправит данные NULL. При записи значений NULL в поля в источнике данных могут возникнуть потенциальные проблемы. Представьте себе приложение для принятия заказов, которое принимает значения для города и почтового индекса; он может принимать либо или оба значения, но не ни, так как в этом случае доставка будет невозможна. Поэтому необходимо ограничить определенные сочетания значений NULL в полях, которые имеют смысл для приложения.
Разработчик поставщика должен учитывать, как хранить эти данные, как считывать эти данные из хранилища данных и как указать это пользователю. В частности, необходимо учитывать, как изменить состояние данных набора строк в источнике данных (например, DataStatus = NULL). Вы решаете, какое значение следует возвращать, когда потребитель обращается к полю с значением NULL.
Просмотрите код в примере UpdatePV; в нем показано, как поставщик может обрабатывать данные NULL. В UpdatePV поставщик сохраняет данные NULL, записывая строку NULL в хранилище данных. Когда он считывает данные NULL из хранилища данных, он видит эту строку, а затем очищает буфер, создавая строку NULL. Он также имеет переопределение, IRowsetImpl::GetDBStatus
в котором возвращается DBSTATUS_S_ISNULL, если это значение данных пусто.
Маркировка столбцов, допускающих значение NULL
Если вы также реализуете наборы строк схемы (см. см IDBSchemaRowsetImpl
.), реализация должна указываться в наборе строк DBSCHEMA_COLUMNS (обычно помеченном поставщиком CxSchemaColSchemaRowset), что столбец может иметь значение NULL.
Кроме того, необходимо указать, что все столбцы, допускающие значение NULL, содержат значение DBCOLUMNFLAGS_ISNULLABLE в вашей GetColumnInfo
версии.
В реализации шаблонов OLE DB, если не удается пометить столбцы как допускающие значение NULL, поставщик предполагает, что они должны содержать значение и не позволят потребителю отправлять значения NULL.
В следующем примере показано, как CommonGetColInfo
функция реализована в CUpdateCommand (см. UpProvRS.cpp) в UpdatePV. Обратите внимание, что столбцы имеют этот DBCOLUMNFLAGS_ISNULLABLE для столбцов, допускающих значение NULL.
/////////////////////////////////////////////////////////////////////////////
// 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;
}
Значения по умолчанию
Как и в случае с данными NULL, вы несете ответственность за изменение значений по умолчанию.
Значение по умолчанию FlushData
и Execute
возвращает S_OK. Таким образом, если эта функция не переопределяется, изменения отображаются успешно (S_OK будут возвращены), но они не будут передаваться в хранилище данных.
UpdatePV
В примере (в Rowset.h) SetDBStatus
метод обрабатывает значения по умолчанию следующим образом:
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;
}
Флаги столбцов
Если вы поддерживаете значения по умолчанию для столбцов, необходимо задать его с помощью метаданных в <классе>SchemaRowset поставщика. Задайте m_bColumnHasDefault = VARIANT_TRUE
.
Вы также несете ответственность за установка флагов столбцов, указанных с помощью перечисленного типа DBCOLUMNFLAGS. Флаги столбцов описывают характеристики столбцов.
Например, в CUpdateSessionColSchemaRowset
классе UpdatePV
(в Session.h) первый столбец настраивается следующим образом:
// 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]);
Этот код указывает, помимо прочего, что столбец поддерживает значение по умолчанию 0, которое можно записать, и что все данные в столбце имеют одинаковую длину. Если вы хотите, чтобы данные в столбце имели переменную длину, этот флаг не будет задан.