The Basic OLE DB Template Architecture
Now that you understand the basic architecture behind OLE DB, let's 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 (which is often an inefficient approach—unless 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++ .NET provides a template library (the Active Template Library) for implementing ActiveX controls, Visual C++ .NET provides a template library that helps you manage OLE DB. The OLE DB template library provides classes that implement many of the commonly used OLE DB interfaces. In addition, Visual C++ .NET provides great wizard support for generating code to apply to common scenarios.At 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, and 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 (regular providers) to access data. OLE DB providers are COM servers that provide data and services in a form that a consumer can understand.
The 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. They simply wrap the existing OLE DB object model. For each of the consumer-related components discussed in the previous section, you'll find a corresponding C++ template class. This design philosophy leverages the flexibility of OLE DB and allows more advanced features—such as multiple accessors on rowsets—to be available through the OLE DB templates.
The OLE DB templates are small and flexible. They're 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. A quick summary of these classes follows.
General Data Source Support
The data source (where the data comes from) is the most fundamental concept underlying data access using OLE DB. Of course, the OLE DB templates have support for data sources. General data source support comprises three classes, as described in the following table.
Class | Description |
---|---|
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 it to create rowsets, commands, and many other objects. A CDataSource object creates a CSession object using the CSession::Open method. |
Data Access and Rowset Support
The OLE DB templates provide binding and rowset support through several classes. The accessor classes talk to the data source, and 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 listed in the following table.
Class | Description |
---|---|
CAccessor | This class is used when a record is statically bound to a data source—it contains the preexisting data buffer and understands the data format up front. You use CAccessor 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. This is especially useful for handling unknown command types. |
CManualAccessor | This class lets you access any 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. 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, and 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 consumer template 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). You should use this class when you need bare-bones access to a source; CTable is designed for simple providers that do not support commands.Other data sources do support commands. For those sources, you should 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 you use the CCommand class, you specialize it using three template arguments: an accessor, a rowset, and a third template argument (which defaults to CNoMultipleResults). If you specify CMultipleResults for the third argument, the CCommand class will support the IMultipleResults interface for a command that returns multiple rowsets.
The 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. Let's discuss each type in turn and look at code snippets that show how the templates bring in the correct functionality for each component.
The Data Source Object
A data source object wraps most aspects of data access. For example, a data source consists of actual data and its associated DBMS, the platform on which the DBMS exists, and the network used to access that platform. A data source is just a COM object that implements a bunch of interfaces, as shown in Table 27-1.
Note | The upcoming tables describing interface requirements were compiled from the Visual Studio .NET MSDN Online Help. |
Interface | Required? | Implemented? |
---|---|---|
IDBInitialize | Ö | Ö |
IDBCreateSession | Ö | Ö |
IDBProperties | Ö | Ö |
IPersist | Ö | Ö |
IDBDataSourceAdmin | ||
IDBInfo | ||
IPersistFile | ||
ISupportErrorInfo |
Here's a code snippet showing the code inserted by the ATL OLE DB Provider 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.
The Command Object
Providers that support building and executing queries expose a command object. Command objects specify, prepare, and execute a database manipulation language (DML) query or data definition language (DDL) definition and its associated properties. For example, the command object translates a SQL-type command into an operation specific to the data source. A single session can be associated with multiple commands. Table 27-2 lists the interfaces used in a command object.Here's a code snippet showing the code that the ATL OLE DB Provider Wizard inserts 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>,
public IInternalCommandConnectionImpl<CAProviderCommand>
{![]()
};
Interface | Required? | Implemented? |
---|---|---|
IAccessor | Ö | Ö |
IColumnsInfo | Ö | Ö |
ICommand | Ö | Ö |
ICommandProperties | Ö | Ö |
ICommandText | Ö | Ö |
IConvertType | Ö | Ö |
IColumnsRowset | ||
ICommandPrepare | ||
ICommandWithParameters | ||
ISupportErrorInfo |
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, IAccessor 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.
The Session Object
Session objects define the scope of a transaction and generate rowsets from the data source. Session objects also generate command objects. The command object executes commands on the rowset. For providers that support commands, the session acts as a command factory. Calling IDBCreateSession::CreateSession creates a session from the data source object. A single data source object can be associated with many sessions. Table 27-3 lists the interfaces found on a session object.
Interface | Required? | Implemented? |
---|---|---|
IGetDataSource | Ö | Ö |
IOpenRowset | Ö | Ö |
ISessionProperties | Ö | Ö |
IDBCreateCommand | Ö | |
IDBSchemaRowset | Ö | |
IIndexDefinition | ||
ISupportErrorInfo | ||
ITableDefinition | ||
ITransactionJoin | ||
ITransactionLocal | ||
ITransactionObject |
Here's a code snippet showing the code that the ATL OLE DB Provider Wizard inserts 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>
{![]()
};
The Rowset Object
A rowset object represents tabular data. At the raw OLE DB level, rowsets are generated by calling IOpenRowset::OpenRowset on the session. For providers that support commands, rowsets are used to represent the results of row-returning queries. In addition to IOpenRowset::OpenRowset, OLE DB has a number of other methods that return rowsets. For example, the schema functions return rowsets. Single sessions can be associated with multiple rowsets. In addition, single command objects can be associated with multiple rowsets. Table 27-4 lists the interfaces associated with the rowset object.
Interface | Required? | Implemented? |
---|---|---|
IAccessor | Ö | Ö |
IColumnsInfo | Ö | Ö |
IConvertType | Ö | Ö |
IRowset | Ö | Ö |
IRowsetInfo | Ö | Ö |
IColumnsRowset | ||
IConnectionPointContainer | Ö (through ATL) | |
IRowsetChange | ||
IRowsetIdentity | Ö (for Level 0) | Ö |
IRowsetLocate | ||
IRowsetResynch | ||
IRowsetScroll | ||
IRowsetUpdate | ||
ISupportErrorInfo |
Here's a code snippet showing the code that the ATL OLE DB Provider Wizard inserts 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_STR("FileName", 4, cFileName)
PROVIDER_COLUMN_ENTRY_STR("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 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 purpose of the first part of the architecture—the data source—should 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, the data source object creates a session object using the IDBCreateSession interface. The consumer uses this interface to connect to the data source object.The command object does most of the work. To make the data provider actually do something, you 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 while. Also, like most COM-based protocols, the OLE DB protocol involves a good amount of code to get going—code 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.