Einfügen eines BLOB mit OLE DB
-
20-09-2019 - |
Frage
Ich arbeite an einer Anwendung, die ein SQL Server DB OLE DB und SQL Server Native Client für den Zugriff verwendet. Bisher habe ich nur mit relativ einfachen SQL beschäftigt. Dazu habe ich eine ICommandText
gewesen zu erhalten und mit SetCommandText
. Ich möchte jetzt ein großes Objekt in die Datenbank einzufügen. Ich sehe, dass ICommandStream
existiert, aber es scheint, wie dies mit mir würde erfordern eine Klasse hinzufügen, dass Geräte IStream
und auch meine BLOB entsprechend zu zitieren (entweichende Apostrophe, etc.). Sicherlich gibt es einen einfacheren Weg?
Side Hinweis: OLE DB war nicht meine Wahl, und ich kann es in diesem Stadium nicht ändern. So den einfacheren Weg „Verwendung etwas höhere Ebene“ ist nicht verfügbar.
Lösung 2
Es stellt sich heraus, gibt es eine Antwort auf dem Microsoft SQLNCLI Team-Blog .
auf diese zu erweitern, hier ist der Code, den ich am Ende mit. Zunächst müssen Sie eine ISequentialStream für SQL Server Native Client aus zu lesen. Ich habe meine Daten im Speicher, so dass ich nur diese mit einem Zeiger auf meine BLOB konstruieren könnte, aber es ist trivial zu gehen und die Daten aus anderen Ländern zu erhalten. Es ist nicht Bestandteil des Vertrages, aber es ist vielleicht nützlich zu wissen, dass die in 1024-Byte-Blöcken passieren liest scheinen. Hier ist meine Stream-Klasse:
struct ISequentialStream;
class XYZSQLStream : public ISequentialStream
{
public:
XYZSQLStream(LPBYTE data, __int64 ulLength);
virtual ~XYZSQLStream();
virtual BOOL Clear();
virtual ULONG Length() { return m_cBufSize; };
virtual operator void* const() { return m_pBuffer; };
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv);
STDMETHODIMP Read(void __RPC_FAR *pv, ULONG cb, ULONG __RPC_FAR *pcbRead);
STDMETHODIMP Write(const void __RPC_FAR *pv, ULONG cb, ULONG __RPC_FAR *pcbWritten);
private:
ULONG m_cRef; // reference count
void* m_pBuffer; // buffer
ULONG m_cBufSize; // buffer size
ULONG m_iPos; // current index position in the buffer
};
Die Umsetzung dieser ist trivial:
XYZSQLStream::XYZSQLStream(LPBYTE data, ULONG ulLength)
{
m_iPos = 0;
m_cRef = 0;
m_pBuffer = data;
m_cBufSize = ulLength;
AddRef();
}
XYZSQLStream::~XYZSQLStream()
{
// Shouldn't have any references left
if (m_cRef)
throw L"Destroying SQLStream with references";
delete[] m_pBuffer;
}
ULONG XYZSQLStream::AddRef()
{
return ++m_cRef;
}
ULONG XYZSQLStream::Release()
{
if (!m_cRef)
throw L"Releasing referenceless SQLStream";
if (--m_cRef)
return m_cRef;
delete this;
return 0;
}
HRESULT XYZSQLStream::QueryInterface(REFIID riid, void** ppv)
{
if (!ppv)
return E_INVALIDARG;
*ppv = NULL;
if (riid == IID_IUnknown)
*ppv = this;
if (riid == IID_ISequentialStream)
*ppv = this;
if(*ppv)
{
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
BOOL XYZSQLStream::Clear()
{
m_iPos = 0;
m_cBufSize = 0;
m_pBuffer = NULL;
return TRUE;
}
HRESULT XYZSQLStream::Read(void *pv, ULONG cb, ULONG* pcbRead)
{
if (pcbRead)
*pcbRead = 0;
if (!pv)
return STG_E_INVALIDPOINTER;
if (cb == 0)
return S_OK;
ULONG cBytesLeft = m_cBufSize - m_iPos;
ULONG cBytesRead = cb > cBytesLeft ? cBytesLeft : cb;
//DEBUG(L"cb %d, left %d, read %d\n", cb, cBytesLeft, cBytesRead);
if (cBytesLeft == 0)
return S_FALSE;
// Copy to users buffer the number of bytes requested or remaining
memcpy(pv, (void*)((BYTE*)m_pBuffer + m_iPos), cBytesRead);
m_iPos += cBytesRead;
if (pcbRead)
*pcbRead = cBytesRead;
if (cb != cBytesRead)
return S_FALSE;
return S_OK;
}
HRESULT XYZSQLStream::Write(const void *pv, ULONG cb, ULONG* pcbWritten)
{
// Parameter checking
if (!pv)
return STG_E_INVALIDPOINTER;
if (pcbWritten)
*pcbWritten = 0;
if (cb == 0)
return S_OK;
// Enlarge the current buffer
m_cBufSize += cb;
// Need to append to the end of the stream
m_pBuffer = CoTaskMemRealloc(m_pBuffer, m_cBufSize);
memcpy((void*)((BYTE*)m_pBuffer + m_iPos), pv, cb);
// m_iPos += cb;
if (pcbWritten)
*pcbWritten = cb;
return S_OK;
}
Mit einem ICommandText
, können Sie dann eine SELECT
auf dem Tisch auszuführen. Sie sind nicht eigentlich diese abzurufen gehen alle Daten verwenden, es ist nur ein Weg, um eine IRowsetChange
zu bekommen. Ich habe eine zusätzliche ExecuteCommand Methode dafür. Die SQL in PSQL geben wird (ähnlich) SELECT x,y,z FROM TableWithBlob
. FAIL
ist ein benutzerdefiniertes Makro, dass Datensätze das Problem und kehrt zurück.
HRESULT XYZSQLCommand::ExecuteCommand(TCHAR* pSQL, IRowset** ppRowSet, IRowsetChange** ppRowSetChange)
{
HRESULT hr;
IRowsetChange* pIRowsetChange;
IRowset* pIRowset;
hr = m_pICommandText->SetCommandText(DBGUID_DBSQL, pSQL);
if (FAILED(hr))
FAIL(hr);
hr = m_pICommandText->Execute(NULL, IID_IRowsetChange, NULL, NULL, (IUnknown**)&pIRowsetChange);
if (FAILED(hr))
FAIL(hr);
hr = pIRowsetChange->QueryInterface(IID_IRowset, (void**)&pIRowset);
if (FAILED(hr))
{
pIRowsetChange->Release();
FAIL(hr);
}
*ppRowSet = pIRowset;
*ppRowSetChange = pIRowsetChange;
return S_OK;
}
Ich habe jetzt ein IRowset und ein IRowsetChange für die Tabelle in Frage. Sie bauen dann eine DBBINDING
wie gewohnt. Ich eliding dies - es ist nicht wirklich relevant für die Frage. Das entsprechende Bit ist:
static DBOBJECT streamObj = {STGM_READ, IID_ISequentialStream};
pDBBindings[nCol].pObject = &streamObj;
pDBBindings[nCol].wType = DBTYPE_IUNKNOWN;
pDBBindings[nCol].cbMaxLen = sizeof(ISequentialStream*);
Wenn anschließend in dem entsprechenden Datenspeicherblock füllt, können Sie dies tun (sorry für die hässlichen Abgüsse):
XYZSQLStream *stream = new XYZSQLStream(data_to_write, length_of_data);
*((ISequentialStream**)(pbData+pDBBindings[x].obValue)) = stream;
*((DBLENGTH*)(pbData+pDBBindings[x].obLength)) = (DBLENGTH)length_of_data;
*((DBSTATUS*)(pbData+pDBBindings[x].obStatus)) = DBSTATUS_S_OK;
Holen Sie sich einen IAccessor
mit Ihrem IRowsetChange
und binden es:
IAccessor* pIAccessor;
HACCESSOR hAccessor;
DBBINDSTATUS* pDBBindStatus;
hr = pRowsetChange->QueryInterface(IID_IAccessor, (void**) &pIAccessor);
// Error handling elided
pDBBindStatus = new DBBINDSTATUS[ulCols];
//Associate the bindings with the data accessor for the rowset
hr = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, ulCols, pDBBindings, 0, hAccessor, pDBBindStatus);
// Error handling, cleanup elided
Schließlich können Sie Ihre Zeile einfügen:
hr = pRowsetChange->InsertRow(NULL, hAccessor, pbData, NULL);
SQL Server Native Client wird von Ihrem Stream lesen und die Zeile einzufügen. Der Reifen-Springen ist nun abgeschlossen. ReleaseAccessor
, Reinigung usw. elided.
Andere Tipps
Ein Blob ist nur binäre Daten, so dass Sie irgendeine Form von Byte-Array verwenden, benötigen.