Overview
Raw OLEDB is discussed scarcely and working with COM is a nightmare for many and at times for me too. Anyways, if you can�t do it in raw it simply can�t be done with wrappers templates or whatever. I spend around a weekend working on the provided raw OLEDB code to create a set of classes that could show you how to work with raw OLEDB.
The following code has been used since 2001 and many production projects successfully, therefore many bugs or issues have been resolved. Don�t expect to have it perfectly, still I have tried to be clear on the use of OLEDB with lots of code comments where it merits. Your constructive comments and notes about the use, issues or suggestions will be greatly appreciated.
The following are some comments that I wrote by 2001:
I decided to implement a ClOleDb<x>
class family using raw OLEDB to be used when performance is an issue. The classes here implement a similar set of classes as ADO COM, still since we are using OLEDB directly we should have better performance.
A secondary benefit is that we are spending time to implement database access in raw OLEDB that will help us to understand the details and capabilities of this technology and provide us with additional functionality and control beyond the use of ADO.
About the experience, I should tell that since I had implemented a similar class family using ODBC (v2.0, v3.5) and had been working with raw ODBC for the past 2 years, I will say that implementing access with OLEDB has been relatively painless.
To help me on working with OLEDB, I have used the following references:
- Microsoft OLE DB 2.0 Programmer's Reference and Data Access SDK (1998), Microsoft Press.
- MSDN Library October 2000 and January 2000. OLE DB and ODBC Developer's Guide, Wood Chuck (1999), M&T Books.
Understanding ClOleDb Implementation
I don�t plan to provide detailed class library documentation; still here are some notes on the implemented classes (see ClOleDb.h header):
Type |
Description |
DBSettings |
Use the following to supply the info needed to access the database including the host/server, catalog, user and related password. |
ClDBValidate |
A helper to validate dates. |
ClDBField |
Support for managing output fields and their required info needed for later binding. |
ClDBFields |
Holds a collection of ClDBField s. |
ClDBParams |
Support for managing command input / output parameters and the required info needed for later binding. |
ClDBConnection |
Support for handling a connection, errors and other session related management. |
ClDBCommand |
Support for submitting and managing a DB request. |
ClDBColumnInfo |
Provided to obtain detailed information about columns. |
ClDBFastLoad |
Support for FastLoad (Bulk requests). |
ClDBResult |
Support for managing requests result sets. |
The following is the list of files included:
File |
Description |
ClOleDb.h |
Declarations and class definitions. |
ClOleDb.cpp |
Class method definitions not provided in include file. |
FString.h |
String / Buffer handling definitions for parsing and conversion. |
FString.cpp |
Class method definitions for classes in FString.h. |
Get.h |
Commonly used helper functions for managing strings / chars, conversions and so on. |
Get.cpp |
Class method definitions for classes in Get.h. |
SysLib.h |
Commonly used macros and definitions. |
Select Command Sample
I compiled and updated the code to work in VS2005 and also wrote the following sample for you to try it. Just change the command text and setup connection parameters to something meaningful for you.
void TestClOleDb()
{
CoInitialize(NULL) ;
ClDBConnection dbConn ;
if (!dbConn.Connect(
TEXT("openk3"),TEXT("BMv4Model"),
TEXT("sa"),TEXT("password")))
goto DieNow ;
TCHAR *pQry = TEXT("select userid, lastname"
" from sysuser where deptid=?") ;
ClDBFields *pFields = dbConn.AllocFields(2) ;
ClDBParams *pParams = dbConn.AllocParams(1) ;
if ((!pFields) && (!pParams))
goto DieNow ;
TCHAR UserId[UserIdLen+1] ;
TCHAR LastName[LastNameLen+1] ;
TCHAR Dept[DeptLen+1] ;
pFields->Set(0,UserId,UserIdLen) ;
pFields->Set(1,LastName,LastNameLen) ;
pParams->Set(0,Dept,DeptLen) ;
_tcscpy_s(Dept,TEXT("006")) ;
ClDBResult *pRes = dbConn.Prepare(pQry,pFields,pParams) ;
if (pRes) {
if (pRes->Execute()) {
if (pRes->GetResult()) {
pRes->Open() ;
if (pRes->Next(pFields)) {
while (!pRes->Eof()) {
pRes->CopyString(UserId,UserIdLen,0) ;
pRes->CopyString(LastName,LastNameLen,1) ;
_tprintf(_T("%s (%s)\n"),UserId,LastName) ;
pRes->Next() ;
}
}
}
}
pRes->Close() ;
}
delete pParams ;
delete pFields ;
delete pRes ;
dbConn.Disconnect() ;
DieNow:
CoUninitialize() ;
}
Stored Procedure Command Execution Sample
Given the following stored procedure:
CREATE PROCEDURE TestGetUserNo1
@UserId CHAR(4),
@OutFirstName VARCHAR(20) OUTPUT
AS
BEGIN
SET NOCOUNT ON
IF EXISTS(SELECT * FROM SysUser WHERE UserId = @UserId)
BEGIN
SELECT @OutFirstName = FirstName
FROM SysUser
WHERE UserId = @UserId
RETURN 1
END
ELSE
RETURN 0
END
GO
Use the following code to execute the procedure and fetch the return value and output parameter value:
void TestSPSupport()
{
CoInitialize(NULL) ;
ClDBConnection dbConn ;
if (!dbConn.Connect(TEXT("host"),TEXT("catalog"),
TEXT("userid"),TEXT("password")))
goto DieNow ;
TDBInt ReturnValue ;
TDBChar UserId[UserIdLen+1] ;
TDBChar FirstName[FirstNameLen+1] ;
ClDBParams *pParams = dbConn.AllocParams(3) ;
if (pParams) {
pParams->Set(0,&ReturnValue) ;
pParams->Set(1,UserId,UserIdLen) ;
pParams->Set(2,FirstName,FirstNameLen) ;
pParams->IsForOutput(0) ;
pParams->IsForOutput(2) ;
_tcscpy_s(UserId,UserIdLen+1,_T("MAN")) ;
}
else
return ;
ClDBResult *pRecordSet = dbConn.Prepare(
TEXT("{ ? = CALL TestGetUserNo1(?,?) }"),
NULL,pParams) ;
if (pRecordSet) {
bool done ;
done = pRecordSet->ExecuteNoneQuery() ;
if (done) {
int returnVal ;
pParams->GetInt(0,&returnVal) ;
pParams->GetString(2,FirstName,FirstNameLen) ;
_tprintf(_T("%d (%s)\n"),returnVal,FirstName) ;
}
}
pParams->Free() ;
delete pParams ;
if (pRecordSet)
delete pRecordSet ;
dbConn.Disconnect() ;
DieNow:
CoUninitialize() ;
}
Multiple Results Sample
Given the following stored procedure:
CREATE PROCEDURE TestMultiResults1
@UserId CHAR(4)
AS
BEGIN
SET NOCOUNT ON
SELECT FirstName,LastName FROM SysUser WHERE UserId = @UserId
SELECT FirstName,LastName FROM SysUser WHERE UserId <> @UserId
END
GO
Use the following code to execute the procedure and fetch multiple results:
void TestSPMultipleResults()
{
CoInitialize(NULL) ;
ClDBConnection dbConn ;
if (!dbConn.Connect(
TEXT("openk3"),TEXT("BMv4Model"),TEXT("sa"),TEXT("password")))
goto DieNow ;
TDBInt ReturnValue ;
TDBChar UserId[UserIdLen+1] ;
TDBChar FirstName[FirstNameLen+1] ;
TDBChar LastName[LastNameLen+1] ;
ClDBFields *pFields = dbConn.AllocFields(2) ;
ClDBParams *pParams = dbConn.AllocParams(2) ;
if (pFields && pParams) {
pFields->Set(0,FirstName,FirstNameLen) ;
pFields->Set(1,LastName,LastNameLen) ;
pParams->Set(0,&ReturnValue) ;
pParams->Set(1,UserId,UserIdLen) ;
pParams->IsForOutput(0) ;
_tcscpy_s(UserId,UserIdLen+1,_T("MAN")) ;
}
else
return ;
ClDBResult *pRecordSet = dbConn.Prepare(
TEXT("{ ? = CALL TestMultiResults1(?) }"),
pFields,pParams) ;
if (pRecordSet) {
if (pRecordSet->Execute()) {
if (pRecordSet->GetResult()) {
pRecordSet->Open() ;
if (pRecordSet->Next(pFields)) {
while (!pRecordSet->Eof()) {
pRecordSet->CopyString(FirstName,FirstNameLen,0) ;
pRecordSet->CopyString(LastName,LastNameLen,1) ;
_tprintf(_T("%s %s\n"),FirstName,LastName) ;
pRecordSet->Next() ;
}
}
}
pRecordSet->Close() ;
if (pRecordSet->GetResult()) {
pRecordSet->Open() ;
if (pRecordSet->Next(pFields)) {
while (!pRecordSet->Eof()) {
pRecordSet->CopyString(FirstName,FirstNameLen,0) ;
pRecordSet->CopyString(LastName,LastNameLen,1) ;
_tprintf(_T("%s %s\n"),FirstName,LastName) ;
pRecordSet->Next() ;
}
}
pRecordSet->Close() ;
}
}
}
delete pRecordSet ;
delete pParams ;
delete pFields ;
dbConn.Disconnect() ;
DieNow:
CoUninitialize() ;
}
Known Issues and Notes
What has been a bit difficult is handling MS-SQL TEXT
fields since they are treated as a BLOB
and I have not figured out why for some reason if they are the only piece of information been requested or is the last field in a query the data is fetched successfully. If they are not the only field requested and is not the last one in the query, the Provider returns STATUS
=8 (meaning DBSTATUS_E_UNAVAILABLE
). TEXT
fields can be handled as BINARY
or by using the ISequentialStream
, still either will behave the same (as describe above). If your reader can provide some insides into this, please tell me.
The provided code was compiled in VS2005 B2 and I tried it with the UNICODE conditional compilation and found that it has some issues. Working with ANSI char (without the UNICODE conditional), it is working OK. I will be working on this issue soon and will update the article at a later time.
Also note that I am declaring my own enumerators and macros to support type mappings for later field and parameter bindings. This is good in the sense that I may take my ODBC classes and provide a ClOleDb<x>
family that uses the exact properties and methods but that accesses ODBC instead of OLEDB.
Using ClOleDb Classes
Use the code as needed; if any code issue / bug are found, please send me an email and tell me about it. If you do any enhancements or modifications on the provided class please send me those too.
If you have any other questions, assistance or want to share your thoughts about the code, send me an email at esob@openk.com.