Now that you understand the basic architecture behind OLE DB, it's time to take a look at a specific implementation of the OLE DB interfaces (provided by the new OLE DB consumer and provider templates). Like most other COM-based technologies, OLE DB involves implementing a bunch of interfaces. Of course, just as with ActiveX Controls, you can choose to implement them by hand (often an inefficient approachunless you're just trying to understand the technology inside-out), or you can find someone else to do most of the dirty work. While OLE DB is a rich and powerful data access technology, getting it up and running by hand is a somewhat tedious task.
Just as Visual C++ provides a template library (ATL) for implementing ActiveX Controls, Visual C++ also provides a template library that helps you manage OLE DB. The OLE DB template support provides classes that implement many of the commonly used OLE DB interfaces. In addition, Visual C++ provides great wizard support for generating code to apply to common scenarios.
From a high level, you can divide the classes in this template library
into the two groups defined by OLE DB itself: the consumer classes and the
provider classes. The consumer classes help you implement database client
(consumer) applications, while the provider classes help you implement
database server (provider) applications. Remember that OLE DB consumers are
applications that call the COM interfaces exposed by OLE DB service providers
(or regular providers) to access data. OLE DB providers are COM servers
that provide data and services in a form that a consumer can understand.
OLE DB Consumer Template Architecture
Microsoft has kept the top layer classes in the OLE DB Consumer Templates as close to the OLE DB specification as possible. That is, OLE DB templates don't define another object model. Their purpose is simply to wrap the existing OLE DB object model. For each of the consumer-related components listed, you'll find a corresponding C++ template class. This design philosophy leverages the flexibility of OLE DB and allows more advanced featuressuch as multiple accessors on rowsetsto be available through the OLE DB Templates.
The OLE DB Templates are small and flexible. They are implemented using C++ templates and multiple inheritance. Because OLE DB templates are close to the metal (they wrap only the existing OLE DB architecture), each class mirrors an existing OLE DB component. For example, CDataSource corresponds to the data source object in OLE DB.
The OLE DB Consumer Template architecture can be divided into
three parts: the general data source support classes, classes for supporting data
access and rowset operations, and classes for handling tables and commands.
Here's a quick summary of these classes.
General Data Source Support
A data source is the most fundamental concept to remember when talking about data access using OLE DB. That is, where is the data coming from? Of course, the OLE DB templates have support for data sources. General data source support comprises three classes as shown in this table.
Class | Use |
CDataSource | This class represents the data source component and manages the connection to a data source. |
CEnumerator | This class provides a way to select a provider by cycling through a list of providers. Its functionality is equivalent to the SQLBrowseConnect and SQLDriverConnect functions. |
CSession | This class handles transactions. You can use this class to create rowsets, commands, and many other objects. A CDataSource object creates a CSession object using the CSession::Open method. |
The OLE DB templates provide binding and rowset support through several classes. The accessor classes talk to the data source while the rowset manages the data in tabular form. The data access and rowset components are implemented through the CAccessorRowset class. CAccessorRowset is a template class that's specialized on an accessor and a rowset. This class can handle multiple accessors of different types.
The OLE DB Template library defines the accessors in this table.
Class | Use |
CAccessor | This class is used when a record is statically bound to a data sourceit contains the pre-existing data buffer and understands the data format up front. CAccessor is used when you know the structure and the type of the database ahead of time. |
CDynamicAccessor | This class is used for retrieving data from a source whose structure is not known at design time. This class uses IColumnsInfo::GetColumnInfo to get the database column information. CDynamicAccessor creates and manages the data buffer. |
CDynamicParameterAccessor | This class is similar to CDynamicAccessor except that it's used with commands. When used to prepare commands, CDynamicParameterAccessor can get parameter information from the ICommandWithParameters interface, which is especially useful for handling unknown command types. |
CManualAccessor | This class lets you access whatever data types you want as long as the provider can convert the type. CManualAccessor handles both result columns and command parameters. |
Along with the accessors, the OLE DB templates define three types
of rowsets: single fetching, bulk, and array. These are fairly self-explanatory
descriptions. Clients use a function named
MoveNext to navigate through the data. The difference between the single fetching, bulk, and array rowsets lies in the
number of row handles retrieved when
MoveNext is called. Single fetching rowsets
retrieve a single rowset for each call to
MoveNext while bulk rowsets fetch multiple rows. Array rowsets provide a convenient array syntax for fetching
data. The OLE DB Templates provide the single row-fetching capability by default.
Table and Command Support
The final layer in the OLE DB Template consumer architecture consists of two more classes: table and command classes (CTable and CCommand). These classes are used to open the rowset, execute commands, and initiate bindings. Both classes derive from CAccessorRowset
The CTable class is a minimal class implementation that opens a table on a data source (which you can specify programmatically). Use this class when you need bare-bones access to a source, since CTable is designed for simple providers that do not support commands.
Other data sources also support commands. For those sources, you'll want to use the OLE DB Templates' CCommand class. As its name implies, CCommand is used mostly for executing commands. This class has a function named Open that executes singular commands. This class also has a function named Prepare for setting up a command to execute multiple times.
When using the CCommand class, you'll specialize it with three
template arguments: an accessor, a rowset, and a third template argument (which
defaults to CNoMultipleResults). If you specify
CMultipleResults for this third argu-
ment, the CCommand class will support the
IMultipleResults interface for a command that returns multiple rowsets.
OLE DB Provider Template Architecture
Remember that OLE DB is really just a set of interfaces that specify a protocol for managing data. OLE DB defines several interfaces (some mandatory and others optional) for the following types of objects: data source, session, rowset, and command. Here's a description of each followed by a code snippet that shows how the templates bring in the correct functionality for each component.
Interface | Required? | Implemented? |
IDBInitialize | Mandatory | Yes |
IDBCreateSession | Mandatory | Yes |
IDBProperties | Mandatory | Yes |
IPersist | Mandatory | Yes |
IDBDataSourceAdmin | Optional | No |
IDBInfo | Optional | No |
IPersistFile | Optional | No |
ISupportErrorInfo | Optional | No |
Table 33-1. Data source object interface requirements.
Tables in this section were compiled from the Microsoft Visual Studio MSDN Online Help.
Here's a code snippet showing the code that is inserted by the ATL Object Wizard when you create a data source for an OLE DB provider:
class ATL_NO_VTABLE CAProviderSource : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CAProviderSource, &CLSID_AProvider>, public IDBCreateSessionImpl<CAProviderSource, CAProviderSession>, public IDBInitializeImpl<CAProviderSource>, public IDBPropertiesImpl<CAProviderSource>, public IPersistImpl<CAProviderSource>, public IInternalConnectionImpl<CAProviderSource> { };
Notice that this is a normal COM class (with ATL's IUnknown implementation). The OLE DB data source object brings in implementations of the IDBCreateSession, IDBInitialize, IDBProperties, and IPersist interfaces through inheritance. Notice how the templates are specialized on the CAProviderSource and CAProviderSession classes. If you decide to add more functionality to your class, you can do so by inheriting from one of the OLE DB interface implementation classes.
Here's a code snippet showing the code inserted by the ATL Object Wizard to implement a command object when you create an OLE DB provider:
class ATL_NO_VTABLE CAProviderCommand : public CComObjectRootEx<CComSingleThreadModel>, public IAccessorImpl<CAProviderCommand>, public ICommandTextImpl<CAProviderCommand>, public ICommandPropertiesImpl<CAProviderCommand>, public IObjectWithSiteImpl<CAProviderCommand>, public IConvertTypeImpl<CAProviderCommand>, public IColumnsInfoImpl<CAProviderCommand> { };
As with the data source, notice that this is just a regular COM class. This class brings in the required interfaces through inheritance. (For example, IAccesor comes in through the IAccessorImpl template.) A command object uses IAccessor to specify parameter bindings. Consumers call IAccessor::CreateAccessor, passing an array of DBBINDING structures. DBBINDING contains information on the column bindings (type, length, and so on). The provider receives the structures and determines how the data should be transferred and whether conversions are necessary.
The ICommandText interface provides a way to specify a text command. The ICommandProperties interface handles all of the command properties.
The command class is the heart of the data provider. Most of the action happens within this class.
Interface | Required? | Implemented? |
IAccessor | Mandatory | Yes |
IColumnsInfo | Mandatory | Yes |
ICommand | Mandatory | Yes |
ICommandProperties | Mandatory | Yes |
ICommandText | Mandatory | Yes |
IConvertType | Mandatory | Yes |
IColumnsRowset | Optional | No |
ICommandPrepare | Optional | No |
ICommandWithParameters | Optional | No |
ISupportErrorInfo | Optional | No |
Table 33-2. Command object interfaces requirements.
Interface | Required? | Implemented? |
IGetDataSource | Mandatory | Yes |
IOpenRowset | Mandatory | Yes |
ISessionProperties | Mandatory | Yes |
IDBCreateCommand | Optional | Yes |
IDBSchemaRowset | Optional | Yes |
IIndexDefinition | Optional | No |
ISupportErrorInfo | Optional | No |
ITableDefinition | Optional | No |
ITransactionJoin | Optional | No |
ITransactionLocal | Optional | No |
ITransactionObject | Optional | No |
Table 33-3. Session object interfaces requirements.
Here's a code snippet showing the code inserted by the ATL Object Wizard to implement a session object when you create an OLE DB provider:
class ATL_NO_VTABLE CAProviderSession : public CComObjectRootEx<CComSingleThreadModel>, public IGetDataSourceImpl<CAProviderSession>, public IOpenRowsetImpl<CAProviderSession>, public ISessionPropertiesImpl<CAProviderSession>, public IObjectWithSiteSessionImpl<CAProviderSession>, public IDBSchemaRowsetImpl<CAProviderSession>, public IDBCreateCommandImpl<CAProviderSession, CAProviderCommand> { };
Interface | Required? | Implemented? |
IAccessor | Mandatory | Yes |
IColumnsInfo | Mandatory | Yes |
IConvertType | Mandatory | Yes |
IRowset | Mandatory | Yes |
IRowsetInfo | Mandatory | Yes |
IColumnsRowset | Optional | No |
IConnectionPointContainer | Optional | Yes, through ATL |
IRowsetChange | Optional | No |
IRowsetIdentity | Required for Level 0 | Yes |
IRowsetLocate | Optional | No |
IRowsetResynch | Optional | No |
IRowsetScroll | Optional | No |
IRowsetUpdate | Optional | No |
ISupportErrorInfo | Optional | No |
Table 33-4. Rowset object interfaces requirements.
Here's a code snippet showing the code inserted by the ATL Object Wizard to implement a rowset object when you create an OLE DB provider:
class CAProviderWindowsFile: public WIN32_FIND_DATA { public: BEGIN_PROVIDER_COLUMN_MAP(CAProviderWindowsFile) PROVIDER_COLUMN_ENTRY("FileAttributes", 1, dwFileAttributes) PROVIDER_COLUMN_ENTRY("FileSizeHigh", 2, nFileSizeHigh) PROVIDER_COLUMN_ENTRY("FileSizeLow", 3, nFileSizeLow) PROVIDER_COLUMN_ENTRY("FileName", 4, cFileName) PROVIDER_COLUMN_ENTRY("AltFileName", 5, cAlternateFileName) END_PROVIDER_COLUMN_MAP() }; class CAProviderRowset : public CRowsetImpl<CAProviderRowset, CAProviderWindowsFile, CAProviderCommand> { }
The wizard-generated rowset object implements the
IAccessor, IRowset, and IRowsetInfo interfaces, among others.
IAccessorImpl binds both output columns. The
IRowset interface fetches rows and data. The
IRowsetInfo interface handles the rowset properties. The
CWindowsFile class represents the user record class. The class generated
by the Wizard is really just a placeholder. It doesn't do very much.
When you decide on the column format of your data provider, this is
the class you'll modify.
How the Provider Parts Work Together
The use for the first part of the architecturethe data sourceshould be obvious. Every provider must include a data source object. When a consumer application needs data, the consumer calls CoCreateInstance to create the data source object and start the provider. Within the provider, it's the data source object's job to create a session object using the IDBCreateSession interface. The consumer uses this interface to connect to the data source object. In comparing this to how ODBC works, the data source object is equivalent to ODBC's HENV and the session object is the equivalent of ODBC's HDBC.
The command object does most of the work. To make the data provider actually do something, you'll modify the command class's Execute function.
Like most COM-based protocols, the OLE DB protocol makes sense once you've examined it for a little while. Also, like most COM-based protocols, the OLE DB protocol involves a good amount of code to get goingcode that could be easily implemented by some sort of framework. That's what the Data Consumer and Data Provider templates are all about. The rest of the chapter shows you what you need to do to create Data Consumers and Data Providers.