Basic OLE DB Template Architecture

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 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++ 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 features—such as multiple accessors on rowsets—to 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.

ClassUse
CDataSourceThis class represents the data source component and manages the connection to a data source.
CEnumeratorThis 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.
CSessionThis 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.

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 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.

ClassUse
CAccessorThis class is used when a record is statically bound to a data source—it 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.
CDynamicAccessorThis 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.
CDynamicParameterAccessorThis 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.
CManualAccessorThis 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.

  • 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 database management system (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 33-1.

InterfaceRequired?Implemented?
IDBInitialize MandatoryYes
IDBCreateSession MandatoryYes
IDBProperties MandatoryYes
IPersist MandatoryYes
IDBDataSourceAdmin OptionalNo
IDBInfo OptionalNo
IPersistFile OptionalNo
ISupportErrorInfoOptionalNo

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.

  • 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. Compared to ODBC, the command corresponds to the general functionality of an ODBC statement in an unexecuted state. A single session can be associated with multiple commands. Table 33-2 shows the interfaces used in a command object.

    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.

InterfaceRequired?Implemented?
IAccessorMandatoryYes
IColumnsInfoMandatoryYes
ICommandMandatoryYes
ICommandPropertiesMandatoryYes
ICommandTextMandatoryYes
IConvertTypeMandatoryYes
IColumnsRowsetOptionalNo
ICommandPrepareOptionalNo
ICommandWithParametersOptionalNo
ISupportErrorInfoOptionalNo

Table 33-2. Command object interfaces requirements.

  • 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. Compared to ODBC, the session object and the data source object encapsulate the functionality of the ODBC connection. Calling IDBCreateSession::CreateSession creates a session from the data source object. A single data source object can be associated with many sessions. Table 33-3 shows the interfaces found on a session object.

InterfaceRequired?Implemented?
IGetDataSourceMandatoryYes
IOpenRowsetMandatoryYes
ISessionPropertiesMandatoryYes
IDBCreateCommandOptionalYes
IDBSchemaRowsetOptionalYes
IIndexDefinitionOptionalNo
ISupportErrorInfoOptionalNo
ITableDefinitionOptionalNo
ITransactionJoinOptionalNo
ITransactionLocalOptionalNo
ITransactionObjectOptionalNo

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> { };
  • 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, there are a number of other methods in OLE DB that return rowsets. For example, the schema functions return rowsets. Compared to ODBC, a rowset encapsulates the general functionality of an ODBC statement in the executed state. Single sessions can be associated with multiple rowsets. In addition, single command objects can be associated with multiple rowsets. Table 33-4 shows the interfaces associated with the rowset object.
InterfaceRequired?Implemented?
IAccessorMandatoryYes
IColumnsInfoMandatoryYes
IConvertTypeMandatoryYes
IRowsetMandatoryYes
IRowsetInfoMandatoryYes
IColumnsRowsetOptionalNo
IConnectionPointContainerOptionalYes, through ATL
IRowsetChangeOptionalNo
IRowsetIdentityRequired for Level 0 Yes
IRowsetLocateOptionalNo
IRowsetResynchOptionalNo
IRowsetScrollOptionalNo
IRowsetUpdateOptionalNo
ISupportErrorInfoOptionalNo

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 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, 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 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.



Programming Visual C++
Advanced 3ds max 5 Modeling & Animating
ISBN: 1572318570
EAN: 2147483647
Year: 1997
Pages: 331
Authors: Boris Kulagin

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net