http://www.developer.com/db/article.php/3327171/A-CE-Database-Primer.htm
In the most basic sense, there are really only two things you can do with a computer: store data and manipulate data. Even executable code really amounts to a very specialized kind of data, stored in a canonical database. Given this distillation of computing reality, it's not surprising that a sleek, spare architecture like CE would build database management into the system. It's just so sensible. If you are porting a data management application to CE from Win32, in all likelihood you've given the issues around database APIs some fairly serious thought. And well you should. You'll probably also have considered finding a way to avoid using the built-in functions and instead using files or "rolling your own" more familiar type of database API. If there is one bit of porting advice I would emphatically offer, it's to make use of the CE database API. While it is certainly very different in character from contemporary relational database tools, it has some really overwhelming strengths and advantages. If you take the leap of faith that brings you to the conclusion that the CE database API is the way to go, you probably have some fairly large-scale code surgery in store. If you've been circumspect about segregating data access code in libraries or sets of source files, the work of porting begins with these. If your circumstances are less ideal, you'll need to locate data management operations dispersed throughout your code. The beauty of CE's database API is that, although it is spare, it is very flexible. You only need to know a handful of APIs, and the key to using them effectively is in understanding how to modify their behavior by using various flags and argument values. We are going to take a slow, careful tour of database basics in this chapter, by means of two example programs. The first one, the BirthdayReminder example, will demonstrate how to create, open, and add records to a database. The second example, BetterBirthdays, will expand on these topics, by showing how to open a database with a sort order active, and detect and interpret error information. In addition, BetterBirthdays shows how to position and search in a database, how to update existing records, and how to iterate records based on a sort order. On with the show! The CE database API is fairly simple to use, but you need to take sort of a canonical view. You have to do database operations in a specific order, and many operations depend on the manner in which prior operations were handled. Here is a basic chronology: We'll use the BirthdayReminder example to examine each of these actions in more depth. Most of the business of managing the Birthdays database is handled in the BirthdayDlgProc() function. We create the database in response to the WM_INITDIALOG message, with a call to the CeCreateDatabase() function. The parameters to CeCreateDatabase(), in the order shown, are the database name as a Unicode string, a DWORD value that identifies a general, application specific database type, the number of active sort orders for this database, and a pointer to the array of SORTORDERSPEC structures for the database. The way in which you initially create the database has great influence on the operations you'll be able to perform subsequently. Let's look at each of the parameters in a bit more depth. A couple of points remain before we move on to opening the database. Notice that CeCreateDatabase() returns a value of type CEOID. A CEOID is a unique object identifier that specifies an object in the Windows CE Object Store. Unlike a handle, the CEOID for the database is persistent. It can be used to recover information about the database even if the database isn't open. Unlike file functions and the like, CE database functions often don't return values that directly encode status information. To find out whether database calls are successful, you'll often need to use GetLastError(), as we did following the call to CeCreateDatabase(). Notice that in this case we test for two conditions that might cause failure to create the database. If the database already exists, GetLastError() will report ERROR_DUP_NAME. This isn't a problem for us, as it won't prevent us from opening the database by name. If we get a return of ERROR_DISK_FULL, we notify the user, clean up, and bail out. If we just wanted to unconditionally test for success or failure, we could do so like this: Strangely enough, a return code of ERROR_SUCCESS signifies that everything is fine. If we get this far, the next step is to open the database we just created, with a call to CeOpenDatabase(): The parameters to CeOpenDatabase(), in the order shown, are the address of a variable of CEOID type that will contain the database's CEOID on successful return; the name of the database as a Unicode string; a null placeholder for the property ID of a primary key sort order field; the flag CEDB_AUTOINCREMENT, which means that the current record location is incremented on each read operation, and the handle to the window that will receive notifications if another application modifies the database after we open it. The fashion in which you open the database has a great deal of influence on what data access operations you are able to perform later. Also, the meanings of the parameters to this function depend on one another in subtle ways. Let's explore them further: First, notice that, in this case, we set the globalCEOID parameter equal zero before calling the function. This indicates that we want the CEOID returned in this value. If we had set globalCEOID equal to a valid CEOID for the database, we could open it without using (or even knowing) its literal name. In the example code, we use the second parameter, a Unicode literal, to open the database by its literal name. If we had chosen to open it using a CEOID value, we'd set the database name parameter to NULL. The next parameter, the property ID of a primary sort key, is set to NULL here. The most significant effect of this is that with no active sort key, we won't be able to locate a record by subjecting one of its properties (fields) to a specific search criterion. (For example, we can't look for a record with a field greater than, less than, or equal to a reference value). However, even without a sort order active, you can iteratively read records, write records, and position (seek) in the database by absolute location (For example, you can set the "current record" pointer at the third record from the beginning of the database.) or position using a record's CEOID. The open mode flag parameter is set to CEDB_AUTOINCREMENT in this case. This flag causes the "current record" pointer to move ahead one record after each read operation. If you set this parameter equal to zero, the record pointer only moves when you explicitly set it to a new location by seeking. The final parameter, in this case the handle to the BirthdayDlg, identifies the window that is to be notified if the database is modified by another application. Our example includes no code to handle such a notification, and for application-specific databases, it probably isn't necessary to worry much about this. However, if you are using a native CE database (the Contacts database, for example) you'll need to be ready to handle such notifications. A few final observations on opening CE databases are in order. First, notice that there is no explicit provision for open "permissions." Once the database is opened, you may read, write, and delete at will. Second, how you open the database makes a lot of difference in terms of what kind of searching and positioning operations you are subsequently able to perform. For these reasons, you'll probably find it both more convenient and more robust to open the database in the way that is most conducive to your access needs for a given circumstance, and then close and reopen for different types of operations. In the example above, we opened the database in the way that makes it easiest to sequentially read records. In the next installment, we'll look into the mechanics of CE database access. Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, and entry time data validation. In addition to writing for Developer.com, she has written several books, including Making Win 32 Applications Mobile. # # #
A CE Database Primer
March 17, 2004
Getting Started
Porting Tip: Here's how to find data management calls dispersed in the body of code you are porting.
Integrating Database Functionality, CE Style
Overview of The Steps
Creating a Database
case WM_INITDIALOG: //Init the dialog controls InitDlgCtrls( hDlg ); //Create data store if( ceoidDB = CeCreateDatabase(TEXT("Birthdays"), 0x2468, 0, NULL)) { return TRUE; }
//either the disk is full or the db existsrc = GetLastError();//this case is okif( rc == ERROR_DUP_NAME ) { break; }//uh-oh...if( rc == ERROR_DISK_FULL ){ iCaption = IDS_CANT_CREATE_DB; iTitle = IDS_NO_DATABASE; { goto FAIL; }}
//sample code fragmentif( rc != ERROR_SUCCESS ) { //rock on... }
globalCEOID = 0;globalHDB = CeOpenDatabase (&globalCEOID, TEXT("Birthdays"), 0, CEDB_AUTOINCREMENT, hDlg);if( globalHDB == INVALID_HANDLE_VALUE ) {goto FAIL; }Porting Tip: Open the database in the way that most closely supports immediate access needs; then close and reopen as access requirements change.
Looking Ahead
About the Author