테이블 반환 매개 변수 사용(OLE DB)

이 예제는 SQL Server 2008 이상에서만 작동합니다. 이 예제에서는 다음을 수행합니다.

  • IOpenRowset::OpenRowset을 통해 동적 검색을 사용하여 테이블 반환 매개 변수를 만듭니다.

  • EmployeesRowset 클래스의 끌어오기 모델을 사용하여 테이블 반환 매개 변수 행을 보냅니다. 끌어오기 모델에서는 소비자가 공급자에게 데이터를 요청 시 제공합니다.

  • CPhotograph 클래스에 있는 테이블 반환 매개 변수의 일부로 BLOB을 보냅니다.

  • ISSCommandWithParameters를 사용하는 사용자 지정 매개 변수 속성을 사용합니다.

  • SQLNCLI11 오류를 처리하는 방법을 보여 줍니다.

테이블 반환 매개 변수에 대한 자세한 내용은 테이블 반환 매개 변수(SQL Server Native Client)를 참조하십시오.

첫 번째(Transact-SQL) 코드 목록은 예제에서 사용하는 데이터베이스를 만듭니다.

두 번째 코드 목록을 stdafx.h라는 파일에 배치합니다.

세 번째 코드 목록을 OLEDBUtils.hpp라는 파일에 배치합니다.

ole32.lib oleaut32.lib를 사용하여 컴파일하고 네 번째(C++) 코드 목록을 실행합니다. 이 응용 프로그램은 컴퓨터의 기본 SQL Server 인스턴스에 연결됩니다. 일부 Windows 운영 체제에서는 (localhost) 또는 (local)을 해당 SQL Server 인스턴스의 이름으로 변경해야 합니다. 명명된 인스턴스에 연결하려면 연결 문자열을 L"(local)"에서 L"(local)\\name"으로 변경합니다. 여기서 name은 명명된 인스턴스입니다. 기본적으로 SQL Server Express는 명명된 인스턴스에 설치됩니다. INCLUDE 환경 변수에 sqlncli.h가 들어 있는 디렉터리를 포함해야 합니다.

다섯 번째(Transact-SQL) 코드 목록은 예제에서 사용하는 데이터베이스를 만듭니다.

create database testdb
go
use testdb
go
create table tblEmployees (
id int identity primary key,
name nvarchar(50) not null,
birthday date null,
salary int null,
photograph varbinary(max) null
)
go

create type tvpEmployees as table(
name nvarchar(50) not null,
birthday date null,
salary int null,
photograph varbinary(max) null
)
go

create procedure insertEmployees @tvpEmployees tvpEmployees readonly, 
@id int output as
insert tblEmployees(name, birthday, salary, photograph)
select name, birthday, salary, photograph from @tvpEmployees
select @id = coalesce(scope_identity(), -1)
go

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//

#pragma once

// The following macros define the minimum required platform.  The minimum required platform
// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run 
// your application.  The macros work by enabling all features available on platform versions up to and 
// including the version specified.

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef _WIN32_WINNT            // Specifies that the minimum required platform is Windows Vista.
#define _WIN32_WINNT 0x0600     // Change this to the appropriate value to target other versions of Windows.
#endif

#include <stdio.h>
#include <tchar.h>
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>
#include <windows.h>
#include <strsafe.h>
// #defines necessary for initializing the CLSID & IIDs of OLEDB specific interfaces
#define DBINITCONSTANTS
#define INITGUID
#include <oledberr.h>
#include <sqlncli.h>

// OLEDBUtils.hpp
#pragma once

// Utility Macros & Functions
#define CHKHR_GOTO(hr, Label) \
   { if (FAILED(hr)) { wprintf(L"Error in file %S at line %d.\n", __FILE__, __LINE__); goto Label;} };

#define CHKHR_GOTO_MSG(hr, Label, wszMessage) \
{ if (FAILED(hr)) { wprintf(L"Error in file %S at line %d.\nError Message: %s\n", __FILE__, __LINE__, wszMessage); goto Label;} };

#define CHKHR_OLEDB_GOTO(hr, Label, pItf, IID_Itf) \
{ if (FAILED(hr)) { wprintf(L"Error in file %S at line %d.\n", __FILE__, __LINE__); DumpErrorInfo(pItf, IID_Itf); goto Label;} };

#define NUMELEM(arr) (sizeof(arr) / sizeof(arr[0])) 

// Template function that checks the NULL-ness of a COM interface and if it is non-NULL releases it & also sets it to NULL
template<class T>
void Release(T** pUnkCOMItf) {
if (*pUnkCOMItf) {
(*pUnkCOMItf)->Release();
*pUnkCOMItf = NULL;
}
}

// Utility routine for displaying OLEDB errors
void DumpErrorInfo (
    IUnknown* pObjectWithError,
    REFIID IID_InterfaceWithError
);

// COM Load/Unload Helper
class CCOMLoader {
public:
HRESULT Load() {
return CoInitializeEx(NULL, COINIT_MULTITHREADED);
}
~CCOMLoader() {
CoUninitialize();
}
};

// Represents an OLEDB data source, used for connection & session creation
class CSQLNCLIDataSource {
private:
bool m_fIsConnected;
IDBInitialize* m_pIDBInitialize;   // Data Source Initialization interface

public:
CSQLNCLIDataSource() : m_pIDBInitialize(NULL), m_fIsConnected(false) {}

      HRESULT Connect(const wchar_t* wszDataSource, const wchar_t* wszCatalog) {
         HRESULT hr = S_OK;
         IDBProperties* pIDBProperties = NULL;

         const ULONG INIT_PROPS = 3; 
         DBPROP rgInitProps[INIT_PROPS] = {0};
         const ULONG INIT_PROPSETS = 1;
         DBPROPSET rgInitPropSets[INIT_PROPSETS] = {0};

         //Obtain access to the SQLOLEDB provider.
         hr = CoCreateInstance(CLSID_SQLNCLI11, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize, reinterpret_cast<void **>(&m_pIDBInitialize));
         CHKHR_GOTO_MSG(hr, _Exit, L"Unable to load SQLNCLI11");

         // Set initialization property values
         SetPropertyBSTR(DBPROP_INIT_DATASOURCE, wszDataSource,  &rgInitProps[0]);
         SetPropertyBSTR(DBPROP_INIT_CATALOG,    wszCatalog, &rgInitProps[1]);
         SetPropertyBSTR(DBPROP_AUTH_INTEGRATED, L"SSPI", &rgInitProps[2]);

         // Setup the initialization property sets
         InitializePropSet(
            rgInitPropSets,
            NUMELEM(rgInitProps),
            DBPROPSET_DBINIT,
            rgInitProps);

         hr = m_pIDBInitialize->QueryInterface(IID_IDBProperties, reinterpret_cast<void**>(&pIDBProperties));
         CHKHR_GOTO_MSG(hr, _Exit, L"Failure to QI IDBInitialize.");

         hr = pIDBProperties->SetProperties(NUMELEM(rgInitPropSets), rgInitPropSets);
         CHKHR_OLEDB_GOTO(hr, _Exit, pIDBProperties, IID_IDBProperties);

         hr = m_pIDBInitialize->Initialize();
         CHKHR_OLEDB_GOTO(hr, _Exit, m_pIDBInitialize, IID_IDBInitialize);

         m_fIsConnected = true;

_Exit:
         Release(&pIDBProperties);
         CleanPropSet(rgInitPropSets);
         return hr;
      }

HRESULT GetSession(IOpenRowset** ppIOpenRowset) {
assert(m_pIDBInitialize);
assert(m_fIsConnected);

HRESULT hr = S_OK;
IDBCreateSession* pIDBCreateSession = NULL;

        if (m_pIDBInitialize)
           hr = m_pIDBInitialize->QueryInterface(IID_IDBCreateSession, reinterpret_cast<void**>(&pIDBCreateSession));
CHKHR_GOTO_MSG(hr, _Exit, L"Failure to QI IDBCreateSession.");

        if (pIDBCreateSession)
           hr = pIDBCreateSession->CreateSession(NULL, IID_IOpenRowset, reinterpret_cast<IUnknown**>(ppIOpenRowset));
CHKHR_OLEDB_GOTO(hr, _Exit, pIDBCreateSession, IID_IDBCreateSession);
_Exit:
Release(&pIDBCreateSession);
return hr;
}

    ~CSQLNCLIDataSource() {
if (m_fIsConnected) {
assert(m_pIDBInitialize);
            HRESULT hr = S_OK;
            if (m_pIDBInitialize)
               hr = m_pIDBInitialize->Uninitialize();
CHKHR_OLEDB_GOTO(hr, _Exit, m_pIDBInitialize, IID_IDBInitialize);
}
_Exit: 
Release(&m_pIDBInitialize);
}

private:
void InitializePropSet( DBPROPSET* pPropSet, ULONG cProps, GUID guidPropSet, DBPROP* pProps ) {
pPropSet->cProperties     = cProps;
pPropSet->guidPropertySet = guidPropSet;
pPropSet->rgProperties    = pProps;
}

void CleanPropSet (DBPROPSET*  pPropSet) {
for (ULONG idxProp = 0; idxProp < pPropSet->cProperties; idxProp++)
if (pPropSet->rgProperties[idxProp].vValue.vt == VT_BSTR)
SysFreeString(pPropSet->rgProperties[idxProp].vValue.bstrVal);
}

void SetPropertyBSTR (DBPROPID propID, const wchar_t*  wszValue, DBPROP* pProperty) {
pProperty->dwPropertyID    = propID;
pProperty->dwOptions       = DBPROPOPTIONS_REQUIRED;
pProperty->colid           = DB_NULLID;
pProperty->vValue.vt       = VT_BSTR;
pProperty->vValue.bstrVal  = SysAllocStringLen(wszValue, (UINT)wcslen(wszValue));
}

void SetPropertyBool( DBPROP* pProperty, DBPROPID dwPropID, VARIANT_BOOL boolValue) {
pProperty->dwPropertyID     = dwPropID;
pProperty->dwOptions        = DBPROPOPTIONS_REQUIRED;
pProperty->colid            = DB_NULLID;
pProperty->vValue.vt        = VT_BOOL;
pProperty->vValue.boolVal   = boolValue;
}

void SetPropertyI8 ( DBPROP* pProperty, DBPROPID dwPropID, LONGLONG i8Value ) {
pProperty->dwPropertyID     = dwPropID;
pProperty->dwOptions        = DBPROPOPTIONS_REQUIRED;
pProperty->colid            = DB_NULLID;
pProperty->vValue.vt        = VT_I8;
pProperty->vValue.llVal     = i8Value;
}
};

class CPhotograph : public ISequentialStream {
private:
    DBREFCOUNT  m_cRef;
    BYTE*       m_pbStream;
    size_t      m_cbStreamLength;
    size_t      m_idxStreamOffset;
    
public:
    CPhotograph(size_t cbStreamLength) : m_cbStreamLength(cbStreamLength), m_idxStreamOffset(0), m_cRef(1) {
        m_pbStream = new BYTE[m_cbStreamLength];

// Generate random data for the photograph stream
for (size_t i = 0; i < m_cbStreamLength; i++)
m_pbStream[i] = static_cast<BYTE>(rand() % 256);
    }
    
    ~CPhotograph() {
        delete [] m_pbStream;
    }

    STDMETHODIMP QueryInterface(REFIID riid, LPVOID* ppv) {
        if (ppv == NULL)
           return E_INVALIDARG;

        if (riid == IID_IUnknown || riid == IID_ISequentialStream)
            *ppv = reinterpret_cast<void*>(this);
        else
            *ppv = NULL;

        if (*ppv) {
            (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
            return S_OK;
        }

        return E_NOINTERFACE;   
    }

    // @cmember Increments the Reference count
    STDMETHODIMP_(DBREFCOUNT) AddRef() {
        return InterlockedIncrement((long*)&m_cRef);
    }

    STDMETHODIMP_(DBREFCOUNT) Release() {
        assert(m_cRef > 0);
        ULONG cRef = InterlockedDecrement((long*) &m_cRef);

        if (!cRef)
            delete this;
        
        return cRef;
    }

    STDMETHODIMP Read(void* pBuffer, ULONG cb, ULONG* pcb) {
        if (pcb) 
           *pcb = 0;
        
        if (m_idxStreamOffset == m_cbStreamLength)
            return S_FALSE;

        size_t cbRemainingBytes = m_cbStreamLength - m_idxStreamOffset;

        if (pcb) 
           *pcb = min(cb, static_cast<ULONG>(cbRemainingBytes));
        
        memcpy(pBuffer, m_pbStream + m_idxStreamOffset,  min(cb, cbRemainingBytes)); 
        return S_OK;
    }

    STDMETHODIMP Write(const void*, ULONG, ULONG* /*pcb*/ ) {
        return E_NOTIMPL;
    }
};

void DumpErrorInfo (IUnknown*   pObjectWithError, REFIID IID_InterfaceWithError) {
    // Interfaces used in the example.
    IErrorInfo*             pIErrorInfoAll          = NULL;
    IErrorInfo*             pIErrorInfoRecord       = NULL;
    IErrorRecords*          pIErrorRecords          = NULL;
    ISupportErrorInfo*      pISupportErrorInfo      = NULL;
    ISQLErrorInfo*          pISQLErrorInfo          = NULL;
    ISQLServerErrorInfo*    pISQLServerErrorInfo    = NULL;

    // Number of error records.
    ULONG                   nRecs;
    ULONG                   nRec;

    // Basic error information from GetBasicErrorInfo.
    ERRORINFO               errorinfo;

    // IErrorInfo values.
    BSTR                    bstrDescription;
    BSTR                    bstrSource;

    // ISQLErrorInfo parameters.
    BSTR                    bstrSQLSTATE;
    LONG                    lNativeError;
 
    // ISQLServerErrorInfo parameter pointers.
    SSERRORINFO*            pSSErrorInfo    = NULL;
    OLECHAR*                pSSErrorStrings = NULL;

    // Obtain the default locale ID
    DWORD                   MYLOCALEID = GetUserDefaultLCID();

    // Only ask for error information if the interface supports it.
    if (FAILED(pObjectWithError->QueryInterface(IID_ISupportErrorInfo, (void**) &pISupportErrorInfo)))
        return;

    if (FAILED(pISupportErrorInfo->InterfaceSupportsErrorInfo(IID_InterfaceWithError)))
        return;

    // Do not test the return of GetErrorInfo. It can succeed and return
    // a NULL pointer in pIErrorInfoAll. Simply test the pointer.
    if (GetErrorInfo(0, &pIErrorInfoAll) == S_FALSE) {
        pISupportErrorInfo->Release();
        pISupportErrorInfo = NULL;
        return;
    }

    if (pIErrorInfoAll != NULL) {
        // Test to see if it's a valid OLE DB IErrorInfo interface 
        // exposing a list of records.
        if (SUCCEEDED(pIErrorInfoAll->QueryInterface(IID_IErrorRecords,(void**) &pIErrorRecords))) {
            pIErrorRecords->GetRecordCount(&nRecs);

            // Within each record, retrieve information from each
            // of the defined interfaces.
            for (nRec = nRecs - 1; (long)nRec >= 0; nRec--) {
                // From IErrorRecords, get the HRESULT and a reference
                // to the ISQLErrorInfo interface.
                pIErrorRecords->GetBasicErrorInfo(nRec, &errorinfo);
                pIErrorRecords->GetCustomErrorObject(nRec,IID_ISQLErrorInfo, (IUnknown**) &pISQLErrorInfo);

                if (pISQLErrorInfo != NULL) {
                    pISQLErrorInfo->GetSQLInfo(&bstrSQLSTATE, &lNativeError);

                    if (bstrSQLSTATE[0] == '0' && bstrSQLSTATE[1] == '1') {}
                    else {
                        // Display the SQLSTATE and native error values.
                        wprintf(L"SQLSTATE:\t%s\nNative Error:\t%ld\n",
                            bstrSQLSTATE, lNativeError);
                        
                        // SysFree BSTR references.
                        SysFreeString(bstrSQLSTATE);
                    }

                    // Get the ISQLServerErrorInfo interface from
                    // ISQLErrorInfo before releasing the reference.
                    pISQLErrorInfo->QueryInterface(IID_ISQLServerErrorInfo, (void**) &pISQLServerErrorInfo);

                    pISQLErrorInfo->Release();
                    pISQLErrorInfo = NULL;
                }

                // Test to ensure the reference is valid, then
                // get error information from ISQLServerErrorInfo.
                if (pISQLServerErrorInfo != NULL) {
                    pISQLServerErrorInfo->GetErrorInfo(&pSSErrorInfo,&pSSErrorStrings);

                    // ISQLServerErrorInfo::GetErrorInfo succeeds
                    // even when it has nothing to return. Test the
                    // pointers before using.
                    if (pSSErrorInfo) {
                        // Display the state and severity from the
                        // returned information. The error message comes
                        // from IErrorInfo::GetDescription.
                        wprintf(L"Error state:\t%d\nSeverity:\t%d\n",
                                pSSErrorInfo->bState,
                                pSSErrorInfo->bClass);

// IMalloc::Free needed to release references
                        // on returned values. For the example, assume
                        // the g_pIMalloc pointer is valid.
                        CoTaskMemFree(pSSErrorStrings);
                        CoTaskMemFree(pSSErrorInfo);
                    }

                    pISQLServerErrorInfo->Release();
                    pISQLServerErrorInfo = NULL;
                }

                if (SUCCEEDED(pIErrorRecords->GetErrorInfo(nRec,MYLOCALEID, &pIErrorInfoRecord))) {
                    // Get the source and description (error message)
                    // from the record's IErrorInfo.
                    pIErrorInfoRecord->GetSource(&bstrSource);
                    pIErrorInfoRecord->GetDescription(&bstrDescription);

                    if (bstrSource != NULL) {
                        wprintf(L"Source:\t\t%s\n", bstrSource);
                        
                        SysFreeString(bstrSource);
                    }
                    if (bstrDescription != NULL) {
                        wprintf(L"Error message:\t%s\n", bstrDescription);
                        SysFreeString(bstrDescription);
                    }
                    pIErrorInfoRecord->Release();
                    pIErrorInfoRecord = NULL;
                }
            }

            pIErrorRecords->Release();
            pIErrorRecords = NULL;
        }
        else {
            // IErrorInfo is valid; get the source and
            // description to see what it is.
            pIErrorInfoAll->GetSource(&bstrSource);
            pIErrorInfoAll->GetDescription(&bstrDescription);

            if (bstrSource != NULL) {
                wprintf(L"Source:\t\t%s\n", bstrSource);
                SysFreeString(bstrSource);
            }
            if (bstrDescription != NULL) {
                wprintf(L"Error message:\t%s\n", bstrDescription);
                SysFreeString(bstrDescription);
            }
        }

        pIErrorInfoAll->Release();
        pIErrorInfoAll = NULL;
    }

    pISupportErrorInfo->Release();
    pISupportErrorInfo = NULL;
}

// compile with: /D "_UNICODE" /D "UNICODE" ole32.lib oleaut32.lib
#include "stdafx.h"
#include "OLEDBUtils.hpp"

class BaseAggregatingRowset : public IRowset {
public:
   BaseAggregatingRowset(DBCOUNTITEM cTotalRows) : m_cRef(0), m_idxRow(1), m_cTotalRows(cTotalRows), m_pUnkInnerSQLNCLIRowset(NULL) {
      m_hAccessor[0] = 0;
   }

      virtual HRESULT SetupAccessors(IAccessor* pIAccessorTVP) = 0;

      STDMETHODIMP_(ULONG) AddRef() {
         ULONG cRef = InterlockedIncrement((long*)&m_cRef);
         return cRef;
      }

      STDMETHODIMP_(ULONG) Release() {
         assert(m_cRef > 0);

         ULONG cRef = InterlockedDecrement((long *) &m_cRef);

         if (!cRef)
            delete this;

         return cRef;
      }

      // In QueryInterface, delegate to Inner Rowset for anything but IRowset & IUnknown
      STDMETHODIMP QueryInterface (REFIID  riid, LPVOID* ppv ) {
         if (riid == IID_IUnknown) 
            *ppv = static_cast<IUnknown*>(this);
         else {
            // If we are not initialized yet and somebody is asking for non-Unk interface
            if (!m_pUnkInnerSQLNCLIRowset) {
               *ppv = NULL;
               return E_NOINTERFACE;
            }

            if (riid == IID_IRowset) 
               *ppv = static_cast<IUnknown*>(this);
            else
               return m_pUnkInnerSQLNCLIRowset->QueryInterface(riid, ppv);
         }

         (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
         return S_OK;
      }

      STDMETHODIMP AddRefRows (DBCOUNTITEM, const HROW[], DBREFCOUNT[], DBROWSTATUS[]) {
         // Never gets called, so we can return E_NOTIMPL
         return E_NOTIMPL;
      }

      // Read the data from storage, allocate row handles and give 
      // them back to the caller.
      STDMETHODIMP GetNextRows( HCHAPTER, DBROWOFFSET cRowsToSkip, DBROWCOUNT cRows, DBCOUNTITEM* pcRowsObtained, HROW** prghRows) {
         assert(cRowsToSkip == 0);
         assert(cRows == 1);
         assert(*prghRows);

         *pcRowsObtained = 0;

         // If we still have rows to give back
         if (m_idxRow <= m_cTotalRows) {
            *pcRowsObtained = 1;

            // For us, row handle is simply an index in our row list
            HROW* phRows = *prghRows;
            *phRows = m_idxRow;
            m_idxRow++;

            return S_OK;
         }
         else
            return DB_S_ENDOFROWSET;
      }

      // Release data that is not needed corresponding to row handle
      STDMETHODIMP ReleaseRows(DBCOUNTITEM cRows, const HROW rghRows[], DBROWOPTIONS[], DBREFCOUNT[], DBROWSTATUS[]) {
         assert(cRows == 1);
         assert(rghRows[0] <= m_cTotalRows);
         return S_OK;
      }

      STDMETHODIMP GetData(HROW, HACCESSOR hAccessor, void*) {
#ifdef _DEBUG
         DBORDINAL idxAccessor;
         for (idxAccessor = 0; idxAccessor < 1; idxAccessor++) {
            if (m_hAccessor[idxAccessor] == hAccessor)
               break;
         }
         assert(idxAccessor < 1);
#endif
         return S_OK;
      }

      STDMETHODIMP RestartPosition( HCHAPTER) {
         m_idxRow = 1;
         return S_OK;
      }

protected:
   DBCOUNTITEM m_idxRow;
   IUnknown* m_pUnkInnerSQLNCLIRowset;

   // Make the destructor private, so that the object is only creatable on the heap
   virtual ~BaseAggregatingRowset() {
      HRESULT hr = S_OK;
      if (m_hAccessor[0]) {
         IAccessor* pIAccessor = NULL;
         hr = m_pUnkInnerSQLNCLIRowset->QueryInterface(IID_IAccessor, reinterpret_cast<void**>(&pIAccessor));
         assert(SUCCEEDED(hr));
         hr = pIAccessor->ReleaseAccessor(m_hAccessor[0], NULL);
         assert(SUCCEEDED(hr));
      }
      ::Release(&m_pUnkInnerSQLNCLIRowset);
   }

   // Save the handle of the accessor that we create, the indexing is 0 based
   void SetAccessorHandle(DBORDINAL idxAccessor, HACCESSOR hAccessor) {
      m_hAccessor[idxAccessor] = hAccessor;
   }

private:
   ULONG m_cRef;
   DBCOUNTITEM  m_cTotalRows;
   // Defining as an array because in general there can be as many accessors as necessary
   // the reading rules from the provider for such scenarios are describe in the Books online
   HACCESSOR m_hAccessor[1];
};

// There is just 1 accessor for this Rowset
class EmployeesRowset : public BaseAggregatingRowset {
private:
   struct EmployeeData {
      DBLENGTH  nameLength;
      DBSTATUS  nameStatus;
      wchar_t   nameValue[50 + 1];
      DBLENGTH  birthdayLength;
      DBSTATUS  birthdayStatus;
      DBDATE    birthdayValue;
      DBLENGTH  salaryLength;
      DBSTATUS  salaryStatus;
      long      salaryValue;
      DBLENGTH  photographLength;
      DBSTATUS  photographStatus;
      IUnknown* photographValue;
   };

protected:
   // Make the destructor private, so that the object is only creatable on the heap
   virtual ~EmployeesRowset() {}

public:
   EmployeesRowset ( DBCOUNTITEM cTotalRows ) : BaseAggregatingRowset(cTotalRows) {
      // For the random number generator, used for producing dummy random data
      srand(123456);
   }

   // Set up aggregator & aggregatee relationship here
   HRESULT Initialize(IOpenRowset* pIOpenRowset) {
      HRESULT hr = S_OK;

      IUnknown* pUnkOuter = static_cast<IUnknown*>(this);
      IAccessor* pIAccessorEmployees = NULL;

      DBID dbidEmployees;
      dbidEmployees.eKind = DBKIND_GUID_NAME;
      dbidEmployees.uGuid.guid = CLSID_ROWSET_TVP;
      dbidEmployees.uName.pwszName = L"tvpEmployees";

      hr = pIOpenRowset->OpenRowset(pUnkOuter, &dbidEmployees, NULL, IID_IUnknown, 0, NULL, &m_pUnkInnerSQLNCLIRowset);
      CHKHR_OLEDB_GOTO(hr, _Exit, pIOpenRowset, IID_IOpenRowset);

      hr = pUnkOuter->QueryInterface(IID_IAccessor, reinterpret_cast<void**>(&pIAccessorEmployees));
      CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IAccessor.");

      hr = SetupAccessors(pIAccessorEmployees);
      CHKHR_GOTO(hr, _Exit);

_Exit:
      ::Release(&pIAccessorEmployees);
      return hr;
   }

   STDMETHODIMP GetData ( HROW hRow, HACCESSOR hAccessor, void* pData ) {
      // The base implementation just does validation, could have possibly
      // been made an abstract virtual function
      BaseAggregatingRowset::GetData(hRow, hAccessor, pData);

      // Use m_hAccessor, to figure out which accessor caller specified, and write the columns data 
      // for columns corresponding to those accessors into pData. Fetch the data into provided buffer, 
      // we will know the format of these accessors, because we created them.

      EmployeeData* pCurrentEmployee = reinterpret_cast<EmployeeData*>(pData);
      FillRowData(pCurrentEmployee);

      return S_OK;
   }
private:

   HRESULT SetupAccessors(IAccessor* pIAccessorEmployees) {
      HRESULT hr = S_OK;

      DBBINDING bindingsEmployees[4];
      FillBindingsAndSetupRowBuffer(bindingsEmployees);

      HACCESSOR hAccessorEmployees;
      DBBINDSTATUS bindStatusEmployees[4] = {DBBINDSTATUS_OK, DBBINDSTATUS_OK, DBBINDSTATUS_OK, DBBINDSTATUS_OK};

      hr = pIAccessorEmployees->CreateAccessor(
         DBACCESSOR_ROWDATA, 
         4, 
         bindingsEmployees, 
         sizeof(EmployeeData),
         &hAccessorEmployees, 
         bindStatusEmployees); 
      CHKHR_OLEDB_GOTO(hr, _Exit, pIAccessorEmployees, IID_IAccessor);

      SetAccessorHandle(0, hAccessorEmployees);
_Exit:
      return hr;
   }

   // This routine does the job of populating data for each row, in real world scenarios, hRow could
   // possibly be passed here, in order to identify the particular row of data & it could be read
   // from some persistent medium like disk/network/UI-controls etc
   void FillRowData(EmployeeData* pCurrentEmployee) {
      pCurrentEmployee->birthdayStatus = DBSTATUS_S_OK;
      pCurrentEmployee->birthdayLength = sizeof(DBDATE);
      pCurrentEmployee->birthdayValue.day   = 15;
      pCurrentEmployee->birthdayValue.month = 5;
      pCurrentEmployee->birthdayValue.year  = 1980;

      pCurrentEmployee->salaryLength = sizeof(long);
      pCurrentEmployee->salaryStatus = DBSTATUS_S_OK;
      pCurrentEmployee->salaryValue  = 100000;

      pCurrentEmployee->nameLength = static_cast<DBLENGTH>(wcslen(L"John Doe") * sizeof(wchar_t));
      pCurrentEmployee->nameStatus = DBSTATUS_S_OK;
      StringCchCopy(pCurrentEmployee->nameValue, sizeof(pCurrentEmployee->nameValue) / sizeof(wchar_t), L"John Doe");

      pCurrentEmployee->photographLength = 2000 + (rand() % 2000);
      pCurrentEmployee->photographStatus = DBSTATUS_S_OK;
      pCurrentEmployee->photographValue  = new CPhotograph(pCurrentEmployee->photographLength);
   }

   void FillBindingsAndSetupRowBuffer(DBBINDING* pBindingsEmployees) {
      for (DBORDINAL i = 0; i < 4; i++) {
         pBindingsEmployees[i].pTypeInfo = NULL;
         pBindingsEmployees[i].pObject = NULL;
         pBindingsEmployees[i].pBindExt = NULL;
         pBindingsEmployees[i].eParamIO = DBPARAMIO_NOTPARAM;
         pBindingsEmployees[i].iOrdinal = i + 1;
         pBindingsEmployees[i].dwPart = DBPART_LENGTH | DBPART_VALUE | DBPART_STATUS;
         pBindingsEmployees[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
         pBindingsEmployees[i].dwFlags = 0;
      }

      pBindingsEmployees[0].wType = DBTYPE_WSTR;
      pBindingsEmployees[0].cbMaxLen = (50 + 1) * sizeof(wchar_t);
      pBindingsEmployees[0].obLength = offsetof(EmployeeData, nameLength);
      pBindingsEmployees[0].obStatus = offsetof(EmployeeData, nameStatus);
      pBindingsEmployees[0].obValue = offsetof(EmployeeData, nameValue);
      pBindingsEmployees[1].wType = DBTYPE_DBDATE;
      pBindingsEmployees[1].cbMaxLen = sizeof(DBDATE);
      pBindingsEmployees[1].obLength = offsetof(EmployeeData, birthdayLength);
      pBindingsEmployees[1].obStatus = offsetof(EmployeeData, birthdayStatus);
      pBindingsEmployees[1].obValue = offsetof(EmployeeData, birthdayValue);
      pBindingsEmployees[2].wType = DBTYPE_I4;
      pBindingsEmployees[2].cbMaxLen = sizeof(long);
      pBindingsEmployees[2].obLength = offsetof(EmployeeData, salaryLength);
      pBindingsEmployees[2].obStatus = offsetof(EmployeeData, salaryStatus);
      pBindingsEmployees[2].obValue = offsetof(EmployeeData, salaryValue);
      pBindingsEmployees[3].wType = DBTYPE_IUNKNOWN;
      pBindingsEmployees[3].cbMaxLen = sizeof(IUnknown*);
      pBindingsEmployees[3].obLength = offsetof(EmployeeData, photographLength);
      pBindingsEmployees[3].obStatus = offsetof(EmployeeData, photographStatus);
      pBindingsEmployees[3].obValue = offsetof(EmployeeData, photographValue);
   }
};

HRESULT PopulateEmployees(IDBCreateCommand* pIDBCreateCommand, IRowset* pIRowsetEmployees) {
   HRESULT hr = S_OK;

   // Create the RPC call
   ICommandText* pICommandText = NULL;
   ISSCommandWithParameters* pISSCommandWithParameters = NULL;
   IAccessor* pIAccessorCmd = NULL;
   HACCESSOR hAccessorCmd;
   DBBINDING bindingsCmd  [2] = {0};
   DBBINDSTATUS bindStatusCmd[2] = {DBBINDSTATUS_OK, DBBINDSTATUS_OK};
   DBOBJECT dbObjTVP = {STGM_READ, IID_IRowset};

   hr = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, reinterpret_cast<IUnknown**>(&pICommandText));
   CHKHR_OLEDB_GOTO(hr, _Exit, pIDBCreateCommand, IID_IDBCreateCommand);

   hr = pICommandText->SetCommandText(DBGUID_DEFAULT, L"{call insertEmployees(?, ?)}");
   CHKHR_OLEDB_GOTO(hr, _Exit, pICommandText, IID_ICommandText);

   hr = pICommandText->QueryInterface(IID_ISSCommandWithParameters, reinterpret_cast<void**>(&pISSCommandWithParameters));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IUnknown for ISSCommandWithParameters.");

   // Give the parameter information to the provider
   const DB_UPARAMS rgParamOrdinalsEmployees[2] = {1, 2};
   DBPARAMBINDINFO rgParamBindInfoEmployees[2] = {0};

   rgParamBindInfoEmployees[0].pwszDataSourceType = L"table";
   rgParamBindInfoEmployees[0].pwszName = L"@tvpEmployees";
   rgParamBindInfoEmployees[0].ulParamSize = ~0UL;
   rgParamBindInfoEmployees[0].dwFlags = DBPARAMFLAGS_ISINPUT;

   rgParamBindInfoEmployees[1].pwszDataSourceType = L"DBTYPE_I4";
   rgParamBindInfoEmployees[1].pwszName = L"@id";
   rgParamBindInfoEmployees[1].ulParamSize = sizeof(long);
   rgParamBindInfoEmployees[1].dwFlags = DBPARAMFLAGS_ISOUTPUT;

   hr = pISSCommandWithParameters->SetParameterInfo(2, rgParamOrdinalsEmployees, rgParamBindInfoEmployees);
   CHKHR_OLEDB_GOTO(hr, _Exit, pISSCommandWithParameters, IID_ISSCommandWithParameters);

   DBPROP ssPropParam [1] = {0};
   DBPROPSET ssPropsetParam [1];
   SSPARAMPROPS ssParamProps [1];

   ssPropParam[0].dwPropertyID = SSPROP_PARAM_TYPE_TYPENAME;
   ssPropParam[0].vValue.vt = VT_BSTR;
   ssPropParam[0].vValue.bstrVal = SysAllocString(L"tvpEmployees");

   ssPropsetParam[0].cProperties = 1;
   ssPropsetParam[0].guidPropertySet = DBPROPSET_SQLSERVERPARAMETER;
   ssPropsetParam[0].rgProperties = ssPropParam;

   ssParamProps[0].cPropertySets = 1;
   ssParamProps[0].iOrdinal = 1;
   ssParamProps[0].rgPropertySets = ssPropsetParam;

   hr = pISSCommandWithParameters->SetParameterProperties(1, ssParamProps);
   SysFreeString(ssPropParam[0].vValue.bstrVal);
   CHKHR_OLEDB_GOTO(hr, _Exit, pISSCommandWithParameters, IID_ISSCommandWithParameters);

   struct PARAMDATA {
      DBLENGTH employeesLength;
      DBSTATUS employeesStatus;
      IUnknown * employeesValue;
      DBLENGTH idLength;
      DBSTATUS idStatus;
      long idValue;
   };

   PARAMDATA cmdParamData;

   hr = pICommandText->QueryInterface(IID_IAccessor, reinterpret_cast<void**>(&pIAccessorCmd));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IUnknown for IAccessor.");

   // Define the binding information
   bindingsCmd[0].wType = DBTYPE_TABLE;
   bindingsCmd[0].cbMaxLen = sizeof(IUnknown*);
   bindingsCmd[0].pObject = &dbObjTVP;
   bindingsCmd[0].eParamIO = DBPARAMIO_INPUT;
   bindingsCmd[0].iOrdinal = 1;
   bindingsCmd[0].dwPart = DBPART_LENGTH | DBPART_VALUE | DBPART_STATUS;
   bindingsCmd[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
   bindingsCmd[0].obLength = offsetof(PARAMDATA, employeesLength);
   bindingsCmd[0].obStatus = offsetof(PARAMDATA, employeesStatus);
   bindingsCmd[0].obValue = offsetof(PARAMDATA, employeesValue);
   bindingsCmd[1].wType = DBTYPE_I4;
   bindingsCmd[1].cbMaxLen = sizeof(long);
   bindingsCmd[1].pObject = NULL;
   bindingsCmd[1].eParamIO = DBPARAMIO_OUTPUT;
   bindingsCmd[1].iOrdinal = 2;
   bindingsCmd[1].dwPart = DBPART_LENGTH | DBPART_VALUE | DBPART_STATUS;
   bindingsCmd[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
   bindingsCmd[1].obLength = offsetof(PARAMDATA, idLength);
   bindingsCmd[1].obStatus = offsetof(PARAMDATA, idStatus);
   bindingsCmd[1].obValue = offsetof(PARAMDATA, idValue);

   hr = pIAccessorCmd->CreateAccessor(
      DBACCESSOR_PARAMETERDATA, 
      2, 
      bindingsCmd, 
      sizeof(PARAMDATA), 
      &hAccessorCmd, 
      bindStatusCmd);
   CHKHR_OLEDB_GOTO(hr, _Exit, pIAccessorCmd, IID_IAccessor);

   // Fill cmdParamData with parameter values
   cmdParamData.employeesLength = sizeof(IUnknown*);
   cmdParamData.employeesStatus = DBSTATUS_S_OK;
   cmdParamData.employeesValue = pIRowsetEmployees;
   cmdParamData.idLength = sizeof(long);
   cmdParamData.idStatus = DBSTATUS_S_OK;
   cmdParamData.idValue = 0;

   // Execute the command
   DBPARAMS cmdParams;
   cmdParams.cParamSets = 1;
   cmdParams.hAccessor = hAccessorCmd;
   cmdParams.pData = &cmdParamData;

   hr = pICommandText->Execute(NULL, IID_NULL, &cmdParams, NULL, NULL);
   CHKHR_OLEDB_GOTO(hr, _Exit, pICommandText, IID_ICommandText);

   wprintf(L"Employee table population completed. ID : %d.\n", cmdParamData.idValue);

_Exit:
   Release(&pIAccessorCmd);
   Release(&pISSCommandWithParameters);
   Release(&pICommandText);

   return hr;
}

int main() {
   HRESULT hr = S_OK;

   CCOMLoader comLoader;
   CSQLNCLIDataSource dso;

   IOpenRowset*pIOpenRowset = NULL;
   IDBCreateCommand* pIDBCreateCommand = NULL;
   IRowset* pIRowsetEmployees = NULL;

   hr = comLoader.Load();
   CHKHR_GOTO_MSG(hr, _Exit, L"Unable to Load COM.");

   hr = dso.Connect(L"localhost", L"testdb");
   CHKHR_GOTO(hr, _Exit);

   hr = dso.GetSession(&pIOpenRowset);
   CHKHR_GOTO(hr, _Exit);

   hr = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, reinterpret_cast<void**>(&pIDBCreateCommand));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI for IDBCreateCommand.");

   EmployeesRowset* pEmployeesRowset = new EmployeesRowset(20);
   if (pEmployeesRowset == NULL) {
      hr = E_OUTOFMEMORY;
      CHKHR_GOTO_MSG(hr, _Exit, L"Out of memory.");
   }

   // Do an extra AddRef. This IUnknown will be automatically released by the command execution code
   pEmployeesRowset->AddRef();

   hr = pEmployeesRowset->Initialize(pIOpenRowset);
   CHKHR_GOTO(hr, _Exit);

   hr = pEmployeesRowset->QueryInterface(IID_IRowset, reinterpret_cast<void**>(&pIRowsetEmployees));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IRowset for Employees Rowset.");

   hr = PopulateEmployees(pIDBCreateCommand, pIRowsetEmployees);
   CHKHR_GOTO(hr, _Exit);

_Exit:
   Release(&pIRowsetEmployees);
   Release(&pIDBCreateCommand);
   Release(&pIOpenRowset);
   return SUCCEEDED(hr) ? EXIT_SUCCESS : EXIT_FAILURE;
}

use master
IF EXISTS (SELECT name FROM master..sysdatabases WHERE name = 'testdb')
    DROP DATABASE [testdb]
go