Click here to Skip to main content
Click here to Skip to main content

C++ Object Oriented Database Generator

By , 17 Nov 1999
 

Sample Image

Shown here is the application that generates CObject and CObList derived classes for Object-Oriented database management.

Some of the projects I have been working on lately have made extensive use of CObject derived classes for encapulated data management (Object-Oriented Databases). After creating only a few data objects, you begin to realize that the production of much of the code could be automated. This project is a code generator to produce CObject-dervied data classes with serialization and protected data members.

In addition to the CObject derived data classes, the application can also generate a sorted CObList derived class to hold your data objects. For each data object you create, you can specify index members. The list of objects is then sorted by these index members. When you add a new object to the list, the CObList derived class ensures that it is added in the correct position in the list.

Features of this code generator:

  • Produces CObject derived classes, and CObList derived classes to hold the objects in a sorted list
  • Class name follows the selected file name
  • Variables are assigned data types based on Hungarian notation prefixes
  • Includes Serialization code
  • All data members are protected and accessed with Get/Set inline functions
  • Includes initialization code in constructor
  • Includes member function to duplicate the properties of an object
  • Includes last modified date/time
  • Includes Dump and AssertValid functions
  • Object list can be sorted on multiple fields
Limitations of the current version:
  • Currently, you can only create one "key" value, although with a little trickery, your could run the application multiple times to generate multiple CObList derived class and allow multiple lists.
  • If you change the value of one of the key fields, the list does not resort. Although it would be a simple matter to add a sort function to the CObList derived class, the difficult is that there could be multiple instances of the CObList derived class throughout your application, all containing pointers to the same objects. So if one object changed a key field, you have to resort all those lists. The question is how best to keep track of where all those lists are. I don't have an answer for that yet!
  • The function to add a new object to the list is not well optimized for the sorted list
  • There is no copy constructor

To use the code generator:

1. Select a file name for the source file output. Your header file will have the same name but with an ".h" extension. Also, the class name is derived from the file name such that Example.cpp will produce a class called "CExample", and the COblist derived class will be CExampleList. Note that the code generator will overwrite any existing files.

2. Enter the names of the data members for the class. Data types are determined from the Hungarian notation prefixes. The program uses the following prefixes (these are easy to change):

  • dt -- COleDateTime
  • str -- CStirng
  • n -- int
  • f -- double (not FLAG!)
  • b -- BOOL
  • rgb -- COLORREF
If you want to generate a CObList derived class to hold your objects, enter Index members for the class. These will be added to your CObject derived class. The list will be sorted by these members in the order in which they appear. The indeces will also be used for comparing objects, and will available as parameters in a constructor.

If you leave the Indeces list blank, the application will not generate a CObList derived class. Only the CObject derived class will be generated.

3. Click the "Generate" button to generate the source file and header file.

The code generator produces code using my own programming style so you may want to adapt it to more closely follow your own style.


///////////////////////////////////////////////////////////////////////////
// CExample -- Interface for CExample object



#ifndef __EXAMPLE_H__
#define __EXAMPLE_H__


class CExample;

// This ObList derived class is generated if you add index member functions

class CExampleList : public CObList
{
public:
    CExample* FindEntry(COleDateTime dtDate, int nOrder, CString strLocationID);
    POSITION FindEntryPos(COleDateTime dtDate, int nOrder, CString strLocationID);
    POSITION FindEntryPosBruteForce(COleDateTime dtDate, int nOrder, 
        CString strLocationID);
    void AddExample(CExample* pNew);
    void RemoveExample(COleDateTime dtDate, int nOrder, CString strLocationID);
    virtual void Serialize(CArchive& ar);
    void ClearAndDelete();
};


// Here is the CObject derived class

class CExample : public CObject
{
// construction
public:
    DECLARE_SERIAL(CExample);
    CExample();
    CExample(COleDateTime dtDate, int nOrder, CString strLocationID);
    ~CExample();

// Attributes
public:



protected:
    COleDateTime m_dtDate;
    int m_nOrder;
    CString m_strLocationID;
    CString m_strCustomerName;
    COleDateTime m_dtBirthDate;
    COLORREF m_rgbColor;
    COleDateTime m_dtCreated;
    COleDateTime m_dtLastModified;


// Operations
public:
    virtual void Serialize(CArchive& ar);
    void Duplicate(CExample* pSource);
    void Clear();
    int Compare(COleDateTime dtDate, int nOrder, CString strLocationID);
    int Compare(CExample* pTest);


// Diagnostics
#ifdef _DEBUG
    void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif


// inlines
public:
    inline void SetDate(COleDateTime dtNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_dtDate = dtNew; }
    inline COleDateTime GetDate() {;
        return m_dtDate; }

    inline void SetOrder(int nNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_nOrder = nNew; }
    inline int GetOrder() {;
        return m_nOrder; }

    inline void SetLocationID(CString strNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_strLocationID = strNew;
        m_strLocationID.FreeExtra(); }
    inline CString GetLocationID() {;
        return m_strLocationID; }

    inline void SetCustomerName(CString strNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_strCustomerName = strNew;
        m_strCustomerName.FreeExtra(); }
    inline CString GetCustomerName() {;
        return m_strCustomerName; }

    inline void SetBirthDate(COleDateTime dtNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_dtBirthDate = dtNew; }
    inline COleDateTime GetBirthDate() {;
        return m_dtBirthDate; }

    inline void SetColor(COLORREF rgbNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_rgbColor = rgbNew; }
    inline COLORREF GetColor() {;
        return m_rgbColor; }

    inline void SetCreated(COleDateTime dtNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_dtCreated = dtNew; }
    inline COleDateTime GetCreated() {;
        return m_dtCreated; }

    inline void SetLastModified(COleDateTime dtNew) {
        m_dtLastModified = COleDateTime::GetCurrentTime();
        m_dtLastModified = dtNew; }
    inline COleDateTime GetLastModified() {;
        return m_dtLastModified; }



};


#endif // #define __EXAMPLE_H__

Here is the source file code generated using the program as configured in the above picture. The green comments are ones I added for this article and are not generated by the program.

///////////////////////////////////////////////////////////////////////////
// CExample -- Implementation for CExample object


#include "stdafx.h"
#include "Example.h"

IMPLEMENT_SERIAL(CExample, CObject, 0);


////////////////////////////////////////////////////////////
// CExampleList class members

// This function finds an object based on the index values provided
// it returns a pointer to the object
CExample* CExampleList::FindEntry(COleDateTime dtDate, int nOrder, 
                                  CString strLocationID)
{
    POSITION Pos = FindEntryPos(dtDate, nOrder, strLocationID);
    if (Pos == NULL) return NULL;
    return (CExample*)GetAt(Pos);
}


// This function finds the POSITION of an object based on the
// key values provided
POSITION CExampleList::FindEntryPos(COleDateTime dtDate, int nOrder, 
                                    CString strLocationID)
{
    POSITION Pos;
    CExample* pExample;
    div_t divresult;

    int nCurrent, nHigh, nLow, nCompareResult, nLastCurrent = -1;
    nLow = 0;
    nHigh = GetCount();
    divresult = div(nHigh - nLow, 2);
    nCurrent = nLow + divresult.quot;

    if (nHigh <= 0) goto l_NotFound;  // no items in the list
    while (TRUE)
    {
        Pos = FindIndex(nCurrent);
        pExample = (CExample*)GetAt(Pos);
        nCompareResult = pExample->Compare(dtDate, nOrder, 
            strLocationID);
        if (nCompareResult == 0)
        {
            return Pos;
        }
        if (nCompareResult > 0) // we are in lower half of test range
        {
            nHigh = nCurrent;
        divresult = div(nHigh - nLow, 2);
        nCurrent = nLow + divresult.quot;
        }
        else // we are in upper half of test range
        {
            nLow = nCurrent;
        divresult = div(nHigh - nLow, 2);
        nCurrent = nLow + divresult.quot;
        }
        if (nCurrent == nLastCurrent) goto l_NotFound;
        nLastCurrent = nCurrent;
    }

    l_NotFound:;

// The search above is optimized to work on a pre-sorted list
// To make sure it works, we search the list through brute force.  If we didn't
// find it above, we shouldn't find it below

#ifdef _DEBUG
    Pos = FindEntryPosBruteForce(dtDate, nOrder, strLocationID);
    if (Pos != NULL) TRACE("Searching algorithm failed\n");
#endif

    return NULL;
} // end of FindEntryPos


// This function searches the list for an object
// based on the key values provided.  However it does not require
// a pre-sorted list.  You should implement this class so that this function
// is not required in the release version

POSITION CExampleList::FindEntryPosBruteForce(COleDateTime dtDate, 
                                              int nOrder, 
                                              CString strLocationID)
{
    CExample* pExample;
    POSITION Pos = GetHeadPosition();
    while (Pos)
    {
        pExample = (CExample*)GetNext(Pos);
        if (pExample->Compare(dtDate, nOrder, strLocationID) == 0)
        {
            return Pos;
        }
    }
    return NULL;
} // end of FindEntryPosBruteForce


// This function adds an object to the list and places it in
// it's proper position based on the value of the key fields

void CExampleList::AddExample(CExample* pNew)
{
    CExample* pExample;
    int nCompareResult;
    POSITION Pos;

    // need to search through list and add in the proper sorted order
    ASSERT_VALID(pNew);
    ASSERT(Find(pNew) == NULL);

    // start from end because it is more likely to be added to the end
    Pos = GetTailPosition();
    while (Pos)
    {
        pExample = (CExample*)GetAt(Pos);
        nCompareResult = pExample->Compare(pNew);
        ASSERT(nCompareResult != 0);
        if (nCompareResult == 0) return;
        if (nCompareResult == -1)
        {
            InsertAfter(Pos, pNew);
            return;
        }
        GetPrev(Pos);
    }
    AddHead(pNew);
    return;
}


void CExampleList::RemoveExample(COleDateTime dtDate, int nOrder, 
                                 CString strLocationID)
{
    POSITION Pos = FindEntryPos(dtDate, nOrder, strLocationID);
    if (Pos) RemoveAt(Pos);
}


// Function to serialize the list and all it's objects

void CExampleList::Serialize(CArchive& ar)
{
    // NOTE:  Do not call the base class!
    DWORD dwVersion = 0x00000000;
    int n, nCount;
    POSITION Pos;
    CExample* pExample;

    if (ar.IsStoring())
    {
        ar<<dwVersion;

        nCount = GetCount();
        ar<<nCount;
        Pos = GetHeadPosition();
        while (Pos)
        {
            pExample = (CExample*)GetNext(Pos);
            pExample->Serialize(ar);
        }
    }
    else
    {
        ar>>dwVersion;
        ASSERT(GetCount() == 0);
        ar>>nCount;
        for (n = 0; n < nCount; ++n)
        {
            pExample = new CExample();
            if (pExample == NULL)
            THROW(new CMemoryException());
            pExample->Serialize(ar);
            AddTail(pExample);
        }
    }
} // end of Serialize

// This function clears the list but also deletes the object.
// Make sure you only delete the objects once!
// If you have multiple instances of this list, only one should 
// ever call this function!
void CExampleList::ClearAndDelete()
{
    CExample* pExample;
    POSITION Pos = GetHeadPosition();
    while (Pos)
    {
        pExample = (CExample*)GetNext(Pos);
        ASSERT_VALID(pExample);
        delete pExample;
    }
    RemoveAll();
}



////////////////////////////////////////////////////////////
// CExample class members



////////////////////////////////////////////
// CExample construction/destruction 


// Construction
CExample::CExample()
{
    Clear();
    m_dtCreated = COleDateTime::GetCurrentTime();
}

// If you create Index fields for your object,
// the application will generate a second constructor with the index 
// fields as parameters

CExample::CExample(COleDateTime dtDate, int nOrder, CString strLocationID)
{
    Clear();
    m_dtCreated = COleDateTime::GetCurrentTime();
    m_dtDate = dtDate;
    m_nOrder = nOrder;
    m_strLocationID = strLocationID;
}


// Initialization
void CExample::Clear()
{
    m_dtDate = 0.0;
    m_nOrder = 0;
    m_strLocationID = _T("");
    m_strCustomerName = _T("");
    m_dtBirthDate = 0.0;
    m_rgbColor = 0;
    m_dtCreated = 0.0;
    m_dtLastModified = 0.0;
}



// Destruction
CExample::~CExample()
{



}




////////////////////////////////////////////
// CExample Diagnostics 

#ifdef _DEBUG

void CExample::Dump(CDumpContext& dc) const
{
    dc.SetDepth(1);
    dc <<"Date = " << m_dtDate;
    dc <<"Order = " << m_nOrder;
    dc <<"LocationID = " << m_strLocationID;
    dc <<"CustomerName = " << m_strCustomerName;
    dc <<"BirthDate = " << m_dtBirthDate;
    dc <<"Color = " << m_rgbColor;
    dc <<"Created = " << m_dtCreated;
    dc <<"LastModified = " << m_dtLastModified;
}

void CExample::AssertValid() const
{
    CObject::AssertValid();

    // TODO:  Add validity checking here
}
#endif





////////////////////////////////////////////
// CExample operations 


void CExample::Serialize(CArchive& ar)
{
    DWORD dwVersion = 0x00000000;

    if (ar.IsStoring())
    {
        ar<<dwVersion;
        ar<<m_dtDate<<m_nOrder<<m_strLocationID
            <<m_strCustomerName<<m_dtBirthDate<<m_rgbColor
            <<m_dtCreated<<m_dtLastModified;


    }
    else
    {
        ar>>dwVersion;
        ar>>m_dtDate>>m_nOrder>>m_strLocationID>>m_strCustomerName
          >>m_dtBirthDate>>m_rgbColor>>m_dtCreated>>m_dtLastModified;


    }
    CObject::Serialize(ar);
} // end of Serialize


// I prefer a duplicate function rather than a copy constructor

void CExample::Duplicate(CExample* pSource)
{
    m_dtDate = pSource->m_dtDate;
    m_nOrder = pSource->m_nOrder;
    m_strLocationID = pSource->m_strLocationID;
    m_strCustomerName = pSource->m_strCustomerName;
    m_dtBirthDate = pSource->m_dtBirthDate;
    m_rgbColor = pSource->m_rgbColor;
    m_dtCreated = pSource->m_dtCreated;
    m_dtLastModified = pSource->m_dtLastModified;
} // end of Duplicate


// To support the searching and sorting in the list
// we must have a compare function to compare to objects based on their key fields
// the function operates the same as CString::Compare
int CExample::Compare(COleDateTime dtDate, int nOrder, CString strLocationID)
{
    int nCompare;

    if (dtDate.m_status != COleDateTime::valid) return -1;
    if (m_dtDate < dtDate) return -1;
    if (m_dtDate > dtDate) return 1;

    if (m_nOrder < nOrder) return -1;
    if (m_nOrder > nOrder) return 1;

    nCompare = m_strLocationID.Compare(strLocationID);
    if (nCompare < 0) return -1;
    if (nCompare > 0) return 1;

    return 0;
}

int CExample::Compare(CExample* pTest)
{
    return Compare(pTest->GetDate(), pTest->GetOrder(), pTest->GetLocationID());
}

So that's it. I am somewhat new to OODB, so I am interested in feedback and improvements.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Robert Pittenger, MCPD-EAD
President Starpoint Software Inc.
United States United States
Member
Bob Pittenger is founder and President of Starpoint Software Inc. He holds a B.A. degree from Miami University, M.S. and Ph.D. degrees from Purdue University, and an MBA from Xavier University. He has been programming since 1993, starting with Windows application development in C++/MFC and moving to C# and .NET around 2005 and is a .NET Microsoft Certified Professional Developer.
 
Bob is the author of two books:
Billionaire: How the Ultra-Rich Built Their Fortunes Through Good and Evil and What You Can Learn from Them
and
Wealthonomics: The Most Important Economic and Financial Concepts that Can Make You Rich Fast.
Visit http://www.billionairebook.net for more information.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Generalpardon my curiocity (and a cool suggestion)susssteveb30 May '03 - 17:54 
all I see is the genereator of serializable class. where did I miss the actual relational database?
Smile | :)
 
Anyway, serializable nested classes work spledid as far as small nested storage concerned. But there is one problem. Consider a database that contain close sub trillion records. Now - serializable class keeps all its data in the RAM at all times in between serializations. What I am drifting to is - some databases are so large that there is no amounts of RAM will be ever sufficient to hold their data. You need to add a mechanism of having some type of reading data by not actually loading it all into RAM. Consider scenario of 100 gig of data (on average 256 Meg RAM workstation) loaded with a statement SELECT * FROM INTRADAY_TICKS WHERE SYMBOL='SUNW'. If all this data is loaded into RAM it will effectively bring down your workstation (quiet possibly on the amount of lesser trhan 1% of total data queried). Nice workaround this very problem is creating a scrollable record iterator that only delimits record from start to end, spanning only query range, but not loading it physically into ram. When you iterate through it it loads current record and unloads previous so you always have a robust OS state. Poke tongue | ;-P
cheers
GeneralObject databasememberspike3128 Feb '03 - 16:08 
Hi Robert,
 
I found your application very interesting. I want to use it for an application in MFC, but wiyh ODBC connetion. My problem is how to connect it with the database wich i made in Access. Can you please tell my how the classes stitch up together with a database? I also use a maps and CObMapToString member. If you like i can send you the application.
 
Thanks in advance,
Eduard
GeneralMore Robust Code Generatormemberperlmunger26 Jul '02 - 7:37 
Seems to me that a lot of people are now starting at the database level to create objects that abstract database access. I personally love code generators because I'm a bit lazy Wink | ;-) (see sig) . The thing that I would think would make this really killer is if it were to either read a database schema or connect to a DSN and generate objects from what it finds. I've written my own code generator to do this sort of thing in the past, but it was limited because I didn't start at the database either. It just seems like the logical thing to do. I suppose it is just a larger undertaking than I am willing to take on.
 
Nice job even though it's been over two years. Wink | ;-)
 
-Matt
 
------------------------------------------
 
The 3 great virtues of a programmer:
Laziness, Impatience, and Hubris.
--Larry Wall
GeneralRe: More Robust Code GeneratorsussAnonymous26 Jul '02 - 14:11 
I (the author of the article) am currently using this technology in my fourth maor application. Here is the big problem... It takes many iterations to get the database schema the way you want it. You can spend all the time in the world planning your design, but when you see it in action, you (or the customer) gets new ideas that weren't considered before. Except now, your machine generated code has been modified, so if you regenerate it, you lose your modifications. Otherwise you have to make further modifications by hand. I have developed some work arounds from experience. All in all, it is still much better and more robust than coding everything by hand from the beginning.
 
IMO, there is not much point in implementing all the work to read a database scheme. Matching the DB schema to the object takes very little time at all.
 
BTW, I am posting anonymous because I don't want to log in.
GeneralRe: More Robust Code Generatormemberperlmunger27 Jul '02 - 7:20 
I agree with you. In a perfect world we would have all of the requirements of a given project locked down from the get-go, but that's not, IMO, possible. Part of developing software is demonstrating it to your customer. This *always* introduces new ideas to them and they want it changed. I do think, however, that if you take a code generator for what it's worth, it is an extremely powerful tool that can eliminate much of the tedium.
 
Maybe some day AI will be good enough to figure things out, so to speak. Until then, we just rely on the computer for the parts it's good at and fill in the gaps ourselves. I have found that an iterative approach works pretty well to get you closer to the perfect code generator. For instance, if you find that you are always changing a certain part of the generated code when you first start, it is a very likely that that piece of code should be added to your code generator. I've done this many times and it really helps it to become a more and more robust application. It, obviously, doesn't remove the need for human tweakery altogether, but it sure helps to narrow the gaps.
 
Thanks for engaging this discussion.
 
Best regards.
 
-Matt
 
------------------------------------------
 
The 3 great virtues of a programmer:
Laziness, Impatience, and Hubris.
--Larry Wall
GeneralAuthor's reflections 2 years latermemberRobert Pittenger7 Dec '01 - 11:28 
Well, I can tell that not a lot of people have been interested in this code and that is too bad, because two years later, I am starting to think this is one of the smartest things I ever did.
 
Since this article was posted, I have enhanced the generator to create database objects, and collection classes to hold them. I can't repost it because there are a lot of proprietary dependencies in the code, and the generator no longer produces code that compiles without modification.
 
Since this article was written, I have used variations of this object oriented design in two commercial applications. What have I learned?
 
1. An object oriented design has a lot of advantages, but the biggest disadvantage (as implemented here) is the inability to have data accessed by multiple users on a client-server architecture.
 
2. Another disadvantage is the time it takes to allocate all the memory for all the objects when reading files and updating pointers. Speed was definately a problem.
 
3. I ditched the pure OO design, and went to a relational design for an update to one of the commercial products. You'd think this a difficult task, but the good OO design, and the already existing objects made it relatively easy. The point is, it works VERY WELL to use an object oriented data design running over top of a relational database. There are so many advantages, it is hard to list them all here (besides, I doubt you are reading this anyway). The only disadvantage was really speed, and a little bit of akwardness reading rowsets from queries.
 
4. The idea of a code generator saved many many many hours of tedious repetative coding. I can not quickly whip together database schemas for complex objects, generate the code, and whip the whole thing together in an app. It is well worth your time to write code generators if you think you will be in this situation.
 
That's all for now. I will check in again in another two years.
GeneralRe: Author's reflections 2 years latermemberAnonymous10 Feb '02 - 9:55 
Could you post the genrator executable?
GeneralRe: Author's reflections 2 years latermemberWes Aday8 Aug '06 - 12:54 
Robert Pittenger wrote:
not a lot of people have been interested


Not at all. I just found this and think that it's a nifty idea.
GeneralObject-oriented database design and implementationmemberkushibhai6 Aug '01 - 7:22 
Objectives
The objectives of this project are: to design a conceptual database schema (Entity-
Relationship diagram or any other conceptual modeling notation) for a given
specification of a sample database domain, to translate a conceptual schema into
object classes and associations of object-oriented database, to specify the object
classes and associations in Object Definition Language, to implement and to load a
sample contents of object-oriented database and to implement few database
applications using Object SQL and C++ bindings.
GeneralRe: Object-oriented database design and implementationmemberBoby24 Aug '01 - 3:57 
What other modeling notations do you know and how I can get their descriptions or articles about them?
Who is starting this project - personally you, your company? I am interested just because I intend to star something similar.

GeneralNew versionmemberAnonymous18 Nov '00 - 7:36 
AppBuilder--A complete truly visual C/C++ IDE.it offers Microsoft Foundation Class developers a complete library of MFC extension classes that implement Authentic Looking GUIs like those seen in Microsoft money 2000? AppBuilder can help developers develop application with database.The classes fit seamlessly with MFC and inherit many of MFC's existing classes. You can now easily give your database application the MS money look, without going broke! that's not all,AppBuilder is also a code generater,it can generate database code based on ODBC API or MFC ODBC, this can help you develop manage information system easily and quickly. if you want learn more about appbuilder,Please visit: http://www.ucancode.net
GeneralRe: New versionmemberrajul10 Feb '01 - 18:54 
I want to know about this project can u send me details about this project on my emil id.
GeneralRe: New versionmemberFrijo Franco25 Apr '01 - 23:03 
Hai,
Please provide me some sample codes for connecting C++ with sql/access database. I am uding Turbo C++ on windows platform
Thanks in advance
 
My email is frijofranco@yahoo.com
 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 18 Nov 1999
Article Copyright 1999 by Robert Pittenger, MCPD-EAD
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid