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

The CGeneric Database classes

, 2 Dec 1999
Rate this:
Please Sign up or sign in to vote.
<!-- Article Starts -->

I was partly inspired by an article by Bob Place. I sent an e-mail to him address to ask for permission to re-use his code, but the e-mail address doesn't seem to work anymore. Therefore, I decided not to republish his initial code. All the code located in this article was written by the author of the article.

Introduction

In October of 1999, I had to convert my company's database files ( which used serialization ) to a DAO database. At first, I had tried the classic approach of creating an Access Database, then deriving classes from the CDaoRecordset class for each table. The whole process struck me as being inefficiant. Changes in the database structure where hard to make, and it made me feel uneasy.

Then I stumbled upon the CGenericRecordset class, in an article published in CodeGuru by Bob Place.

It detailed a class which enabled easy transfert from a DAO Record Set, without the need to derive a new class. I loved the idea, and added some functions to read other type of data. I also created other support classes which will be detailed later in the arcticle. But first, my new functions :

bool CGenericRecordset::GetBool(CString ColumnName)
{
	COleVariant covFieldValue;
	VARIANT *vFieldValue;
	GetFieldValue(ColumnName, covFieldValue);
	vFieldValue = (LPVARIANT)covFieldValue;
	return vFieldValue->boolVal ? true:false;
}
BYTE CGenericRecordset::GetByte(CString ColumnName)
{
	COleVariant covFieldValue;
	VARIANT *vFieldValue;
	GetFieldValue(ColumnName, covFieldValue);
	vFieldValue = (LPVARIANT)covFieldValue;
	return vFieldValue->bVal;
}
bool CGenericRecordset::GetBool(int ColumnNumber)
{
	COleVariant covFieldValue;
	VARIANT *vFieldValue;
	GetFieldValue(ColumnNumber, covFieldValue);
	vFieldValue = (LPVARIANT)covFieldValue;
	return vFieldValue->boolVal? true:false;
}

CByteArray * CGenericRecordset::GetByteArray(CString ColumnName)
{
	CByteArray * pHolder = new CByteArray ;

	COleVariant covFieldValue;
	
	GetFieldValue(ColumnName, covFieldValue);
	if ( covFieldValue.parray != NULL )
		{
		pHolder->SetSize( covFieldValue.parray->cbElements * covFieldValue.parray->cDims * covFieldValue.parray->rgsabound->cElements );
		
		memcpy ( pHolder->GetData(), covFieldValue.parray->pvData, covFieldValue.parray->cbElements * covFieldValue.parray->cDims * covFieldValue.parray->rgsabound->cElements );
		return pHolder;
		}
	else
		{
		delete pHolder;
		return NULL;
		}
}
BYTE CGenericRecordset::GetByte(int ColumnNumber)
{
	COleVariant covFieldValue;
	VARIANT *vFieldValue;
	GetFieldValue(ColumnNumber, covFieldValue);
	vFieldValue = (LPVARIANT)covFieldValue;
	return vFieldValue->bVal;
}
CByteArray * CGenericRecordset::GetByteArray(int ColumnNumber)
{
	CByteArray * pHolder = new CByteArray ;

	COleVariant covFieldValue;
	
	GetFieldValue(ColumnNumber, covFieldValue);
	
	if ( covFieldValue.parray != NULL )
		{
		pHolder->SetSize( covFieldValue.parray->cbElements * covFieldValue.parray->cDims * covFieldValue.parray->rgsabound->cElements );
		
		memcpy ( pHolder->GetData(), covFieldValue.parray->pvData, covFieldValue.parray->cbElements * covFieldValue.parray->cDims * covFieldValue.parray->rgsabound->cElements );
		return pHolder;
		}
	else
		{
		delete pHolder;
		return NULL;
		}

}

The GetByte and GetBool function are easy to understand, since they are used in the same way as the other functions. However, the GetByteArray functions are a little different. Here is an example of how to call them :


CGenericRecordset *p_rs;
// .. open the record set and position it 

CByteArray * pArray = p_rs->GetByteArray( lpszColumnName );
if ( pArray != NULL  )
	{
	memcpy( &data, pArray->GetData(), pArray->GetSize()  );
	delete pArray;
	}

In the example above, the data is copied to the buffer "data", which is assumed to be long enough. By the way, data could be any buffer : It could be a structure, it could be an array. I use it to save structures easily in the database.

Writing to the Record Set

Reading wasn't enough, I also implemented writing functions. Here they are :

void CGenericRecordset::SetCString(CString ColumnName,CString data)
{
	//COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, ( LPCTSTR )data);
}
void CGenericRecordset::SetShort(CString ColumnName,short data)
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, covFieldValue);
}
void CGenericRecordset::SetLong(CString ColumnName,long data)
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, covFieldValue);
}
void CGenericRecordset:: SetDouble(CString ColumnName,double data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, covFieldValue);
}
void CGenericRecordset::SetFloat(CString ColumnName,float  data)
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, covFieldValue);
}
void CGenericRecordset::SetBool(CString ColumnName,bool data)
{
	COleVariant covFieldValue;
	covFieldValue.ChangeType( VT_BOOL );
	covFieldValue.boolVal = data;
	SetFieldValue(ColumnName, covFieldValue);
}
void CGenericRecordset::SetByte(CString ColumnName,BYTE data)
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, covFieldValue);
}

void CGenericRecordset::SetDate(CString ColumnName,DATE data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, covFieldValue);
}
void CGenericRecordset::SetColumn(CString ColumnName,VARIANT data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnName, covFieldValue);
}
// Set by Column Number
void CGenericRecordset::SetCString(int ColumnNumber,CString data )
{
	COleVariant covFieldValue = data;
	CString Holder;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset::SetShort(int ColumnNumber,short data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset::SetLong(int ColumnNumber,long data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset:: SetDouble(int ColumnNumber,double data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset::SetFloat(int ColumnNumber,float data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset::SetBool(int ColumnNumber,bool data )
{
	COleVariant covFieldValue;
	covFieldValue.ChangeType( VT_BOOL );
	covFieldValue.boolVal = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset::SetByte(int ColumnNumber,BYTE data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}

void CGenericRecordset::SetDate(int ColumnNumber,DATE data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset::SetColumn(int ColumnNumber,VARIANT data )
{
	COleVariant covFieldValue = data;
	SetFieldValue(ColumnNumber, covFieldValue);
}
void CGenericRecordset::SetByteArray(int ColumnNumber, CByteArray *ByteArray)
{

COleVariant Variant;
Variant = *ByteArray;
SetFieldValue( ColumnNumber, Variant );
}

void CGenericRecordset::SetByteArray(CString ColumnName, CByteArray *ByteArray)
{
COleVariant Variant;
Variant = *ByteArray;
SetFieldValue( ColumnName, Variant );
}

All the functions are pretty obvious. The CByteArray ARE NOT DELETED by the function, so if you allocate them dynamically, you must free them. Just in case you don't know how to call them, here is an example :


CGenericRecordset *p_rs;
// .. open the record set and position it 

CByteArray * pArray = new CByteArray;
		
pArray->SetSize( SizeOfData );
		
memcpy( pArray->GetData(), &Data, SizeOfData );

p_rs->SetByteArray( ColumnName ,pArray);	  
delete pArray;

CGenericTableDef

I was also caught with a problem : How could I create a Table with as little trouble as possible ? I simply created a new class : A CGenericTableDef class.

Let's focus on the first new "task" of that class : Creating fields. I wanted to be able, everytime I opened my database, to recreate fields which could have been deleted by the user, or, simply, create for the first time a new field added to a table. I was shocked to learn that DAO threw execptions when attempting to create an existing field. Then, I got an idea : I create a function which would first check to see if a field existed, and if it did not, would create it. I also decided ease the process of creating a table by splitting the creation of fields in two parts :
1 - Creating the field info
2 - Adding the fields to the table
The process is performed by the following functions in the CGenericTableDef class:

bool IsFieldInTable ( CString Name );
static int AddFieldToFieldInfo( CPtrArray * pArray, LPCTSTR lpszName, short nType, bool AutoIncrement = false );
static int AddByteArrayFieldToFieldInfo( CPtrArray * pArray, CString lpszName );
static int AddTextFieldToFieldInfo(CPtrArray *pArray, LPCTSTR lpszName, BYTE size );
void CreateTableFromFieldInfo ( CString Name, CPtrArray * pArray );
CPtrArray * GetAllFieldsInformation ();
static void DestroyAllFieldsInformation ( CPtrArray * pArray );

The first one simply checks to see if the field exists. The next three add fields to a field array ( actually a CPtrArray ). The fourth one create the actual table, the fifth one reads the current list of fields, and the last deletes the field array when you are finished. Here are the implementation of these functions :


bool CGenericTableDef::IsFieldInTable(CString Name)
{
CPtrArray * pArray = GetAllFieldsInformation();

bool found = false;

for ( int i = 0; i < pArray->GetSize(); i++ )
	{
	CDaoFieldInfo * pField = ( CDaoFieldInfo * )pArray->GetAt(i) ;
	if ( Name == pField->m_strName )
		{
		found = true;
		break;
		}
	}
DestroyAllFieldsInformation( pArray);
return found;
}


int CGenericTableDef::AddFieldToFieldInfo(CPtrArray *pArray, LPCTSTR lpszName, short nType, bool AutoIncrement)
{

CDaoFieldInfo * p_fieldinfo = new CDaoFieldInfo;

p_fieldinfo->m_nType = nType;
p_fieldinfo->m_strName = lpszName;
p_fieldinfo->m_bAllowZeroLength = false;
p_fieldinfo->m_bRequired = false;
p_fieldinfo->m_lCollatingOrder = 0;
p_fieldinfo->m_nOrdinalPosition = 0;
p_fieldinfo->m_strDefaultValue = "";
p_fieldinfo->m_strForeignName = "";
p_fieldinfo->m_strSourceField = "";
p_fieldinfo->m_strSourceTable = "";
p_fieldinfo->m_strValidationRule = "";
p_fieldinfo->m_strValidationText = "";

if ( AutoIncrement )
	{
	p_fieldinfo->m_lAttributes = dbFixedField | dbAutoIncrField;
	}
else
	{
	p_fieldinfo->m_lAttributes = dbFixedField | dbUpdatableField;

	}
switch ( nType )
	{
	case dbBoolean :
		p_fieldinfo->m_lSize =1 ;
		break;
	case dbByte :
		p_fieldinfo->m_lSize =1 ;
		break;
	case dbInteger :
		p_fieldinfo->m_lSize =2 ;
		break;
	case dbLong :
		p_fieldinfo->m_lSize =4 ;
		break;
	case dbCurrency :
		p_fieldinfo->m_lSize =8 ;
		break;
	case dbSingle :
		p_fieldinfo->m_lSize =4 ;
		break;
	case dbDouble :
		p_fieldinfo->m_lSize =8 ;
		break;
	case dbDate :
		p_fieldinfo->m_lSize =8 ;
		break;
	default:
		p_fieldinfo->m_lSize =0 ;
		break;
	}
return pArray->Add( p_fieldinfo );
}

int CGenericTableDef::AddTextFieldToFieldInfo(CPtrArray *pArray, LPCTSTR lpszName, BYTE size)
{

CDaoFieldInfo * p_fieldinfo = new CDaoFieldInfo;

p_fieldinfo->m_nType = dbText;
p_fieldinfo->m_strName = lpszName;

p_fieldinfo->m_lAttributes = dbUpdatableField;
p_fieldinfo->m_bAllowZeroLength = true;
p_fieldinfo->m_lSize =size ;
p_fieldinfo->m_bRequired = false;
p_fieldinfo->m_lCollatingOrder = 0;
p_fieldinfo->m_nOrdinalPosition = 0;
p_fieldinfo->m_strDefaultValue = "";
p_fieldinfo->m_strForeignName = "";
p_fieldinfo->m_strSourceField = "";
p_fieldinfo->m_strSourceTable = "";
p_fieldinfo->m_strValidationRule = "";
p_fieldinfo->m_strValidationText = "";

return pArray->Add( p_fieldinfo );
}
int CGenericTableDef::AddByteArrayFieldToFieldInfo(CPtrArray *pArray, CString lpszName)
{
CDaoFieldInfo * p_fieldinfo = new CDaoFieldInfo;

p_fieldinfo->m_nType = 11;
p_fieldinfo->m_strName = lpszName;

p_fieldinfo->m_lAttributes = dbUpdatableField;

p_fieldinfo->m_lSize =0 ;
p_fieldinfo->m_bAllowZeroLength = false;
p_fieldinfo->m_bRequired = false;
p_fieldinfo->m_lCollatingOrder = 0;
p_fieldinfo->m_nOrdinalPosition = 0;
p_fieldinfo->m_strDefaultValue = "";
p_fieldinfo->m_strForeignName = "";
p_fieldinfo->m_strSourceField = "";
p_fieldinfo->m_strSourceTable = "";
p_fieldinfo->m_strValidationRule = "";
p_fieldinfo->m_strValidationText = "";
return pArray->Add( p_fieldinfo );

}
void CGenericTableDef::CreateTableFromFieldInfo(CString Name, CPtrArray *pArray)
{
if ( IsOpen() && GetName() != Name )
	{
	Close();
	}

if ( !IsOpen() && !( (CGenericDatabase * )m_pDatabase)->IsTableInDatabase( Name ) )
	{
	Create( Name );
	SetName( Name);
	
	for ( int i = 0; i < pArray->GetSize(); i++ )
		{
		CDaoFieldInfo * pField = ( CDaoFieldInfo * )pArray->GetAt(i) ;
		TRY
			{
			CreateField( *pField );
			}
		CATCH_ALL(e)
			{
			e->ReportError();
			}
		END_CATCH_ALL
		}
	
	Append();
	Close();
	Open( Name);
	}
else
	{
	if ( !IsOpen() )
		{
		Open( Name);
		}
	
	for ( int i = 0; i < pArray->GetSize(); i++ )
		{
		CDaoFieldInfo * pField = ( CDaoFieldInfo * )pArray->GetAt(i) ;
		CDaoFieldInfo pExistingInfo;
		
		if ( !IsFieldInTable ( pField->m_strName ) )
			{
			CreateField( *pField  );
			}
		}
	}
}

CPtrArray * CGenericTableDef::GetAllFieldsInformation()
{
if ( !IsOpen() ) return NULL;

CPtrArray  * pArray = new CPtrArray ;
for ( int i = 0; i < GetFieldCount(); i++ )
	{
	CDaoFieldInfo * p_fieldinfo = new CDaoFieldInfo;
	GetFieldInfo( i, *p_fieldinfo );
	pArray->Add( p_fieldinfo );
	}
return pArray;
}

void CGenericTableDef::DestroyAllFieldsInformation(CPtrArray *pArray)
{
if ( pArray != NULL )
	{
	while ( pArray->GetSize() > 0 )
		{
		delete ( CDaoFieldInfo * )pArray->GetAt(0);
		pArray->RemoveAt(0);
		}
	delete pArray;
	}
}

I personally use the CGenericRecordset objects and the CGenericTableDef objects with the class upon which the table is created. I will create three functions :
1 - A static CreateTable function, which will use the functions listed above to create the fields and the table
2 - A static GetTableName function, which give the default name the function should have
3 - A DoFieldExchange Function, which receives a pointer to a CGenericRecordset object positioned at the right location in the table, and a boolean indicating wheter a load or a save operation is being done. That way, database exchange is very similiar to serialization.

Indexes

Never happy, I added to my CGenericTableDef class support for indexes. Just like fields, Creating an index which already exists throws an execption, so I made a fix. Since there are less indexes in a table than fields, the support was simplifed, by simply adding the IsIndexInTable function, and it's two helpers, GetAllIndexInformation and DestroyAllIndexInformation. Here are the functions bodies :

CPtrArray * CGenericTableDef::GetAllIndexInformation()
{
if ( !IsOpen() ){return NULL;}

CPtrArray  * pArray = new CPtrArray ;

for ( int i = 0; i < GetIndexCount(); i++ )
	{
	CDaoIndexInfo * p_Indexinfo = new CDaoIndexInfo ;
	GetIndexInfo( i, *p_Indexinfo );
	pArray->Add( p_Indexinfo );
	}
return pArray;

}

void CGenericTableDef::DestroyAllIndexInformation(CPtrArray *pArray)
{
if ( pArray != NULL )
	{

	while ( pArray->GetSize() > 0 )
		{
		delete ( CDaoIndexInfo * )pArray->GetAt(0);
		pArray->RemoveAt(0);
		}
	delete pArray;
	}

}
bool CGenericTableDef::IsIndexInTable(CString Name)
{
CPtrArray * pArray = GetAllIndexInformation();

bool found = false;

for ( int i = 0; i < pArray->GetSize(); i++ )
	{
	CDaoIndexInfo * pIndex = ( CDaoIndexInfo * )pArray->GetAt(i) ;
	if ( Name == pIndex->m_strName )
		{
		found = true;
		break;
		}
	}

DestroyAllIndexInformation( pArray);
return found;
}

Relation

Access being a relational database, support for relations are needed. I will not surprise you if I tell you that creating a relation which already exists throw an exception. I therefore created a CDaoDatabase derived class : CGenericDatabase. It adds three relation functions :

static void DestroyAllRelationArray ( CPtrArray * pArray );
bool IsRelationInDatabase ( CString Name );
CPtrArray * GetAllRelationFromDatabase();

Here are the bodies :

CPtrArray * CGenericDatabase::GetAllRelationFromDatabase()
{
if ( !IsOpen() ){return NULL;}

CPtrArray * pArray = new CPtrArray;

for ( int i = 0; i < GetRelationCount(); i++ )
	{
	CDaoRelationInfo  *pRelationInfo = new CDaoRelationInfo;	
	GetRelationInfo( i, *pRelationInfo );
	pArray->Add(pRelationInfo );
	}
return pArray;
}

bool CGenericDatabase::IsRelationInDatabase(CString Name)
{

CPtrArray * pArray = GetAllRelationFromDatabase();

bool found = false;

for ( int i = 0; i < pArray->GetSize(); i++ )
	{
	CDaoRelationInfo  *  pRelation = ( CDaoRelationInfo  *)pArray->GetAt(i) ;
	if ( Name == pRelation->m_strName )
		{
		found = true;
		break;
		}
	}
DestroyAllRelationArray( pArray);
return found;

}

void CGenericDatabase::DestroyAllRelationArray(CPtrArray *pArray)
{
if ( pArray != NULL )
	{
	while ( pArray->GetSize() > 0 )
		{
		delete ( CDaoRelationInfo  * )pArray->GetAt(0);
		pArray->RemoveAt(0);
		}
	delete pArray;
	}
}

Tables

I didn't stop there : Creating a Table which already exists, well, you know what it does. So, I also created three functions :

bool IsTableInDatabase( CString Name );
	static void DestroyAllTableDefArray( CObArray * pArray );
	CObArray * GetAllTableDefFromDatabase( );

Please take note that the CObArray returned by the GetAllTableDefFromDatabase function contains CGenericTableDef, so it is possible to perform a lot of interesting things based on this function. Analysing an unknown Database is much easier when combining my Database and my Tabledef functions. Here are the bodies :

CObArray * CGenericDatabase::GetAllTableDefFromDatabase()
{
if ( !IsOpen() ){return NULL;}

CObArray * pArray = new CObArray;

for ( int i = 0; i < this->GetTableDefCount(); i++ )
	{
	CDaoTableDefInfo  TableInfo;	
	GetTableDefInfo( i, TableInfo );

	if ( TableInfo.m_lAttributes & dbSystemObject  || TableInfo.m_lAttributes & dbHiddenObject  )
		{
		// table is system or hidden, we ignore it
		}
	else
		{
		CGenericTableDef * pTableDef = new CGenericTableDef( this, TableInfo);
		pArray->Add(pTableDef);
		}
	}
return pArray;
}

void CGenericDatabase::DestroyAllTableDefArray(CObArray *pArray)
{
if ( pArray != NULL )
	{

	while ( pArray->GetSize() > 0 )
		{
		delete ( CGenericTableDef *  )pArray->GetAt(0);
		pArray->RemoveAt(0);
		}
	delete pArray;
	}

}

bool CGenericDatabase::IsTableInDatabase(CString Name)
{
CObArray * pArray = GetAllTableDefFromDatabase();

bool found = false;

for ( int i = 0; i < pArray->GetSize(); i++ )
	{
	CGenericTableDef *  pTable = ( CGenericTableDef * )pArray->GetAt(i) ;
	if ( Name == pTable->GetName() )
		{
		found = true;
		break;
		}
	}

DestroyAllTableDefArray( pArray);
return found;
}

Sample Class

Here is a sample class which shows the ease of using my Generic Tables. Another class, CLinkData, isn't shown here, implements the same functions.

class CData
{
static const char * GetTableName(){return "DataTable";}
static void CreateTable( CGenericDatabase * pDatabase );
void DoFieldExchange( CGenericRecordset * p_rs, bool load = true );
};

First, let's look at the CreateTable function ( below) . Notice that first, all the fields are added to the field array, then the table is created. Then, all indexes are created. Finally, All linked table are created, right before creating the relation to them

void CData::CreateTable(CGenericDatabase *pDatabase)
{
CGenericTableDef TableDef ( pDatabase);

CPtrArray * pArray = new CPtrArray;

// primary index

TableDef.AddFieldToFieldInfo(pArray,"Index", dbLong, true );

// foreign keys : The LinkData table 

TableDef.AddFieldToFieldInfo(pArray, "LinkDataNumber" , dbLong );

// local fields
TableDef.AddFieldToFieldInfo(pArray, "ShortField", dbShort );
TableDef.AddByteArrayFieldToFieldInfo(pArray, "ByteArrayField" );
TableDef.AddTextFieldToFieldInfo( pArray, "TextField", 40 );

TableDef.CreateTableFromFieldInfo( GetTableName(), pArray );
TableDef.DestroyAllFieldsInformation( pArray );

// Create Main Index

if ( !TableDef.IsIndexInTable("PrimaryIndex"))
	{
	
	CDaoIndexInfo IndexInfo;
	IndexInfo.m_strName = "PrimaryIndex";
	IndexInfo.m_nFields = 1;
	CDaoIndexFieldInfo FieldInfo[1];
	FieldInfo[0].m_strName = "Index";
	IndexInfo.m_pFieldInfos = FieldInfo;
	IndexInfo.m_bUnique = true;
	IndexInfo.m_bPrimary = true;
	IndexInfo.m_bClustered = false;
	IndexInfo.m_bRequired = true;
	TableDef.CreateIndex(IndexInfo);
	}

	CLinkData::CreateTable( pDatabase );

	// Create Relation between Data and Link Data
if ( !pDatabase->IsRelationInDatabase( "RelationDataToLinkData" ) )
	{
	CDaoRelationInfo Relation;
	Relation.m_strTable			= CLinkData::GetTableName();
	Relation.m_strForeignTable	= GetTableName();
	Relation.m_lAttributes = dbRelationUpdateCascade | dbRelationDeleteCascade;
	Relation.m_nFields = 3;
	Relation.m_strName = "RelationDataToLinkData";
				
	CDaoRelationFieldInfo Fields[1] ;
	Fields[0].m_strName			= "Index";
	Fields[0].m_strForeignName  = "LinkDataNumber";
		
	Relation.m_pFieldInfos = Fields;
		
	pDatabase->CreateRelation( Relation );
	}
	
TableDef.Close();
}

The DoFieldExchange Function could therefore look like that : ( Of course, the data would get load and stored in the actual members, instead of temporary variables ) Notice the similiarity with the Serialize Function. I must admit the code isn't as clear the the FieldExchange solution from Microsoft, but this works well for me.


void CData::DoFieldExchange(CGenericRecordset *p_rs, bool load)
if ( load )
	{
	
	short Number = p_rs->GetShort ( "ShortField");
	CString TextField =  p_rs->GetCString("TextField");
	
	CByteArray * pArray = p_rs->GetByteArray( "ByteArrayField" );
	
	if ( pArray != NULL  )
		{
		BYTE * pData = new BYTE[ pArray->GetSize()]
		memcpy( pData, pArray->GetData(), pArray->GetSize()  );
		delete pArray;
		delete[] pData;
		}
	}
else
	{
	CString string;
	p_rs->SetCString("TextField",string);
	short Number;
	p_rs->SetShort( "ShortField",Number );
	
	
	BYTE data[200];
	CByteArray * pArray = new CByteArray;
		
	pArray->SetSize( 200 );
		
	memcpy( pArray->GetData(), data, 200 );

	p_rs->SetByteArray( "ByteArrayField" ,pArray);	  
	delete pArray;
	
	}

Notice also that the Index and the Foreign Keys are not read or written in the DoFieldExchangeFunction. They are managed by other functions, like for example, an assignment function which would assign CData objects to CLinkData objects ( see below ). With the CGenericRecordset class, it is easy to do.

void AssignCData( CGenericRecordset * p_DataRs, CGenericRecordset * p_LinkDataRs )
{
pDataRs->SetLong( "LinkDataNumber", p_LinkDataRs->GetLong("Index"));
}

Is that it ?

No, I also added a function for opening a table from a CGenericRecordset function, which will create the table and all it's fields if they don't exists : My database therefore seem to "regenerate" themselves. I also derived from my Database class, and create a non-generic database class which handled all the details and inner workings of my database.

I could have decided to cache in each object the lists, like the FieldInfo list, but I didn't, because I wanted, for now, simplicity over performance. Improvements are easy to make to these classes.

As a final note, remember that the goals of these classes is to reduce the quantity of code to write in each of my individual data classes, to ease the transfer from Serialization to Field Exchange. It would be possible to write a CArchive-like recordset, which would serialize the objects to the database. The column number would simply be incremented after every write or read, and insertion-extraction operators would be possible.
A new macro DECLARE_FIELD_EXCHANGE and IMPLEMENT_FIELD_EXCHANGE could be created, that would handle all the basics and make it even more simple.

Where is the source code ?

I am truly sorry, but I cannot publish for the moment the complete source code for the three classes. I believe I published enough informations to let you recreate the classes. If you need help to do so, you can write-me.

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

Share

About the Author

Martin-Pierre Frenette
Web Developer
Canada Canada
No Biography provided

Comments and Discussions

 
GeneralE-mail contact Pinmemberkewl4-Jan-01 7:47 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140821.2 | Last Updated 3 Dec 1999
Article Copyright 1999 by Martin-Pierre Frenette
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid