다음을 통해 공유


업데이트 가능 공급자 만들기

Visual C++는 데이터 저장소를 업데이트(쓰기)할 수 있는 업데이트 가능한 공급자 또는 공급자를 지원합니다. 이 항목에서는 OLE DB 템플릿을 사용하여 업데이트할 수 있는 공급자를 만드는 방법에 대해 설명합니다.

이 항목에서는 사용자가 작업 가능한 공급자로 시작한다고 가정합니다. 호환 가능한 공급자를 만드는 두 가지 단계가 있습니다. 먼저 공급자가 데이터 저장소를 변경하는 방법을 결정해야 합니다. 특히 업데이트 명령이 실행될 때까지 변경 내용을 즉시 수행하거나 연기할지 여부입니다. "공급자를 Updatable로 만들기" 섹션에서는 공급자 코드에서 수행해야 하는 변경 내용과 설정을 설명합니다.

다음으로, 공급자에 소비자가 요청할 수 있는 모든 기능을 지원하는 모든 기능이 포함되어 있는지 확인해야 합니다. 소비자가 데이터 저장소를 업데이트하려는 경우 공급자는 데이터 저장소에 데이터를 유지하는 코드를 포함해야 합니다. 예를 들어 C 런타임 라이브러리 또는 MFC를 사용하여 데이터 원본에서 이러한 작업을 수행할 수 있습니다. "데이터 원본에 쓰기" 섹션에서는 데이터 원본에 쓰고, NULL 및 기본값을 처리하고, 열 플래그를 설정하는 방법을 설명합니다.

참고 항목

UpdatePV 는 업데이트 가능한 공급자의 예입니다. UpdatePV는 MyProv와 동일하지만 업데이트할 수 있는 지원을 제공합니다.

공급자를 업다이팅 가능하게 만들기

공급자를 업데이터로 만드는 핵심은 공급자가 데이터 저장소에서 수행할 작업과 공급자가 이러한 작업을 수행하는 방법을 이해하는 것입니다. 특히 주요 문제는 업데이트 명령이 실행될 때까지 데이터 저장소에 대한 업데이트를 즉시 수행할지 또는 지연(일괄 처리)해야 하는지 여부입니다.

먼저 행 집합 클래스에서 상속할지 아니면 행 집합 클래스에서 IRowsetChangeImpl IRowsetUpdateImpl 상속할지 결정해야 합니다. 구현하도록 선택한 방법에 따라 세 가지 메서드의 기능이 영향을 SetDataInsertRowsDeleteRows받습니다.

에서 파생됩니다 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)
    

    속성 ID 및 값에 대한 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: , SetDataDeleteRows또는 InsertRow.에서 IRowsetChange지원되는 메서드를 지정하는 비트 마스크입니다.

    • DBPROP_CHANGEINSERTEDROWS: 소비자가 새로 삽입된 행을 호출 IRowsetChange::DeleteRows 하거나 SetData 호출할 수 있습니다.

    • DBPROP_IMMOBILEROWS: 행 집합이 삽입되거나 업데이트된 행의 순서를 다시 지정하지 않습니다.

    IRowsetUpdateImpl을 구현하는 경우

    구현 IRowsetUpdateImpl하는 경우 이전에 나열된 모든 속성을 설정하는 것 외에도 공급자에서 다음 속성을 IRowsetChangeImpl 설정해야 합니다.

    • DBPROP_IRowsetUpdate.

    • DBPROP_OWNINSERT: READ_ONLY and VARIANT_TRUE 합니다.

    • DBPROP_OWNUPDATEDELETE: READ_ONLY and VARIANT_TRUE 합니다.

    • DBPROP_OTHERINSERT: READ_ONLY and VARIANT_TRUE 합니다.

    • DBPROP_OTHERUPDATEDELETE: READ_ONLY and VARIANT_TRUE 합니다.

    • DBPROP_REMOVEDELETED: READ_ONLY and 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 및 기본값을 허용하지 않는 것이 좋습니다.

다음 예제에서는 샘플의 RUpdateRowset 클래스에서 UpdatePV 구현되는 방법을 FlushData 보여 줍니다(샘플 코드의 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 문자열을 만듭니다. 또한 해당 데이터 값이 비어 있는 경우 DBSTATUS_S_ISNULL 반환하는 재정의 IRowsetImpl::GetDBStatus 가 있습니다.

Nullable 열 표시

스키마 행 집합도 구현하는 경우(참조 IDBSchemaRowsetImpl) 구현은 DBSCHEMA_COLUMNS 행 집합(일반적으로 CxxxSchemaColSchemaRowset에 의해 공급자에 표시됨)에서 열이 null 허용되도록 지정해야 합니다.

또한 모든 nullable 열에 해당 버전의 GetColumnInfoDBCOLUMNFLAGS_ISNULLABLE 값이 포함되도록 지정해야 합니다.

OLE DB 템플릿 구현에서 열을 nullable로 표시하지 않으면 공급자는 열에 값이 포함되어야 하며 소비자가 null 값을 보낼 수 없다고 가정합니다.

다음 예제에서는 UpdatePV의 CommonGetColInfo CUpdateCommand(UpProvRS.cpp 참조)에서 함수가 구현되는 방법을 보여 줍니다. 열에 null 허용 열에 대해 이 DBCOLUMNFLAGS_ISNULLABLE 방법을 확인합니다.

/////////////////////////////////////////////////////////////////////////////
// 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 데이터와 마찬가지로 기본값 변경을 처리해야 합니다.

기본값 FlushDataExecute 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 열거형 형식을 사용하여 지정된 열 플래그를 설정해야 합니다. 열 플래그는 열 특성을 설명합니다.

예를 들어 Session.h의 CUpdateSessionColSchemaRowset 클래스 UpdatePV 에서 첫 번째 열은 다음과 같이 설정됩니다.

// 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 공급자 만들기