Insertion d'un blob avec OLE DB
-
20-09-2019 - |
Question
Je travaille sur une application qui utilise OLE DB et SQL Server Native Client pour accéder à une base de données SQL Server. À ce jour, je ne l'ai eu affaire avec SQL assez simple. Pour cela, je suis d'obtenir un ICommandText
et en utilisant SetCommandText
. Je veux maintenant insérer un grand objet dans la base de données. Je vois que ICommandStream
existe, mais il semble que cela en utilisant me obliger à ajouter une classe qui implémente IStream
et aussi de citer mon blob de façon appropriée (échapper apostrophes, etc.). Certes, il y a un moyen plus facile?
Note de côté: OLE DB n'a pas été mon choix et je ne peux pas changer à ce stade. Ainsi, la manière plus facile « utiliser niveau supérieur quelque chose » est disponible.
La solution 2
Il se trouve, il y a un réponse sur le blog de l'équipe Microsoft SQLnCli.
Pour développer ce sujet, voici le code que je fini par utiliser. Tout d'abord, vous avez besoin d'un ISequentialStream pour SQL Server Native Client à lire à partir. J'ai mes données en mémoire, donc je pouvais construire cela avec un pointeur vers mon blob, mais il est trivial d'aller chercher les données ailleurs. Il ne fait pas partie du contrat, mais il est peut-être utile de savoir que les lectures semblent se produire en morceaux de 1024 octets. Voici ma classe de flux:
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
};
La mise en œuvre de c'est 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;
}
L'utilisation d'un ICommandText
, vous pouvez alors exécuter une SELECT
sur la table. Vous allez pas vraiment pour récupérer des données en utilisant cela, il est juste un moyen d'obtenir un IRowsetChange
. J'ai une méthode ExecuteCommand supplémentaire pour cela. Le SQL est passé dans pSQL SELECT x,y,z FROM TableWithBlob
(similaire à). FAIL
est une macro personnalisée qui enregistre le problème et retourne.
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;
}
J'ai maintenant un IRowset et un IRowsetChange pour la table en question. Vous construisez ensuite un DBBINDING
comme vous le feriez normalement. J'éludant ce - ce n'est pas vraiment pertinent à la question. Le bit correspondant est:
static DBOBJECT streamObj = {STGM_READ, IID_ISequentialStream};
pDBBindings[nCol].pObject = &streamObj;
pDBBindings[nCol].wType = DBTYPE_IUNKNOWN;
pDBBindings[nCol].cbMaxLen = sizeof(ISequentialStream*);
Quand remplir ensuite dans le bloc de mémoire de données correspondant, vous pouvez le faire (désolé pour les moulages laids):
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;
Procurez-vous un IAccessor
en utilisant votre IRowsetChange
et le lier:
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
Enfin, vous pouvez insérer votre ligne:
hr = pRowsetChange->InsertRow(NULL, hAccessor, pbData, NULL);
SQL Server Native Client lecture de votre flux et insérez la ligne. Le cerceau saut est maintenant terminée. ReleaseAccessor
, nettoyage, etc. éludée.
Autres conseils
Un blob est juste des données binaires, de sorte que vous aurez besoin d'utiliser une certaine forme de tableau d'octets.