Поделиться через


Создание поставщика с возможностью записи

Visual C++ поддерживает обновляемых поставщиков или поставщиков, которые могут обновлять (записывать в) хранилище данных. В этом разделе описывается создание обновляемых поставщиков с помощью шаблонов OLE DB.

В этом разделе предполагается, что вы начинаете работу с работолюбивым поставщиком. Существует два шага для создания обновляемого поставщика. Сначала необходимо решить, как поставщик будет вносить изменения в хранилище данных; в частности, следует ли выполнять изменения немедленно или отложить до выдачи команды обновления. В разделе "Создание обновляемых поставщиков" описаны изменения и параметры, которые необходимо выполнить в коде поставщика.

Затем необходимо убедиться, что поставщик содержит все функциональные возможности для поддержки всего, что потребитель может запросить его. Если потребитель хочет обновить хранилище данных, поставщик должен содержать код, сохраняющий данные в хранилище данных. Например, можно использовать библиотеку времени выполнения C или MFC для выполнения таких операций в источнике данных. В разделе "Запись в источник данных" описывается запись в источник данных, обработка значений NULL и значений по умолчанию и установка флагов столбцов.

Примечание.

UpdatePV является примером обновляемого поставщика. UpdatePV совпадает с MyProv, но с обновляемой поддержкой.

Создание обновляемых поставщиков

Ключом к обновлению поставщика является понимание операций, которые требуется выполнить поставщику в хранилище данных и как поставщик будет выполнять эти операции. В частности, основная проблема заключается в том, следует ли немедленно выполнять обновления хранилища данных или отложить (пакетную) до выдачи команды обновления.

Сначала необходимо решить, следует ли наследовать от класса набора строк или IRowsetUpdateImpl от IRowsetChangeImpl него. В зависимости от того, какие из них вы решили реализовать, функциональные возможности трех методов будут затронуты: SetData, InsertRowsи DeleteRows.

  • Если вы наследуете от IRowsetChangeImpl, вызов этих трех методов немедленно изменяет хранилище данных.

  • Если вы наследуете от IRowsetUpdateImpl, методы откладывают изменения в хранилище данных до вызова UpdateилиGetOriginalDataUndo. Если обновление включает несколько изменений, они выполняются в пакетном режиме (обратите внимание, что пакетные изменения могут добавить значительные затраты на память).

Обратите внимание, что IRowsetUpdateImpl производный от IRowsetChangeImpl. Таким образом, IRowsetUpdateImpl вы можете изменить возможности и пакетную функцию.

Поддержка обновления в поставщике

  1. В классе набора строк наследуется от IRowsetChangeImpl или IRowsetUpdateImpl. Эти классы предоставляют соответствующие интерфейсы для изменения хранилища данных:

    Добавление IRowsetChange

    Добавьте IRowsetChangeImpl в цепочку наследования с помощью следующей формы:

    IRowsetChangeImpl< rowset-name, storage-name >
    

    Кроме того, BEGIN_COM_MAP добавьте COM_INTERFACE_ENTRY(IRowsetChange) раздел в класс набора строк.

    Добавление IRowsetUpdate

    Добавьте IRowsetUpdate в цепочку наследования с помощью следующей формы:

    IRowsetUpdateImpl< rowset-name, storage>
    

    Примечание.

    Вы должны удалить IRowsetChangeImpl строку из цепочки наследования. Это одно исключение из директивы, упоминаемой ранее, должно включать код для IRowsetChangeImpl.

  2. Добавьте следующую команду на карту 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)
  3. В команде добавьте следующее в карту набора свойств (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)
  4. На карте набора свойств необходимо также включить все следующие параметры, как они отображаются ниже:

    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, которое можно записать, и что все данные в столбце имеют одинаковую длину. Если вы хотите, чтобы данные в столбце имели переменную длину, этот флаг не будет задан.

См. также

Создание поставщика OLE DB