<xmp><body><!--'"</title></head>--> <script type="text/javascript"> //OwnerIQ var __oiq_pct = 50; if( __oiq_pct>=100 || Math.floor(Math.random()*100/(100-__oiq_pct)) > 0 ) { var _oiqq = _oiqq || []; _oiqq.push(['oiq_addPageBrand','Lycos']); _oiqq.push(['oiq_addPageCat','Internet > Websites']); _oiqq.push(['oiq_addPageLifecycle','Intend']); _oiqq.push(['oiq_doTag']); (function() { var oiq = document.createElement('script'); oiq.type = 'text/javascript'; oiq.async = true; oiq.src = document.location.protocol + '//px.owneriq.net/stas/s/lycosn.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(oiq, s); })(); } /////// Google Analytics var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-21402695-21']); _gaq.push(['_setDomainName', 'angelfire.com']); _gaq.push(['_setCustomVar', 1, 'member_name', 'blues2/javeline', 3]); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); ////// Lycos Initialization ///////////////////// var lycos_ad = Array(); var lycos_search_query = ""; var lycos_onload_timer; var cm_role = "live"; var cm_host = "angelfire.lycos.com"; var cm_taxid = "/memberembedded"; var angelfire_member_name = "blues2/javeline"; var angelfire_member_page = "blues2/javeline/programming/ado/ado.html"; var angelfire_ratings_hash = "1766504941:15326c45ce5f61a1ee3f5dbbe48c95da"; var lycos_ad_category = {"dmoz":"arts\/animation","ontarget":"&CAT=family%20and%20lifestyles&L2CAT=hobbies","find_what":"about popup window"}; var lycos_ad_remote_addr = "209.202.244.9"; var lycos_ad_www_server = "www.angelfire.lycos.com"; var edit_site_url = "www.angelfire.lycos.com/landing/landing.tmpl?utm_source=house&utm_medium=landingpage&utm_campaign=toolbarlink"; </script> <script type="text/javascript" src="https://scripts.lycos.com/catman/init.js"></script> <script type='text/javascript'> var googletag = googletag || {}; googletag.cmd = googletag.cmd || []; (function() { var gads = document.createElement('script'); gads.async = true; gads.type = 'text/javascript'; var useSSL = 'https:' == document.location.protocol; gads.src = (useSSL ? 'https:' : 'http:') + '//www.googletagservices.com/tag/js/gpt.js'; var node = document.getElementsByTagName('script')[0]; node.parentNode.insertBefore(gads, node); })(); </script> <script type='text/javascript'> googletag.cmd.push(function() { googletag.defineSlot('/95963596/ANG_300x250_dfp', [300, 250], 'div-gpt-ad-1450207484070-0').addService(googletag.pubads()); googletag.enableServices(); }); </script> <script type='text/javascript'> googletag.cmd.push(function() { googletag.defineSlot('/95963596/ANG_above_728x90_dfp', [728, 90], 'div-gpt-ad-1450207484070-1').addService(googletag.pubads()); googletag.enableServices(); }); </script> <script type='text/javascript'> googletag.cmd.push(function() { googletag.defineSlot('/95963596/ANG_below_728X90_dfp', [728, 90], 'div-gpt-ad-1450207484070-2').addService(googletag.pubads()); googletag.enableServices(); }); </script> <script type="text/javascript"> (function(isV) { if (!isV) { return; } //this.lycos_search_query = lycos_get_search_referrer(); var adMgr = new AdManager(); var lycos_prod_set = adMgr.chooseProductSet(); var slots = ["leaderboard", "leaderboard2", "toolbar_image", "toolbar_text", "smallbox", "top_promo", "footer2","slider"]; var adCat = this.lycos_ad_category; adMgr.setForcedParam('page', (adCat && adCat.dmoz) ? adCat.dmoz : 'member'); if (this.lycos_search_query) { adMgr.setForcedParam("keyword", this.lycos_search_query); } else if (adCat && adCat.find_what) { adMgr.setForcedParam('keyword', adCat.find_what); } for (var s in slots) { var slot = slots[s]; if (adMgr.isSlotAvailable(slot)) { this.lycos_ad[slot] = adMgr.getSlot(slot); } } adMgr.renderHeader(); adMgr.renderFooter(); }((function() { var w = 0, h = 0, minimumThreshold = 300; if (top == self) { return true; } if (typeof(window.innerWidth) == 'number' ) { w = window.innerWidth; h = window.innerHeight; } else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) { w = document.documentElement.clientWidth; h = document.documentElement.clientHeight; } else if (document.body && (document.body.clientWidth || document.body.clientHeight)) { w = document.body.clientWidth; h = document.body.clientHeight; } return ((w > minimumThreshold) && (h > minimumThreshold)); }()))); window.onload = function() { var f = document.getElementById("lycosFooterAd"); var b = document.getElementsByTagName("body")[0]; b.appendChild(f); f.style.display = "block"; document.getElementById('lycosFooterAdiFrame').src = '/adm/ad/footerAd.iframe.html'; // Slider Injection (function() { var e = document.createElement('iframe'); e.style.border = '0'; e.style.margin = 0; e.style.display = 'block'; e.style.cssFloat = 'right'; e.style.height = '254px'; e.style.overflow = 'hidden'; e.style.padding = 0; e.style.width = '300px'; })(); // Bottom Ad Injection ( function() { var b = document.getElementsByTagName("body")[0]; var iif = document.createElement('iframe'); iif.style.border = '0'; iif.style.margin = 0; iif.style.display = 'block'; iif.style.cssFloat = 'right'; iif.style.height = '254px'; iif.style.overflow = 'hidden'; iif.style.padding = 0; iif.style.width = '300px'; iif.src = '/adm/ad/injectAd.iframe.html'; var cdiv = document.createElement('div'); cdiv.style = "width:300px;margin:10px auto;"; cdiv.appendChild( iif ); if( b ) { b.insertBefore(cdiv, b.lastChild); } })(); } </script> <style> #body .adCenterClass { margin:0 auto; display:block !important; overflow:hidden; width:100%; } #body .adCenterClass #ad_container { display:block !important; float:left; width:728px; } @media (min-width: 768px) { <!-- For 300px or less ads ONLY --> #body .adCenterClass #ad_container { width: calc(100% - 372px); } } @media (min-width: 1110px) { <!-- For 728px or less ads --> #body .adCenterClass #ad_container { width: calc(100% - 372px); } } </style> <div style="background:#abe6f6; border-bottom:1px solid #507a87; position:relative; z-index:9999999"> <div class="adCenterClass"> <a href="https://www.angelfire.lycos.com/" title="Angelfire.com: build your free website today!" style="display:block; float:left; width:186px; border:0"> <img src="/adm/ad/angelfire-freeAd.jpg" alt="Site hosted by Angelfire.com: Build your free website today!" style="display:block; border:0" /> </a> <div id="ad_container"> <script type="text/javascript">document.write(lycos_ad['leaderboard']);</script> </div> </div> </div> <!-- ///////////////////////////////////// --> <script type="text/javascript">document.write(lycos_ad['slider']);</script> <div id="lycosFooterAd" style="background:#abe6f6; border-top:1px solid #507a87; clear:both; display:none; position:relative; z-index:9999999"> <div class="adCenterClass" style="display:block!important; overflow:hidden; width:936px;"> <div id="aflinksholder" style="float:left; width:186px;"> <a href="https://www.angelfire.lycos.com/" title="Angelfire.com: build your free website today!" style="display:block; border:0"> <img src="/adm/ad/angelfire-freeAd2.jpg" alt="Site hosted by Angelfire.com: Build your free website today!" style="display:block; border:0" /> </a> </div> <iframe id="lycosFooterAdiFrame" style="border:0; display:block; float:left; height:96px; overflow:hidden; padding:0; width:750px"></iframe> </div> </div> <!--- UNDERDOGMEDIA EDGE_lycos.com JavaScript ADCODE START---> <script data-cfasync="false" language="javascript" async src="//udmserve.net/udm/img.fetch?sid=17754;tid=1;dt=6;"></script> <!--- UNDERDOGMEDIA EDGE_lycos.com JavaScript ADCODE END---> </xmp>

ActiveX Data Objects Database Programming in C++

Introduction

Although I intended this tutorial to be as straightforward as possible, I still can't dispense with the following information that I think is necessary to understand the motive behind making this tutorial.  Programming database applications in Visual C++ can be a pain because the tutorials that abound, especially those provided by Microsoft usually deal with the MFC database classes, CRecordset and CDaoRecordset.  While these classes are fine for most applications, we can't help but notice the trend towards OLE DB programming.  So what exactly is OLE DB?

OLE DB and ADO

According to Microsoft, OLE DB is the data access 'in' thing of the future.  This is because OLE DB is intended to provide a simple way of getting data in and out of databases, wherever they may be located.  Through OLE DB it is possible to access data contained in files, RDBMS, email, and anything that could possibly have data.  But programming OLE DB stuff is not for the faint-hearted since having the ability to access data from virtually any source adds to the complexity of  the technology.  What we need is something that's made specifically for database access.  Enter ActiveX Data Objects or ADO.  It would be right to say that ADO is a layer spread on top of OLE DB so that the complexity of OLE DB is hidden from the casual programmer.  Among its many intended functions, ADO was designed to create controls that could provide data access and control in web pages. So to be easily usable in web scripting languages like VBScript and programming environments such as Visual Basic, ADO has a minimal number of objects, namely:

Using ADO

There are two ways to use ADO in Visual C++.  The first is by using ADO controls like the ADO Data Control and the ADO Data-bound Grid.  I won't be discussing this method here since using controls does not provide us with much flexibility.  The second would be to use ADO programmatically.  That is, using the ADO classes directly.

Connecting to a Database and Data Retrieval

Importing the ADO DLL

To be able to use ADO we must first import the ADO DLL since ADO is not part of the MFC class hierarchy.  We can use the #import directive to import ActiveX DLLs, in this case, the msado15.dll.  The directive tells the compiler to extract the object information from this DLL and place the information in a couple of header files (.tlh and .tli) that are automatically included in our project. The following lines, usually placed in "stdafx.h" should do the trick:

#define INITGUID        // This constant is needed by ADO.
#import "C:\Program Files\Common Files\System\ADO\msado15.dll"\
rename_namespace("ADOCG") rename("BOF", "BOFILE") rename("EOF", "EOFILE")    // Rename some elements.
using namespace ADOCG;
#include "icrsint.h"    // Contains definition of some macros used to write ADO application.

In line no. 3, we renamed some elements in order to avoid potential conflict with other elements.  For example, we renamed EOF to avoid conflict with the EOFdefined in <stdio.h>.  Next we will try to establish a connection with a database.

Connecting to a Database

Before we can use ADO, we must first initialize the COM environment for our application with the following:

::CoInitialize(NULL);

This enables as to interact with ActiveX objects and so we are home-free to make calls to ADO.  After we are done with ADO we need to shut down COM by calling CoUnintialize() as follows:

::CoUninitialize();

After we initialize COM, we need to establish a connection to the database by using a Connection object pointer, the _ConnectionPtr.  This is done by declaring a _ConnectionPtr variable then initialize it with an instance of a Connection object.  Passing the UUID of the Connection object to the CreateInstance function of the Connection pointer does the trick:

_ConnectionPtr pCon;                        // _ConnectionPtr has only one underscore
pCon.CreateInstance(__uuidof(Connection));  // __uuidof has two underscores

After creating the Connection instance, we will open the actual connection to the database by using the member function Open():

pCon->Open(L"DSN=pcs;DESC=MySQL ODBC 3.51 Driver DSN;DATABASE=pcs;SERVER=localhost;PORT=3306;OPTION=35;STMT=",
           L"pcs", L"cygnus", adOpenUnspecified);

The Open() function takes four parameters.  The first parameter is the connection string which defines the OLE DB data source for the database.  For an idea of how a connection string like this is formed (MySQL ODBC DSN's only), see the following supplement.  The second parameter is the user ID.  The third is the password, and the fourth is the type of cursor to be used.  These cursor types are defined in the msado15.tlh that are created with the import directive:

enum CursorTypeEnum
{
    adOpenUnspecified = -1,
    adOpenForwardOnly = 0,
    adOpenKeyset = 1,
    adOpenDynamic = 2,
    adOpenStatic = 3
};

Executing Commands and Retrieving Records

After establishing a connection to the database, we can pass SQL commands to the database for execution by using a Command object.  Creating a Command object instance entails almost the same process as creating a Connection:

_CommandPtr pCmd;                        // Declare a CommandPtr variable
pCmd.CreateInstance(__uuidof(Command));  // Create an instance of the Command object

After creating the Command object instance, we must specify which database connection we're going to use:

pCmd->ActiveConnection = pCon;

Next, we'll specify the (SQL) command we will be executing:

pCmd->CommandText = "select * from item";

Since the SQL command above returns records, we have two options on how to proceed.  We can directly use the Execute() member of the Command object.  Execute() has the following syntax:

_RecordsetPtr Execute(VARIANT *RecordsAffected, VARIANT *Parameters, long Options);

The Options parameter of Execute() may be any of the following:

enum CommandTypeEnum
{
    adCmdUnspecified = -1,
    adCmdUnknown = 8,
    adCmdText = 1,
    adCmdTable = 2,
    adCmdStoredProc = 4,
    adCmdFile = 256,
    adCmdTableDirect = 512
};

The RecordsetPtr returned by Execute is valid only for commands that return record sets like the SQL SELECT statement.

_RecordsetPtr pRset;
pRset = pCmd->Execute(NULL, NULL, adCmdText);

A second option would be to specify the Command object as the record source for a Recordset:

_RecordsetPtr pRset;
pRset.CreateInstance(__uuidof(Recordset));
pRset->PutRefSource(pCmd);

Afterwards we will need to call on the Open() member of the Recordset object, which has the following syntax:

HRESULT Open(const _variant_t &Source,            // the record source (an SQL command)
             const _variant_t &ActiveConnection,  // the connection
             enum CursorTypeEnum CursorType,      // cursor type
             enum LockTypeEnum LockType,          // locking type 
             long Options);                       // kind of command

Since we already have a record source and a connection, we need to pass on a Null for the first two parameters.  The second parameter is the cursor type exactly like the values passed to the Open() member of the Connection object.  The third parameter specifies the locking mechanism to be used on the resulting record set, while the fourth would be exactly like the values passed in the Execute() member of the Command object.

enum LockTypeEnum
{
    adLockUnspecified = -1,
    adLockReadOnly = 1,
    adLockPessimistic = 2,
    adLockOptimistic = 3,
    adLockBatchOptimistic = 4
};

The code for executing the command and retrieving the record would then be:

_variant_t vtNull;
vtNull.vt = VT_ERROR;
vtNull.scode = DISP_E_PARAMNOTFOUND;
pRS->Open(vtNull, vtNull, adOpenDynamic, adLockUnspecified, adCmdText);

There is a more direct way of retrieving records that allows us to dispense with the Connection and Command objects altogether:

_RecordsetPtr pRset;
pRset.CreateInstance(__uuidof(Recordset));
pRS->Open(L"select * from item",
          L"DSN=pcs;DESC=MySQL ODBC 3.51 Driver DSN;DATABASE=pcs;SERVER=localhost;UID=pcs;PASSWORD=cygnus;PORT=3306;OPTION=35;STMT=",
          adOpenDynamic, adLockUnspecified, adCmdText);

Take note though  that it is still best to open a single connection to the database and use it for access instead of repeatedly specifying a connection string for opening record sets.

Navigating the Recordset

For navigating through the record set you can use the following member functions of the Recordset object:

HRESULT MoveFirst();
HRESULT MovePrevious();
HRESULT MoveNext();
HRESULT MoveLast();

Their names speak for themselves and I won't explain their purposes anymore.  In addition to these functions, the BOFILE and EOFILE variables (remember that we've renamed these variables when we imported the msado15.dll), provide information where we are in the record set.  BOFILE is true when we are past the first record while EOFILE is true when we are past the last record.  A typical application would be:

while (!pRset->EOFILE)
{
    // Display the contents of the record set.
    // . . .

    pRset->MoveNext();
}

Accessing and Updating Field Data in the Recordset

Accessing Field Values

Because ADO is intended to be used in other languages other than C++, there is no straightforward way to access data from a record set, since the values are of a variant type.  And because these values are variant, we need to convert them to their proper type to be able to make sense out of them.  For example:

_variant_t vtItem;
CString strItem;
vtItem = pRs->GetCollect(_variant_t("item"));
vtItem.ChangeType(VT_BSTR);
strItem = vtItem.bstrVal;

To make life easier I created a function that should bring some uniformity to the way we access data:

_variant_t FieldValue(const _RecordsetPtr pRS, const char *field)
{
    return pRS->GetCollect(_variant_t(field));
}

Applying this to the example above:

CString strItem = (const char *)_bstr_t(FieldValue(pRset, "item"));

double basePrice = double(FieldValue(pRS, "baseprice"));    // <-- Another example
Testing For NULL Fields

When we create table schemas sometimes it is logical to allow some fields to have null values.  When a field is null, trying to access its value ADO-style results to an error.  To avoid this error, we must find a way to test if a field has a null value.  The following function does just this.

BOOL IsNull(_variant_t v)
{
    return (v.vt == VT_NULL);
}

Updating Field Values

Updating field values entails the same tedious process.  For example:

_variant_t vtItem, vtValue;
vtItem.SetString("item");
vtValue.SetString("Power drill");
pRset->Update(vtItem, vtValue);

Again, to make life easier, I created a function or, rather, a series of overloaded functions in the spirit of the one I wrote to access data, to bring uniformity in updating data.  The third parameter should provide you a clue on what each function does:

void FieldValue(_RecordsetPtr pRS, const char *field, const short value)
{
    pRS->Update(_variant_t(field), _variant_t(value, VT_I2));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const long value)
{
    pRS->Update(_variant_t(field), _variant_t(value, VT_I4));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const float value)
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const double value)
{
    pRS->Update(_variant_t(field), _variant_t(value, VT_R8));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const char *value)   // strings of type char*
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const wchar_t *value)    // wide strings of type wchar_t*
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

void FieldValue(_RecordsetPtr pRS, const char *field, _bstr_t &value)    // strings of type _bstr_t
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const unsigned char value)
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const bool value)
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const CY &value)
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

void FieldValue(_RecordsetPtr pRS, const char *field, const DECIMAL &value)
{
    pRS->Update(_variant_t(field), _variant_t(value));
}

Applying to our example above:

FieldValue(pRset, "item", "Power drill");
FieldValue(pRset, "baseprice", 235.00);    // <-- Another example

Adding and Deleting Records

To add a new record to the record set call the Recordset object's AddNew() function.  Doing so adds a new, albeit, blank record to the record set while making that record the current one.  After the record is added you can begin to update each field value using the PutCollect().  For example:

pRset->AddNew();
pRset->PutCollect(_variant_t("item"), _variant_t("Lasgun"));
pRset->PutCollect(_variant_t("specification"), _variant_t("High-powered, medium range"));
pRset->PutCollect(_variant_t("baseprice"), _variant_t(3456.50));
pRset->PutCollect(_variant_t("actualprice"), _variant_t(3400.00));
pRset->PutCollect(_variant_t("supplier"), _variant_t(1));

To delete a record, call the Delete() member function of the Recordset object, which has the syntax:

HRESULT Delete(enum AffectEnum AffectRecords);

Where AffectEnum has the following values:

enum AffectEnum
{
    adAffectCurrent = 1,
    adAffectGroup = 2,
    adAffectAll = 3,
    adAffectAllChapters = 4
};

For example, to delete the current record:

pRset->Delete(adAffectCurrent);
pRset->MovePrevious();
if (pRset->BOFILE)
    pRset->MoveNext();

Finding a Specific Record

To find the specific record in the record set, use the member function Find(), which has the syntax:

HRESULT Find(_bstr_t Criteria, long SkipRecords, enum SearchDirectionEnum SearchDirection, const _variant_t &Start = vtMissing);

Where SearchDirectionEnum has the values:

enum SearchDirectionEnum
{
    adSearchForward,
    adSearchBackward
};

For example, to find the item that has the id of 23 after and including the current record:

pRet->Find("itemdid = 23", adSearchForward);

Error Handling

Most of the member functions of the ADO objects return an HRESULT value that will give us a clue of the how an operation fares.  However, it would be far elegant and better to use the C++ try and catch mechanism for handling ADO errors.  ADO objects throw an exception of type _com_error whenever an error is encountered.  The Description() member of _com_error returns a meaningful description of the error encountered.  For example:

try
{
    // Delicate ADO operations normally go here
    // . . . 
}

catch (_com_error &e)    // Catch any error...
{
    cerr << endl << (const char *)e.Description() << endl;  // ...then show its description.
}

Sample Code

This tutorial comes with supplementary sample programs that will further demonstrate the concepts elaborated above.

Future Installments

Watch out for Part II of this tutorial which would include a detailed walkthrough of the creation of the MFC example program above and another program with a different approach to accessing and updating field values using ADO record binding.  'Till then, happy new year and happy hacking!!!

Back to Programming Page

 

Copyright 2003-2004 Mark Jundo P. Documento

<xmp></body></xmp>