I've worked hard to port my brain from Active X, Visual Basic, and MS Access to VCL, Borland C++Builder, and Interbase with a sprinkling of Paradox. The transition hasn't been easy, but it's been rewarding. In this article, I'll address some of the questions I had to answer in the process. If you have a nagging database question, send it my way at JimBailey@worldnet.att.net. I'll answer as many questions as I can and include some of them in this column.
It was easier to grasp Borland's principles once I realized Borland is not Microsoft. Actually, after working with Delphi and C++Builder for about a year, it's shocking to see how narrow my perspective was. Borland offers much greater flexibility--along with a different set of problems and headaches. Such is life, I guess.
C++Builder gains database access via the Borland Database Engine (BDE). Visual Basic uses Jet. The BDE uses an alias to identify a database, while Jet uses the exact location, such as C:\Data\MyData.MDB.
Obviously, the BDE must know a database's exact location. C++Builder programs specify an alias name, such as MyDB, when establishing a database link. If the alias is properly configured, the BDE will establish the connection. This technique is handy, because you can move the database to a different directory, a different drive, or different PC without making a single program change. When such changes are registered in the BDE Configuration program, they cascade to every C++Builder program using the affected alias.
Sometimes, however, it's desirable to create an alias at runtime. My first learning application was a single-user food catalog, using Paradox. If I used a pre-configured alias, the database had to be installed in the same directory on every end user's PC. I don't think that's very friendly. I wanted to install the database in a directory relative to the execution file. For example, if my application file was installed in C:\Program Files\Food Catalog\MyProg.EXE, I wanted the database to be in C:\Program Files\Food Catalog\DB. This technique is a holdover from my VB experience, but I still like the idea. To use it, though, I had to create an alias at runtime, because I give the users complete control over where the application is to be installed on their system. The TDatabase object provides that functionality.
To add a TDatabase object to a new project, click the Data Access tab on the VCL control palette and locate the Database component. Double-click on the component to add it to a form.
When a BDE data link is properly established, live data appears in data-aware components, such as grids and text boxes, during design time. It's extremely helpful to see whether the link is working without having to compile, link, and run the application. Configuring the link is easy.
Press [F11] to open the Object Inspector (analogous to the property box in VB and Access). Set the Name property to dbMyDb, then double-click the TDatabase object to view its configuration dialog box, shown in Figure A.
Figure A: Configure your TDatabase object in this dialog box.
Now, type MyData in the Name text box. Doing so sets the DatabaseName property and establishes the BDE alias name.
Set the driver name to Standard, and click the Defaults button. The following text appears in the Parameter Overrides text box, as shown in Figure A:
PATH= DEFAULT DRIVER=PARADOX ENABLE BCD=FALSEAdd the design-time physical location of your database following PATH=. Click the OK button, then double-click the Connected property in the Object Inspector. The database is now configured and ready to use. I use the code shown in Listing A to set the location of the database at runtime.
Listing A: Setting the database location at runtime
//get the full path and name of current EXE
String Path(ParamStr(0));
//find the last back slash
int pos = Path.LastDelimiter("\\");
//Strip file name, leaving only the path
Path.SetLength(pos);
//make sure db object is not connected
dbMyDb->Connected = false;
//clear current parameters
dbMyDb->Params->Clear();
//set properties
dbMyDb->Params->Add "PATH=" + Path + "DB");
dbMyDb->Params->Add("DEFAULT DRIVER=PARADOX");
dbMyDb->Params->Add("ENABLE BCD=FALSE");
//reconnect
dbMyDb->Connected = true;
The BDE alias provides maximum flexibility. Every table or query component in
the application is linked to the database via the TDatabase alias, without
concern for the physical location of the file(s). End users have complete
control over the application's installation location, and everyone is happy.
The inability to retrieve new autoincrement (AI) values seems like a BDE deficiency to me. I almost quit using C++Builder over this issue. However, after some steady work and considerable input from other users on the Borland database newsgroup, I found acceptable solutions.
This condition doesn't exist for TTable or TQuery objects when CachedUpdates is false. Most of my work is done with TQuery objects--I prefer the flexibility they offer in sorting and filtering. I like using SQL. But for client/server applications, the TTable objects can cause problems. TQuery objects with cached updates provide more power, control, and flexibility.
There are several solutions to this problem. In a single-user environment, the problem isn't much of an issue. The new AI value for an inserted record can be determined by opening a second dataset on the same table. If the new dataset is sorted on the AI field, the new record will be the last record, and the AI value for that record will be the largest value in the table.
I recommend a second dataset, because it's almost never practical to sort data for users by an AI value. As a result, the new record probably won't be the last record in the dataset. My preference for the second dataset is a TQuery object with the following SQL property setting:
SELECT MAX(ID) FROM "Company.DB"This setting returns the highest value in the table's ID field. When it's an AI field, it will always return the last record. To activate the value in the user's dataset, execute the code in Listing B. It uses a TQuery object named qLastID to locate the value of the last ID field. It toggles the current dataset's Active property and relocates the last record added.
Listing B: Locating the last value added
//SELECT MAX(ID) FROM "Company.DB"
qLastID->Active = true;
//a record was found if true
if(!(qLastID->Bof && qLastID->Eof))
//persistent field value.
{ LastID = qLastIDMAXOFID->AsInteger;
//toggle Active property to refresh DataSet.
DataSet->Active = false;
//Open the DataSet with fresh data.
DataSet->Active = true;
TLocateOptions SearchOptions;
SearchOptions >> loPartialKey >>
loCaseInsensitive;
//locate the new record.
DataSet->Locate("ID",
LastID,SearchOptions);
}
This technique should work for single-user systems. In a multi-user environment, two users could conceivably add new records simultaneously. In such a case, this technique would create a conflict and might not locate the proper record. In Paradox databases, this situation is thought to contribute to the creation of the dreaded Index out of date condition.
The multi-user environment presents a considerable challenge for the Microsoft Access mind-set. I haven't been able to use AI values in C++Builder multi-user applications. Instead, I use a long integer field for the primary key, deriving a unique value for each record.
To derive a unique value in Paradox and other local databases systems, I create a table with a single column for each table requiring AI support. This table will never have more than one record. The value in each column represents the last unique value used in a particular table. I use either the BeforePost or OnUpdateData event to calculate a new value, then plug it into the primary key field. Of course, the unique value table must be updated with the new value. This works nicely, and the value of a new record's primary key is never hidden.
In Interbase, I use a generator, which is a procedure that runs on the Interbase server and generates sequential values. The value of the generator is read from a stored procedure in the BeforePost or OnUpdateData event.
I didn't care for either of these solutions in the beginning. However, after I developed some generic code to handle the related tasks, I began to see their power and flexibility. Using this technique, I can easily reset the value in counter field. I can begin numbering at 100 instead of 1. I can even increment by a value other than one. This technique gives me more control over my application--that's something I could get used to.
This problem was another huge stumbling block for me. In Access, combo boxes have a polite little type-ahead feature. They support multiple columns, and they work in the detail section of a grid-like Access form.
In C++Builder, the DBLookUpComboBox offers very little to the Access mind-set. Typing b selects the first item in the list that starts with b. Press b again to locate the second item starting with b. This is a standard Windows interface, but it isn't appealing. What if the list has 500 items that start with b? It's an annoyance to the user to endure interminable scrolling or the pounding of the b key to complete a selection. Most users today won't tolerate such products.
To make matters worse, you can't use the component in the DBGrid or DBComponentGrid. Its only redeeming quality is that it can show one value and save another. It's an important capability. For example, in a database that lists company contacts, it's common to list all the companies in one table and all the contacts in another. The tables might be structured as shown in Table A.
Table A: Company and Contact table structures.
| Table | Field | Type |
|---|---|---|
| Company | CoID CoName City | AutoIncrement (Primary Key) CHAR(32) CHAR(24) |
| Contact | ContactID CoID LastName FirstName | AutoIncrement (Primary Key) Long Integer CHAR(14) CHAR(14) |
The CoID (Company ID) field in the Contact table is a foreign key (it corresponds to an existing value in another table) referencing one of the Company table's primary key values. However, you shouldn't force users to enter the company number--this number exists for the convenience of the computer. People, on the other hand, think in terms of company names.
If you plan to develop relational database applications in C++Builder, it might be worth your while to visit Softouch Development's Web page
and download the fully functional demo. Let me know what you think.