Adding Records to an Open Database in Windows CE

Well, after all, what would a database be without some data? It's time to think about adding records. Before we dig into the specifics of this task, there are a few clarifications we need to make about the relationship between CE database properties, CE database records, and the overall structure of the CE database. (In the discussion that follows, I use the terms "database property" and "field" interchangeably; they are equivalent.)

First, recall I said in the opening paragraphs of the last installment that CE records accommodate any kind of data. This is indeed the case, although perhaps a bit disingenuous. Here's why.

Basically, a CE database property (field) consists of a CEPROPVAL structure, which provides descriptor information, and optionally of some application allocated space which contains data too large to be directly stored in the CEPROPVAL structure. To better understand this, let's examine the typedef of the CEPROPVAL structure:

When we refer to a specific database property, invariably we do so using its CEPROPID, the first member of the CEPROPVAL structure. The CEPROPID is itself another typedef, which looks like this in the Windows CE header files:

typedef DWORD CEPROPID;

The CEPROPID provides two important pieces of information about a field: the ordinal of the field in the record, and the data type of the field. First, let's take stock of the possible types for a CE database property.

CE Data Type Constants and Their Meanings

Property Type Constant

Data Stored in the Property

CEVT_BLOB

A CEBLOB structure

CEVT_FILETIME

A FILETIME structure

CEVT_I2

A 16-bit signed integer

CEVT_I4

A 32-bit signed integer

CEVT_LPWSTR

A null-terminated string

CEVT_UI2

A 16-bit unsigned integer

CEVT_UI4

A 32-bit unsigned integer

You've probably noticed that this is a pretty short list—there are no floating point types, no native date or time types. Basically, the list above is a collection of the things that can either be stored in 32 bits or referenced through a long pointer. What ever goes into a CE database record must be made to fit into one of these types. The relationship between the type of the data and the actual storage of the data will be easier to picture if we skip ahead in the CEPROPVAL structure's members and examine the CEVALUNION member next. Here's the typedef of CEVALUNION:

A union, if you aren't familiar with the idiom, is a single block of space that is large enough to accommodate the storage of its largest member. It can hold only one value, and thus represent only one of its members at any given time. Unions are a useful construct in situations where a variable might assume the value of any of several different types of data. In the case of a CEPROPVAL, its union member allows us to store seven distinct types of data in a generic database property structure.

Here's a key relationship to visualize: A CEPROPID gives the data type of a specific property. A CEVALUNION stores either the actual data value, or a pointer to the data value. Both are members of the CEPROPVAL structure, which defines the property's attributes and (directly or indirectly) its value.

Now, let's revisit the typedef of the CEPROPID:

typedef DWORD CEPROPID;

Recall that we said that a CEPROPID stores two pieces of information: the data type, and the property's ordinal position in the record. Here's how we create a CEPROPID for a short integer that's the third property in the record:

MAKELONG(CEVT_I2, 2)

The MAKELONG macro places the CE data type constant in the LOWORD, and the zero-based index of the property in the HIWORD of the CEPROPID. By storing the property's index, we are able to implement "sparse records." In other words, if you have a record type that has 10 properties, and in a particular case you only have data for two of them, them you add the record using an array of only two CEPROPVAL structures—one for each field for which you have data.

The wDataLen and wFlags fields of the CEPROPVAL structure are unused, and by convention always set to 0.

Summing up, here is a litany of the relationships among the structures that constitute a CE database:

A database is a group of records owned by a specific database object, and the database object is identified by a unique CEOID.

A given database record consists of an array of CEPROPVAL structures, plus application allocated memory for data that is too large to be stored in the members of the CEVALUNION.

A record needs only store CEVALPROP structures for fields that contain data.

The CEPROPID gives the CE data type of the property, combined with the zero-based index of the property with respect to the complete set of properties in a given record.

The CEVALUNION holds either the value of the property, or a pointer to the space that holds the value.