Flexible Access, Powerful Subtleties

When it comes to data management, real power has more to do with precision of access than just about anything else. We need to be able to find specific records or groups of records, modify them, and iterate them. In the BetterBirthdays example, we tackle these issues. We don’t introduce a lot of new APIs for these jobs. Rather, we mostly use the ones you’ve already seen in slightly different ways.

The BetterBirthdays Example Running on an H/PC:

Using Sort Orders

We refine our use of the database functionality in the BetterBirthdays program. The first and most noticeable change is that this time the Birthdays database that has a sorted field name field. We specify this attribute at the time the database is created, and we create the database in BirthdayDlgProc() in response to the WM_INITDIALOG message.

ceSortProp = MAKELONG(CEVT_LPWSTR, 0);
   switch (message)
   {
      case WM_INITDIALOG:
         //Init the dialog controls
            InitDlgCtrls( hDlg );

         //sort records ascending on name,
         //without respect to case
            sosAscendingNameSort.propid =
               MAKELONG(CEVT_LPWSTR, 0);
            sosAscendingNameSort.dwFlags =
               CEDB_SORT_CASEINSENSITIVE;

In order to create a database in which the properties behave like “key” fields in a tabular database, we provide information at database creation time that supports traversing the database records base on the values of specific properties ( or fields ). This information is encoded in a SORTORDERSPEC structure. The typedef for SORTORDERSPEC is shown below:

typedef struct _SORTORDERSPEC {
                                  CEPROPID propid;
                                  DWORD dwFlags;
                              } SORTORDERSPEC;

The propid member is the now-familiar CEPROPID. The value of the dwFlags member defines the sorting mechanism that will operate on this property.

Table 1: Sort Order Flags and Their Meanings

Sorting Mechanism Flag Meaning
CEDB_SORT_DESCENDING The sort is done in descending order. By default, the sort is done in ascending order.
CEDB_SORT_CASEINSENSITIVE The sort operation is case insensitive. This value is valid only for strings.
CEDB_SORT_UNKNOWNFIRST Records that do not contain this property are placed before all the other records. By default, such records are placed after all other records.
CEDB_SORT_GENERICORDER The default sort order—ascending order, case sensitive—is used. Records that do not contain the sort property are placed at the end of the list.

A database can have a maximum of four fields with active sort orders. The call to CeCreateDatabase() for the new, improved Birthdays database looks like this:

//Create data store and save the CEOID
   globalCEOID = CeCreateDatabase(TEXT("Birthdays"),
                 0x2468, 1, >sosAscendingNameSort);

A successful call will create a Birthdays database in which we have one sorted field. The names are sorted in ascending alphabetical order, without sensitivity to case.

//if the db exists, CeCreateDatabase will fail
rc = GetLastError();
if( rc == ERROR_DUP_NAME )
{
   //just get the oid, then close
   globalCEOID = 0;
   globalHDB = CeOpenDatabase (&globalCEOID, TEXT("Birthdays"),
      0, 0, NULL);
   CloseHandle( globalHDB );
   globalHDB = 0;

If the database already exists when we try to create it, CeCreateDatabase will fail. This case becomes important in the BetterBirthdays example because we created a database of this same name in the BirthdayReminder example last month. We can’t use that database for this project, however, because it was created without sort orders. To get the sort of database functionality we need in this case, we’ll have to delete the old database if it exists and then create a new one. Here’s the prototype for the API we’ll use:

BOOL  CeDeleteDatabase(CEOID oidDatabase);

CeDeleteDatabase() takes a single parameter, the database CEOID. In part, this is because you can’t delete an open database. If we get an error status of ERROR_DUP_NAME, we open the existing database by name in order to get its CEOID. Then, we close the database to make it deleteable, and finally, delete it by using the CEOID.

Unlike most desktop databases, CE’s native database is somewhat “undefended.” By this I mean that if certain somewhat predictable exceptions or errors occur (disk full, passing bad parameters to database APIs, and so on) you can’t assume that the system will clean up and carry on. You must be rigorous about testing return values of any database function calls that could fail.

}
   //uh-oh...
   if( (rc == ERROR_DISK_FULL )
                     ||
       ( rc == ERROR_INVALID_PARAMETER ))
   {

      iCaption = IDS_CANT_CREATE_DB;
      iTitle = IDS_NO_DATABASE;
      { goto FAIL; }
   }

Much of the functionality of the BetterBirthdays example hinges on the ability to find a specific record. For this reason, we’ll explore the FindBirthday() function next:

BOOL FindBirthday(HWND hDlg)
{

   HWND hwndWho, hwndChange, hwndDelete;
   TCHAR* lpwszWho;
   int iTextLen, rc;
   DWORD dwIndex =0;
   CEPROPVAL propBirthday;

   //Find out who from the edit control
   hwndWho  = GetDlgItem(hDlg,IDC_WHO);
   iTextLen = SendMessage( hwndWho, WM_GETTEXTLENGTH, 0, 0);

The FindBirthday() function is called when the user clicks the “Find” dialog control. The first few lines of the function get the name to find from the edit control, detecting an empty string if there was no input.

//require user input
if( !iTextLen )
   {return FALSE;}

//allocate for wide character count plus terminal null
lpwszWho = (TCHAR* )LocalAlloc(LPTR, (iTextLen + 1) *
                               sizeof(WCHAR) );

//get the input string
SendMessage( hwndWho, WM_GETTEXT, (iTextLen + 1),
             (LPARAM)(LPSTR)lpwszWho);

Next, we open the database, specifying an active sort order. The parameters to CeOpenDatabase(), in the order shown, are the CEOID of the database to open, a NULL placeholder for the pointer to the database name, the CEPROPID of the field on which to activate sorting, an open flag value of zero, and a NULL placeholder for the handle of the window to receive database change notification messages.

//open the database with an active sort order
globalHDB = CeOpenDatabase (&globalCEOID, NULL,
                            MAKELONG(CEVT_LPWSTR, 0),
                            0, NULL);
rc = GetLastError();

if( rc == ERROR_INVALID_PARAMETER )
   { goto FAIL; }

if( globalHDB == INVALID_HANDLE_VALUE )
    {goto FAIL; }


//seek to this rec
propBirthday.propid     = MAKELONG(CEVT_LPWSTR, 0) ;
propBirthday.val.lpwstr = lpwszWho;
propBirthday.wLenData   = 0;
propBirthday.wFlags     = 0;

Finding the record that contains the name specified by the user is a three-step process. First, we initialize a CEPROPVAL structure with the information that specifies what field we plan to search. In this case, we set the PROPID member to the name property, and we set the val.lpwstr member to the address of the string for which we are going to search.

globalRecOID =
   CeSeekDatabase (globalHDB,
                   CEDB_SEEK_BEGINNING, 0, &dwIndex);

Next, we initialize the position in the database by seeking to the first record. The paramaeters to CeSeekDatabase(), in the order shown, are the handle to the database, a seek type flag, a NULL placeholder for the value to seek, and the address of the variable that will contain the record index on a successful return.

    globalRecOID =
        CeSeekDatabase (globalHDB,
                        CEDB_SEEK_VALUEFIRSTEQUAL,
                        (DWORD)&propBirthday, &dwIndex);

Finally, we perform a search for the value specified by the user. Put another way, we seek to a record containing a property matched by the search criteria.

This time, the parameters to CeSeekDatabase() are the database handle, a seek flag that initiates a search for the first occurrence of a record containing an exact match of the search criteria, the address of a CEVALPROP structure we initialized with the search data, and the address of the variable that contains the index of the found record on successful return. Notice that we save the returned CEOID of the matching record, so that in the future we can manipulate the record directly.

Looking Ahead

In the next look at the BetterBirthdays example, we’ll examine techniques for deleting, modifying, and iterating records.

# # #

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read