Managing database operations using ADO and C++: Part 3, ADO Wrapper Classes

By Patrick Mancier

Now that the basics are out of the way we can actually begin to design some useful code that will do something! We need an easy way to create, access and manage the ADO objects we have discussed. The term 'wrapper' is used in programming to describe a way to create code that manages some other API's or module. The terms 'encapsulation' or 'legacy management' can be used to describe what a class wrapper is. In this instance we aren't really handling legacy issues, but merely making it a little simpler to handle the mundane tasks associated with setting up and using the ADO interfaces provided. We want to keep all of the standard operations in a set of classes in order to allow our final ADO manager class to function smoothly.



The obvious way to separate our class wrappers is by the interface objects provided in ADO. This way each ADO object has a class that can be used to manipulate and manage it. We may need a few other classes to handle ancillary issues, but the bulk of our design for our class wrappers will be named as follows:
  • CADOConnection
  • CADOCommand
  • CADOParameter
  • CADORecordset


Notice that we have already discussed these ADO objects. The next thing is to break down what we need to do with each object. We will use the class wrapper CADOConnection as an example of how we will design these class wrappers. For the function of the CADOConnection class wrapper it is obvious we want to handle the connections to the database server. However, what may not be so obvious are the other things that are handled by the ADO Connection object. Referring to the MSDN library documentation or simply using the 'Object Browser' trick described in the previous section, we can see there are several methods that could be implemented.

Here is a complete list of methods in the ADO Connection object:
  • BeginTrans() - Begin a database transaction
  • Cancel() – Cancel a database operation that was called asynchronously
  • Close() – Close the connection to the database server
  • CommitTrans() – Commit the current set of database transactions to the database
  • Execute() – Execute a dynamic SQL query (no parameters)
  • Open() – Open up a connection to a database on the database server
  • OpenSchema() – Get the information on the database
  • RollbackTrans() – Cancel and restore the database snapshot to before the beginning of our transaction


If we choose to we could implement every one of these available methods. However, the question is, should we? It seems obvious we need to support Open() and Close(). We also should implement the ones involving transactions. We should also implement the Execute() method for doing on the fly queries. The Command ADO object also has an Execute() method that we will need for other reasons later. However, what is the OpenSchema() method? It turns out OpenSchema() is used to obtain the database schema architecture in the form of a recordset that lists what is in the schema. For our purposes, we really do not need to support this method. If we were creating some generic management tool we might be interested in this, but for creating code to interface to a database, this method is not necessary. So the idea we have discussed here is to take each ADO object, research what each one does and try to make an intelligent decision on which parts of the ADO object we really want to expose in our class wrappers. It does not make sense to do a bunch of work to support something that will never be used. This method can be applied to any project or API that is being used in any design.

We now know we want to support the BeginTrans(), Cancel(), Close(), CommitTrans(), Execute(), Open() and RollbackTrans() methods somehow. What is the best way to do this? That is a matter up for debate, and it is not the intention here to present the most pure methodology available in programming. We just want to make this class work reliably and make it easy to use. That being said let's explore a design that might accomplish this. To handle the database connection, we need to use the Open() and Close() methods. One person would argue that the Open should be discretely called while another person would say we don't need to worry about that, let the class wrapper handle all that and we will just instantiate the class and let it do its thing. The approach taken here is to attempt a combination of the two methodologies using name mangling. Let's say we want to call Open() and Close() discretely and we would also like the wrapper class to do it automatically. This is how we are going to accomplish this. Below is an example of how we will implement the Open() and Close() methods of the ADO Connection object using the wrapper class. This is by no means a final design on this class wrapper. This is to illustrate in simple terms how we can accomplish some the basic operations we would like to perform with the ADO Connection object.
class CADOConnection
{
private:
        _ConnectionPtr pConnection;
        CString m_szConnectionString;

        BOOL Initialize();

public:

        void SetConnectionString(CString& szConnectionString);
        TCHAR *GetConnectionString(){return m_szConnectionString);

        BOOL IsClosed();
        BOOL IsOpen();
        BOOL Open();
        BOOL Open(CString& szConnectionString, CString szUser=_T(""), CString szPassword=_T(""));
        BOOL Close();

        CADOConnection(CString& szConnectionString);
        CADOConnection(void);
        ~CADOConnection(void);
};
As can be seen there are two separate constructors. Specifically we have created a constructor that contains a character parameter for the connection string. The idea here is upon class instantiation to 'automatically' create a connection we will pass this connection string into the constructor. The constructor in this case will call the appropriate methods to establish the database connection. When the class is deleted, the destructor will be designed to do the normal cleanup and close the database connection if necessary. We also have several discrete functions that handle the opening and closing of the connection. As you can see, every one of these functions has a return of BOOL. This can be checked to see whether the operation was successful or not. In the final design we will have some other function that can return a status error code or a string when things go wrong, but for this discussion we are keeping things simple.

Below is an example of using the short hand method of using this class by instantiating the class using the connection string constructor and deleting it after it is done being used.
int main()
{
        CADOConnection *pADOConnection;

        pADOConnection = new CADOConnection("Data Source=(local); Initial Catalog=myDB; user=myusername; password=mypassword");

        if(pADOConnection->IsOpen())
        {
                //
                // Use the established connection
                //
        }
        else
        {
                // Process error
        }

        delete pADOConnection;
}
We also have the option of doing all operations discretely as well. We could instantiate the class, set the connection string up by calling SetConnectionString(), call Open(), check to see that the connection is indeed open by calling IsOpen(), close the connection using Close() and then checking IsClosed().
int main()
{
        CADOConnection *pADOConnection;

        pADOConnection = new CADOConnection();
        
        pADOConnection->SetConnectionString("Data Source=(local); Initial Catalog=myDB; user=myusername; password=mypassword"));
        pADOConnection->Open();
        if(pADOConnection->IsOpen())
        {
                //
                // Use the established connection
                //
        }
        else
        {
                // Process error
        }

        pADOConnection->Close();
        if(!pADOConnection->IsClosed())
        {
                // Process error
        }

        delete pADOConnection;
}
We could also do a semi-automatic combination of the two methodologies by calling the Open() method with the connection string as a parameter and check to see the return on this to see if it was successful or not.
int main()
{
        CADOConnection *pADOConnection;

        pADOConnection = new CADOConnection();
        
        if(pADOConnection->Open("Data Source=(local); Initial Catalog=myDB; user=myusername; password=mypassword"))
        {
                //
                // Use the established connection
                //
        }
        else
        {
                // Process error
        }

        delete pADOConnection;
}
As can be seen, a class wrapper properly designed can accommodate a variety of programming styles. The important thing to keep in mind in any program design is to be sure to check the status of all operations, especially checking things like return codes on Win32 and API calls. We have not discussed things here like exception handling and checking the return codes on various operations. These kind of items will be used in the final design of the class wrappers. This series of examples just serves as a basis for continuing the design process of our class wrappers. In the next part of this article we will discuss the design of the actual class that will use all these ADO wrapper classes and function to interface with the database schema that contains all our SQL tables and procedures.

Continue to part 4 of the tutorial
Back to part 2 of the tutorial
Back to the ADO tutorial index