The Anatomy of a CE Database Record

In this lesson, you’ll finish examining the RemoteDBScan example program, devising a strategy by which to interpret and display the retrieved records. The first step is to get a handle on exactly what a CE database record is and how it is constructed.

The Anatomy of a CE Database Record

A CE database record is a compact and self-documenting block of variable size data, comprised of an array of CEPROPVAL structures. Here is the typedef for CEPROPVAL:

typedef struct _CEPROPVAL {
        CEPROPID propid;    //LOWORD is CE value type
                            //HIWORD is property ID
        WORD wLenData;      //unused
        WORD wFlags;        //flags: CEDB_PROPNOTFOUND returned
                            //if prop isn't found
                            //passing in CEDB_PROPDELETE causes
                            //property to be deleted from record
        CEVALUNION val;     //Union includes members for all CE
                            //database types
        } CEPROPVAL;

There are a couple of important things to note about the way CE treats its database records. First, a CE database record may be “sparse.” Put another way, a record may or may not contain a value for each property. Also, a given CE database may contain only one record type. There is no explicit support for hierarchically structured data. Having said this, it is important to note that the member of the CEPROPVAL that stores record data is a union, one of whose members is of type BLOB. This means that should you need to create hierarchical data relationships, you can implement your own mechanisms for doing so, inside the record’s data.

Interpreting CEPROPVAL Structures

Here are the steps required to interpret an array of CEPROPVAL structures to create and display a relational style “record” from the property value array:

  • Set up an iteration of the CEPROPVAL array, using the count of record properties returned by CeReadRecordProps().
  • For each CEPROPVAL, retrieve the property index by taking the LOWORD of the propid and use it to set the column index for the property.
  • Next, determine the CE value type for the data stored in the record by taking the HIWORD of the propid. Use the CE data type to select the correct member of the CEVALUNION and proper type of formatting for the data.

In CRecordListDialog::PublishRecord(), you discriminate the property ID and data type of each property. For the sake of brevity, you insert them serially in a list box rather than laying them out as rows in a separate list control.

At the top the list of each record’s set of properties, you insert the record OID, the number of properties returned for this record, and the total size of the record in bytes. These data were passed to PublishRecord() as parameters. They were reported by CeReadRecordProps() in the caller.

void CRecordListDialog::PublishRecord( CEOID oid, WORD wProps,
                                       PCEPROPVAL pRecord,
                                       DWORD dwRecSize)
{
    char szInsertString[124];

    sprintf(szInsertString, "%s: 0x%x", "Record CEOID", oid );
    m_RecordListCtrl.InsertString( -1, szInsertString );

    sprintf(szInsertString, "%s: %i", "Properties Returned", wProps );
    m_RecordListCtrl.InsertString( -1, szInsertString );

    sprintf(szInsertString, "%s: %i", "Record Size", dwRecSize );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    m_RecordListCtrl.InsertString( -1, "" );

Here, you set up an iteration of the array of CEPROPVAL structures. You use the HIWORD macro to recover the property ID from the propid member. The property ID is then formatted and inserted in the list box.

    WORD iPropId;
    int iPropDataType;

    for( int i = 0; i < wProps; i++ )
    {
        iPropId = HIWORD(pRecord[i].propid );
        sprintf(szInsertString, "%s: %i", "Property Id", iPropId );
        m_RecordListCtrl.InsertString( -1, szInsertString );

        iPropDataType = LOWORD(pRecord[i].propid );

Now, you have to retrieve and format the record’s value data. The way in which you do this depends on the data’s type. Here are the type constants for the data in a CE database record, along with the name of the structure member in the CEVALUNION in which that type is stored.

Table 1: CE Database Types and Corresponding CEVALUNION Members

CE Database Value Type Constant _CEVALUNION Member Type And Name
CEVT_I2 short iVal;
CEVT_UI2 USHORT uiVal;
CEVT_I4 long lVal;
CEVT_UI4 ULONG ulVal;
CEVT_FILETIME FILETIME filetime;
CEVT_LPWSTR LPWSTR lpwstr;
CEVT_BLOB CEBLOB blob;

You declare a variable to handle translation of FILETIME data outside the switch, and then use the CE value type as a switch test. Notice that with the integer and file time types, all of the record’s data is stored within the CEPROPVAL structure.

CTime tFileTime;
CString csFileTime;
switch( iPropDataType )
{
    //ce type
case CEVT_I2:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_I2" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%i", pRecord[i].val.iVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    break;
case CEVT_UI2:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_UI2" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%ui", pRecord[i].val.uiVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    break;
case CEVT_I4:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_I4" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%li", pRecord[i].val.lVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    break;
case CEVT_UI4:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_UI4" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%uli", pRecord[i].val.ulVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    break;

More by Author

Must Read