You are on page 1of 25

27/09/2014

Top 10 Tricks for Delphi and C++Builder VCL Database De…

EMBAR C ADER O HO ME

LO C ATIO N | KO R EAN | LO G O N

Watch, Follow, &
Connect with Us
Share This

COMMUNITIES

ARTICLES

BLOGS

RESOURCES

DOWNLOADS

HELP

EDN » Delphi » Database

Top 10 Tricks for Delphi and C++Builder VCL Database
Developers by Cary Jensen

RATING

By: David Intersimone
Abstract: This article provides an overview of a number of important techniques in general Delphi and
C++Builder VCL database development.

Top 10 Tricks for Delphi and C++Builder VCL Database Developers
by Cary Jensen
Jensen Data Systems, Inc.
Note: The following paper was presented at the 1999 Inprise/Borland Conference in Philadelphia Pennsylvania.
Click on the source code link to download the examples used in this paper.
The Visual Component Library (VCL) provides a number of components that greatly simplify database
development. These include the components on the Data Access page of the component palette, as well as
those on the Data Controls page. Additional database development components appear on the Midas page
of the component palette, but these are generally associated with multi-tier development, which is a special
case, and are largely ignored in this paper.

Download Trial
Buy Now
Download Delphi XE7
now!
Get Free Trial
Special Offer

This paper provides you with an overview of a number of important techniques in general VCL database
development. If you are currently developing database applications that use the VCL you will not doubt be
familiar with some of these techniques. Therefore, the explicit goal of this paper is to provide you with a list
of techniques that should be familiar to all active VCL database developers, assuring your awareness of
these operations.
It should be noted in advance that some of the technique described here are appropriate for both
client/server applications as well as those that are not (including both stand alone applications as well as
those that run on a network).

The Importance of Preparing Parameterized Queries and StoredProcs
Client/server applications, those where the data is stored and for the most part manipulated on a remote
database server, make extensive use of queries and stored procedures. When working with data

http://edn.embarcadero.com/kr/article/20563

1/25

27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De…
manipulation language (DML) SQL statements, it is generally necessary to prepare the query or stored
procedure prior to its execution. Likewise, since the act of preparing a query or stored procedure involves
the allocation of resources on the server, it is necessary to unprepare the query or stored procedure when
done.
This preparation and unpreparation can be performed through explict calls to TQuery and TStoredProc
component​s Prepare and UnPrepare methods, or they can be performed automatically by these
components. This is how it works: When you make a TQuery or TStoredProc active (by setting its Active
property to True or calling its Open method) an implicit call to Prepare will be generated if the query or
stored procedure has not already been explicitly prepared. Likewise, when the query or stored procedure is
made inactive (by setting Active to False or calling the Close method) the UnPrepare method is implicitly
called. This calling of UnPrepare, however, only occurs when the Prepare statement was implicitly called.
Whenever a query or stored procedure is explicitly prepared, by calling the Prepare method prior to
activating the object, an implicit call to UnPrepare does not take place. In these cases it is necessary for you
to also explicitly call UnPrepare when you are through with the object.
Since the preparation and unpreparation of a query or stored procedure requires time (including a network
round trip as well as resource allocation on the server), it is best to minimize the number of times these
operations are performed. If you are working with a query or stored procedure that is executed repeatedly,
therefore, it is critical that you explicitly prepare the object prior to its first activation, and only unprepare it
after it is de-activated for the last time.
The significance of explicit versus implicit invocation of Prepare and UnPrepare is important when the query
or stored procedure that you are working with is intended to be called repeatedly. For example,
parameterized queries, those that include one or more parameters, are often called repeatedly.
The difference in performance between explicitly and implicitly prepared queries is demonstrated in the
Delphi project named PREPARE.

http://edn.embarcadero.com/kr/article/20563

2/25

One of the most common uses for a data module is to hold data sets (including the TClientDataSet. it means that every form contains data set components that must be individually configured. and TClientDataSet components). placing those data sets on a single data module that is shared by the two or more forms provides for easier development and maintenance. IBEventAlerters. TStoredProc. a data module is never visible to the user. its sole purpose is to hold one or more components that can be shared by other parts of your program. In fact. While there is certainly nothing fundamentally wrong with this approach. The most common components to place on a data module are datasets (including TTable. to name a few. Many developers also like to place TDataSource components http://edn. Instead.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… Figure 1. The alternative to using a data module is to place a different set of data set components on each form in your application.com/kr/article/20563 Webinars on demand! Delphi Like 16. 3/25 . which is available in the client/server editions of Delphi 3 and 4). If two or more forms need to display the same data or event handlers (for providing client-side data validation. Timers. Unlike a form. as well as any components on the Data Access and Dialogs pages of the component palette. This includes MainMenus. But data sets are not the only components that can be used with data modules. permitting two or more forms within the application to share the properties and methods defined for those data sets. however. The PREPARE project main form Judicious Use of Data Modules. PopupMenus. a data module can hold any component that does not descend from TControl. OLEContainers. and When to Avoid Them A data module is a form-like container.694 people like Delphi. TQuery. for example).embarcadero.

you must place your BDEDataSet components. including any ranges. For example. make use of a WebModule (a TDataModule descendant). The general rule of thumb is that you do not use a data module when there should be no http://edn. as well as any provider components that you need. sort orders. and so on. Depending on the number of data-aware controls.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… on the data module as well. also available in Delphi 3 and Delphi 4 client/server. In fact. they provide you with a single repository for configurations and event handlers that can be shared. there are a number of situations where you must use a data module. Should You Always Use Data Modules The answer is "No.embarcadero." you do not always put data sets on data modules. The DATAMOD project with two forms sharing a common cursor. dialogs. as well as available separately from Inprise. the data module provided an easy and effective means for this. data modules are great. Yes. This sharing is not limited to single Tables either. a web module is not necessary. filters. Facebook social plugin The DATAMOD project on the code disk demonstrates how a data module permits two forms to share a common cursor to a data set. While this is often considered to be a matter of style. Using the WebModule you define the actions to which your web server extension can respond. Yes. this may be a major task. Remote data modules are special data modules that implement certain interfaces necessary for the cross-application communication that is required by MIDAS. Another example where you are required to use a data module can be found with the Web Broker components.) But there are situations where a data module can be used. placed on one or more data modules. or other similar containers. and the like.com/kr/article/20563 4/25 . But they are not appropriate in every situation. Data modules are a perfect solution for those situations where two or more forms. (If you use a WebDispatch component. There is not reason why two or more forms cannot share a multitude of data sets. onto a remote data module. These components. The project example built earlier in this article is a good example of that. timers. need to share a common set of components. if you are using the MIDAS technology found in Delphi 3 and Delphi 4 client/server editions. calculated fields. but doing so unnecessarily complicates your applications. If the DataSource component appears on the data module. More social media choices: Delphi on Google+ @RADTools on Tw itter ARTICLE TAGS restore Figure 2. Doing so permits you to convert a form from using one data module to another by simply changing the DataSet property of the DataSource. switching a form from using one data module to another requires that the DataSource property for every data-aware control on the form be changed. Since both Form1 and Form3 needed to share a common view of Table1. I prefer to place DataSource components onto the individual forms.

Why use two containers (a form and a data module) when one will suffice.com/kr/article/20563 5/25 . or different sort order. there are two other situations where data modules are generally not acceptable. What is interesting about the preceding example is that it represents a "best-case senario" for data module sharing with reports. Likewise. The user is viewing the form and then prints the data set. Not only can this be confusing to the user. Since the primary benefit of data modules is simplicity. Releasing the data module. This can result in long application start up times. Specifically. and that record contains errors that prevent the cursor from leaving it. with multiple instance forms. The reason for this is that VCL-based reporting tools must navigate a data set in order to print data. just as we did to Form3 in the example presented earlier in this article. http://edn. it seems absurd to use a data module when it increases complexity. you must implement some form of reference counting for the data module. if this is desired. such forms cannot share a single data set. Obviously. also requires more coding. filter. Imagine what can happen if one of these reports uses a data set on a data module. they are auto-created. Of course. As you can imagine. reports should not share data set. Imagine what would happen if the user prints two reports simultaneously. however. or some similar difference. By default. The classic example of this is when you are creating reports that use Delphi data sets for their data. since the data-aware controls in the user interface must be repainted as each record is navigated to. The easiest way to design a multiple instance form is to add the data set or data sets directly to the form. as the report navigates the data set. but is causes a catastrophic loss of performance for the report. of the data. each form must test for the pre-existence of the data module upon the form's creation. just not shared. or a table that makes use of a range. Instead. Once you do this. and they are always auto-created. prior to displaying a form that makes use of the components on the data module. This ensures that each instance of the form has its own data set or sets. Imagine what happens to the report if the user is currently editing a record. or sort order that is not used anywhere else in the application. If you always use data modules. The next thing the user sees is their form scrolling frantically. it is likely that all of your data sets will be opened when you start your application. For unique view forms. a data module can be used. This can be complicated. or set or records. However. For both of these last two examples it could be argued that a data module could still be used. using a data module in these cases unnecessarily complicates your application. A second instance where data modules should typically be avoided is when you are writing multiple instance forms. These data sets should never be shared. That view either may involve a table that is never viewed from any other form. Clearly. you must take responsibility for creating your data modules on-the-fly. A multiple instance form is one where more than one copy can be displayed simultaneously.embarcadero. so that you release it only when the last form requiring it is being closed. it is not enough to simply free the data module when a form is closing. one reason that the client asked me to look at this application in the first place was that they were unhappy with the load time. however.While reports provide a clear example where data set sharing is not acceptable. and an unnecessary number of table locking resources being used. The solution to problems caused by auto-created data modules is to remove them from the Auto-created forms list on the Project Options dialog box. They would be fighting for the control of the cursor. but the user may never know this.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… sharing of a data set. The first is when you have one form that uses a completely unique view of data. it must be created. since one data module may be used by more than one form. part of such a design is that each instance displays a different record. I once saw an application that had a data module that was auto-created. If one data module can be used by two or more form. meaning that each form has its own cursor(s). If the data module does not yet exist. and both of those reports share a data module. each instance of the form can be responsible for creating its own instance of a data module. and view(s). A final note about data modules is certainly in order here. and that same data set is used by data-aware controls on a form. and it contained about 100 data sets.

The DISCNTRL project​s main form http://edn. end. you must provide some assurance that the EnableControls method is executed following a call to DisableControls. DBEdits.embarcadero. including the EnableControls in the finally block.com/kr/article/20563 6/25 . The following Object Pascal pseudo code demonstrates this technique: Table1. One way to do this is to immediately following a call to DisableControls with a try-finally.EnableControls. including tables. and client datasets encapsulate methods that perform data access. The data source and data set components communicate with one and other. stored procedures. You use a data source when you want data controls (such as DBGrids. There is one general rule of thumb for using DisableControls. however. queries. invoking EnableControls to restore the communication. For example. try // Perform some operation on Table1 finally Table1.DisableControls. and so forth) to be able to view and/or manipulate this data. permitting the data source to instruct a data control to repaints itself following changes to data. The communication between a data set and a data source is not always desirable.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… When and How to Disable DataSets Data sets. You do this by invoking the data set​s DisableControls method. This technique is demonstrated in the DISCNTRL project on the code disk. Specifically. if you code needs to temporarily leave the current record to examine data in some other location of a table before returning to the original record you probably do not want a DBGrid displaying the data to show the user this operation. In instances like these you should disable the data sets communication with the data source. as well as permitting the data source to attempt to place a data source in an edit mode in response to a user​s interaction with a data control. DBNavigators. Figure 3.

Client/server developers tend to avoid ranges since they are only available with TTable components. In fact.embarcadero. While it is generally a better to use either SELECT queries or stored procedures to extract subsets of records from a remote database server.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… The DISCTNRL project includes a button that scans through every record in a local Paradox table. is the SetRange method. This method has the following syntax: void __fastcall SetRange(const System::TVarRec * StartValues. and easiest to use. Both arrays that are passed as parameters must have the same number of elements. converting the data in the Name field to either uppercase or lowercase characters. The first. By comparison. The Power of Ranges A range limits the records in a data set to a subset of all records. providing high performance in both local and client/server applications. const int StartValues_Size. A critical feature of a range is that it makes use of an index. const int EndValues_Size).com/kr/article/20563 7/25 . the speed and simplicity afforded by ranges makes them a useful tool in two-tier applications. You have two options when it comes to applying a range. I have seen instances where ranges substantially out-perform queries for operations such performing summary calculations on a small subset of records. making them acceptable only when working with relatively small data sets. do not use indexes. This is unfortunate. The value in the first http://edn. const System::TVarRec * EndValues. filters created using the Filter property of a data set.

Furthermore. include more than one set of starting and ending values in the array parameters. The arrays that you pass to the SetRange statement do not have to have the same number of elements as there are fields in the current index. State. or highest values of the range for each indexed field. if you have a table named Invoices. assume that this table has an index named CityIndex. and ApplyRange. The use of SetRange is demonstrated in the RANGE project. if provided.com/kr/article/20563 8/25 . The value in the second element. and Country fields. and this table is using an index based on the fields CustNo and InvoiceDate. if the current index is based on the City. The following statement sets the IndexName property to CityIndex. The following demonstrates how SetRange can be used to limit the display of records in a table to those where the city field contains "New York". or both the city and state fields. Table1->FieldByName("InvoiceDate")->AsString = "12/1/98". the second.ARRAYOFCONST(("New York "))). In general you should issue a Refresh to a Table after calling CancelRange. To remove a range use the method CancelRange.DB. with the first element corresponding to the first field in the index. For example.ARRAYOFCONST(("C1573 ". which is a single field index on the City field of CLIENTS. The following includes all of http://edn. Table1->FieldByName("InvoiceDate")->AsString = "5/1/99". Assume that Table1 is a component defined for a table named CLIENTS. it is acceptable to set a range only on the city field.embarcadero. "5/1/99 ")). This method has the following syntax: void __fastcall CancelRange(void). identifies the lowest value for the range on the second field of the index. or lowest values for the range on the first field of the index.DB. and so on. Table1->ApplyRange(). Table1->FieldByName("CustNo "). the following statement will display all records for customer C1573 for the dates 12/1/98 through 5/1/99: Table1->SetRange(ARRAYOFCONST(("C1573 ". it permits fields to be explicitly assigned their starting and ending values for the range without using an array. Removing a range is much easier than applying one. and then sets a range to display only those clients whose records contain New York in the City field: Table1->IndexName = "CityOrder ". While these statements also require an index (either primary or secondary). For example. The following example defines the same range as that demonstrated in the preceding listing: Table1->SetRangeStart(). and so on. "12/1/98 ")). To set a range based on a multi-field index. The elements of the second array identify the ending.AsString = "C1573 ". Table1->FieldByName("CustNo")->AsString = "C1573 ". Table1->SetRangeEnd(). shown in Figure 4.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… element of the first array corresponds to the beginning. if provided. if desired. Table1->SetRange(ARRAYOFCONST(("New York ")). SetRangeEnd. Using ApplyRange An alternative to using SetRange is to use the methods SetRangeStart. for the second field in the index.

27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… the relevent code. http://edn.embarcadero.com/kr/article/20563 9/25 . } else { Table1->CancelRange().: void __fastcall TForm1::Button1Click(TObject *Sender) { if (Button1->Caption == "Apply Range") { Table1->SetRange(ARRAYOFCONST((Edit1->Text)). have the additional capability of being able to read and write directly to the local hard drive. Button1->Caption = "Cancel Range". while normally associated with thin client applications in a multi-tier environment. found on the OnClick event handler for the Button named RangeButton. ARRAYOFCONST((Edit1->Text))). Table1->Refresh(). Button1->Caption = "Apply Range". } } Figure 4. The RANGE Project Creating Local Lookup Tables with ClientDataSet The ClientDataSet component.

begin //initialize CDSFile variable CDSFile := ExtractFilePath(Application. Furthermore. //Load all records ClientDataSet1. and then the Query's Provider is used to call GetRecords.Provider. However. Obviously. The use of a ClientDataSet as a local lookup table is demonstrated in the CDSET project shown in Figure 5. This value is assigned to the ClientDataSet's Data property. the ClientDataSet provides an attractive solution. once it is loaded. //Open the main table Query1. the memory required to hold this data may reduce your application's performance due to swapping. and then disconnect from the server. when the lookup tables change frequently the use of local copies increases the likelihood that a user will not see a valid value.cds'. if not FileExists(CDSFile) then begin Query2. very fast.com/kr/article/20563 10/25 . There is another. albeit similar use for ClientDataSet components. by loading the data from the local hard drive you can greatly reduce network traffic. which returns an OLEVariant. end.Provider. For example. Upon creation of the application's data module.Open. This simple project displays data from the SALES table in database pointed to by the IBLOCAL alias. and especially in situations where network communication is slow. This is to use the ClientDataSet to load small lookup tables from the local hard disk. save a local copy of it. which selects the EmpNo and Full_Name fields from the Employee table. thereby making it http://edn. Query2 is opened. shown in Figure 6 the following OnCreate event handler executes: procedure TDataModule2.GetRecords(-1. end else ClientDataSet1. in those situations where the lookup tables do not change frequently. Query2.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… This feature is often used to create briefcase applications > those that permit a user to connect to the application server.SaveToFile(CDSFile).Close.ExeName)+'emplkup. At some point the user re-connects to the server and uploads any changes made to the data since it was downloaded. If the local copy of the Employee lookup table is not found in the same directory as the application.embarcadero. The ClientDataSet is then set to the same Provider as the Query.Data := Query2. //Get the Query's provider interface ClientDataSet1.RecordsLoaded). are of a reasonable size and quantity. The ClientDataSet component stores data in an in-memory table. retrieve data. with some of them being very large. Furthermore.DataModule2Create(Sender: TObject). this technique is not appropriate for every application. //Save file to disk for future use ClientDataSet1. The user is then free to use the stored copy of the data without being attached to the server. making access to that data.Provider := Query2.Open. var RecordsLoaded: Integer. if you have great number of lookup tables.LoadFromFile(CDSFile).

Provider := Query2. var RecordsLoaded: Integer.Data := Query2. end.GetRecords(-1. Query2. ClientDataSet1.27/09/2014 available. which is attached to the Edit | Update Lookup menu item: procedure TForm1.RecordsLoaded).Open. begin with DataModule2 do begin Query2. Top 10 Tricks for Delphi and C++Builder VCL Database De… An application must provide a mechanism for updating a local lookup table is there is any chance that the official copy will change.SaveToFile(CDSFile).com/kr/article/20563 11/25 .Provider. http://edn.SimpleText := IntToStr(RecordsLoaded) + ' lookup records loaded'. StatusBar1. In this application this feature is provided by the following event handler. ClientDataSet1.Close.embarcadero. end. ClientDataSet1.UpdateLookup1Click(Sender: TObject).Provider.

There are times. http://edn. Making Direct BDE Calls Data-aware controls encapsulate calls to the Borland Database Engine (BDE). The CDSET project uses a ClientDataSet to store a local copy of the Employee lookup table Figure 6. The data module of the CDSET project includes two Queries. and a ClientDataSet. a Provider. however.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… Figure 5.com/kr/article/20563 12/25 .embarcadero.

0). as well as establishing database handles.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… when you need to get information from. and removes records marked for deletion from dBASE tables.144). and provides several demonstrations of the techniques you can use. cursor handles. procedure TForm1.FileListBox1.Button1Click(Sender: TObject).} dbResult := DbiInit(nil). and record handles.HLP is located in folder C:Program FilesCommon FilesBorland SharedBDE. BDE32. you must take responsibility for initializing the BDE.FileName). Accessing BDE functions and procedures this way is a lot of work. There are two general approaches for working with the BDE. Delphi and C++ Builder come with several sources of help for using the BDE.PackTable(Sender: TObject. the BDE not surfaced by these controls. var Tab: PChar. begin if FileListbox1. This can be done by making BDE calls directly. In order to do this. How to do this is demonstrated in the following section. TabName: PChar).Tab).embarcadero.) The critical code for the main form's unit is shown in following listing. This unit is BDE. end. try StrPCopy(Tab.[mbOK]. The first is the BDE help file.FileName = '' then begin MessageDlg('No table select'. and control features of. var hDb :hDBIDb. GetMem(Tab. (Packing releases space from Paradox tables occupied by deleted records. finally Dispose(tab). hCursor :hDBICur. dbResult :DBIResult. Exit. without the intervention of data-aware controls.mtError.com/kr/article/20563 13/25 . PdxStruct :CRTblDesc. Another source of information about the BDE can be found in the BDE interface unit. PackTable(Sender.PAS in Delphi and BDE.HPP in C++ Builder. procedure TForm1. This section describes where you can find information about BDE calls. The first is to provide for all BDE calls directly. Packing Tables The use of BDE calls to pack Paradox or dBASE tables in demonstrated in the project PACKTAB. begin {Initialize the BDE. http://edn.DRP. end. end.

{The method DoRestructure requires a pointer to a record object of the type CRTblDesc.dbiREADONLY. 0.nil. Note that before we can pack the Paradox table. StrPCopy( PdxStruct. try Check(dbResult).FileListBox1. which is required by many of the BDE calls. DbiExit.hCursor).nil. {Open a Database. TabName. except DbiCloseDatabase(hDB). Initialize this record.False. dbiOPENEXCL. the table's cursor handle must be closed.DB' then begin {Close the Paradox table cursor handle.dbiOPENSHARED.} FillChar(PdxStruct.0.embarcadero.szTblName.FileName)) = '.Filename). Application.Caption := 'Packing '+ FileListBox1. {Open a table.'STANDARD'.FileName. otherwise we would get a 'Table in use' error.} Check(dbResult). The DBTables unit must be in the uses clause to use Check.0).} try Panel1.nil. raise end. if AnsiUpperCase(ExtractFileExt(FileListBox1. and a cursor is open for a table.com/kr/article/20563 14/25 .} DbiCloseCursor(hCursor). except DbiExit. In Delphi 2 this procedure is located in the DB unit.xltNONE.''.''. The following segment shows how to pack a dBASE or Paradox table.hDB).} dbResult := DbiOpenDatabase(''.ProcessMessages. a database is open. raise end. {The BDE is initialized.''. try {Check raises an exception if the BDE call returns an error code other than DBIERR_NONE. We can now work with the table. http://edn. This returns a handle to the table's cursor. ''. PdxStruct.bPack := True.27/09/2014 Check(dbResult) Top 10 Tricks for Delphi and C++Builder VCL Database De… .} dbResult := DbiOpenTable(hDB. SizeOf(CRTblDesc).dbiREADWRITE.

The use of DbiDoRestructure in this example is as simple as this function can get.@PdxStruct. and then open that table. a database handle will be established. You must add this unit to your Delphi uses clause or include it in C++ Builder.Caption := 'Table successfully packed' else Panel1.False). managing all of the access to the BDE is a lot of work.} dbResult := DbiPackTable(hDB. and a cursor handle will also exist. the table that is selected from the main form is opened for exclusive use. In this Delphi example this unit is listed in the Interface uses clause. DbiExit. An example of both of these procedures is demonstrated. Also. Before any calls to the BDE can be made. automatically raises an exception if passed a return code from a BDE call that not successful. the DBTables unit must be added to the Interface uses clause.nil. For example. To actually change the structure of a Paradox table with this function would require a much more complex argument list. if you place a Table component onto a form. This unit defined the Check procedure. Once your work with the BDE is done. to open a table for shared use. it is necessary to cleanup after the application.''.Caption := 'Failure: error '+IntTostr(dbResult).True). DbiOpenDatabase. Since this form contains no data-aware components. you must give your unit access to the BDE import unit. end else begin {Packing a dBASE table is much easier.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… dbResult := DbiDoRestructure(hDB.com/kr/article/20563 15/25 . It is also possible.''. w hich means that no other user can access this table while this procedure has the table opened.nil.Caption := 'Table successfully packed' else Panel1. if dbResult = DBIERR_NONE then Panel1. DbiInit. while a dBASE table requires the use of DbiPackTable.1. the BDE will already be initialized. when called. In this case. In this case. all access to the BDE must be performed through code.nil. (The database handle can be obtained from http://edn. both the table cursor handle and the database handle need to be released (using DbiCloseCursor and DbiCloseDatabase). end. and DbiOpenTable. finally DbiCloseCursor(hCursor). end. if dbResult = DBIERR_NONE then Panel1. DbiCloseDatabase(hDB).embarcadero. This is achieved through the use of three basic BDE calls.Caption := 'Failure: error '+IntTostr(dbResult). and usually desirable. Each of these three calls are demonstrated in the procedure PackTable. and then the BDE must be deactivated (using DbiExit). The PackTable procedure demonstrates how to pack a Paradox table use the function DbiDoRestructure. Preventing Data Corruption As you can see from the preceding example. which. end. It is much easier if you permit data-aware components to do some of this work for you. hCursor.

but only one of these is considered acceptable with the current versions of the BDE. it can all but eliminate index out of date errors when Paradox tables are used in multi-user applications where there are many simultaneous postings to the tables. This project is named DBISAVE. however. a write operation may be delayed a short while after a record has been explicitly posted. you can use BatchMove to create a permanent table with those records. Like a SQL INSERT query. in fact. This function has the following syntax: function DbiSaveChanges(hCursor): DBIResult. deleting records from a Table that correspond to those in a DataSet. In order to improve performance. or copy. inserting records from a DataSet to a Table. But this is just the beginning. end.Table1AfterPost(DataSet: TDataset). BatchMove can be used to quickly make a copy of some or all of the records from one table. the table that you are copying the records to does not already need to exist. but prior to the BDE writing the cached edits. BatchMove can be used to insert records from any DataSet (Table. or both. Before continuing. Instead. It permits you to move. the local may become corrupt. after executing a query that returns a cursor to a set of records. There are. Specifically.) Furthermore. Query. From within this event handler you call DbiSaveChanges. when the application no longer needs the BDE. Likewise.Handle). The source of these records can be any DataSet. But this simplicity belies its usefulness. Using TBatchMove and BatchMove BatchMove is a feature that permits you to quickly move records into a Table component. The hCursor argument corresponds to the Handle property of a DataSet. which demonstrates a technique that is very valuable in high transaction environments. four major capabilities of the BatchMove component: creating a table and placing the current records of a DataSet in it. placing the copies records into another. and updating existing records in a table based on those in a DataSet. Consequently.embarcadero. or StoredProc) into an existing table. the BDE often caches edits to local tables. The one drawback to this is that if there is a system failure following the record post. records from a DataSet to a table. The capabilities of the BatchMove component appear simple enough. you can instruct the BDE to immediately write posted changes to tables. While a number of BDE calls require a database handle. you do not need to acquire these using BDE calls directly. let's consider the essential properties and the sole method of this component. Unlike an INSERT query. begin DbiSaveChanges(Table1.com/kr/article/20563 16/25 .27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… the DBHandle property of the table. There used to be two techniques that you could use for this purpose. This is done in the following project. The acceptable version is to create an AfterPost event handler for each of your DataSet components. reducing the likelihood that a system failure will damage tables. http://edn. and the cursor handle can be obtained from the Handle property. you can use the DBHandle and/or the Handle properties of your Database and/or DataSet components. For example. the data-aware components take responsibility for releasing the handles and deactivating the BDE. The following AfterPost event handler demonstrates this technique: procedure TForm1. a table handler. Using BDE API calls.

In other words. inserted. Fortunately. For example. These are Source. it would reasonable to assign a Query component that contains a SQL SELECT statement to the Source property. The following is the code associated with this button: procedure TForm1.com/kr/article/20563 17/25 .Button1Click(Sender: TObject). the results can be written to a new table by clicking on the Copy Result to Table button. This table is where the records of the source DataSet records are copied. you should only assign one of the other DataSet descendants to this property if they return a cursor to a dataset. There are five different modes. The results of this query. end. which is executed against the database selected in the Alias combobox when the Execute Query button is clicked. a Query. you can assign either a Table. After executing the query. and Mode.embarcadero. This technique is demonstrated in the project BATDEMO. Destination := Table1. or a StoredProc component name to this property. The Destination property is always assigned a Table component. is displayed in the Query Result DBGrid on the form. The main form for this project provides the user with a memo field in which to type a SQL SELECT query. end.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… Using the BatchMove Component There are three essential properties for the use of the BatchMove component. if SaveDialog1. along with whether the table assigned to the Destination property must exist prior to calling the Execution method of BatchMove.Execute then begin Table1. end.TableName := SaveDialog1. but it would not make sense to assign to this property a Query containing an ALTER TABLE statement. Execute. this is also the simplest use of BatchMove. deleted. Mode batAppend (default) batAppendUpdate batCopy batDelete batUpdate Destination Must Exist? No Yes No Yes Yes Must be Indexed? No Yes No Yes Yes One of the most common uses for a BatchMove component is to create a new table containing the records returned by a Query or StoredProc.Filename.Active = False then Exit. The Source property can be assigned any DataSet component. The third essential property. with BatchMove1 do begin Source := Query1. http://edn. Destination. While any Table component is acceptable. ShowMessage(IntToStr(MovedCount)+' records copied'). Mode. as well as whether the destination table must be indexed. or updated. begin if Query1. Mode := batCopy. These are shown in the following table. defines the type of operation performed by the BatchMove component. shown in Figure 7.

If the destination table is an existing table. the destination table is not keyed. and its Mode property is set to batCopy. If the user selects or enters the name of the file to copy the query result records to. http://edn. Under normal conditions. BatchMove will create the destination table if it does not already exist (this is also true when Mode is set to batAppend. When you set this property to any positive integer. If it is. despite what it says in the online help description). If you want to apply an index to this table you must use the AddIndex method of the TTable class. indicating how many records were actually copied based on the BatchMove's MovedCount property. For example. Next. for example. Finally. Figure 7. it is replaced by the new table when Mode is set to batCopy. in the millions. and the user has the ability to request that all of these records be processed by BatchMove. and added to if Mode is set to batAppend. When the Mode property is set to batCopy. In each case where Mode is set to batCopy. the BatchMove's Source property is set to Query1. its default value.embarcadero. You can do this with the RecordCount property. There may be times that you want to place an upper limit on the number of records that BatchMove can process.com/kr/article/20563 18/25 .27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… This code starts by ensuring that the Query component is active. Its Execute method is then called. all records referred to by the source DataSet are processed by the BatchMove's Execute method. The BATDEMO Project main form. the selected filename is assigned to the Table component Table1. the code displays the Save File common dialog box using a SaveDialog component. When RecordCount is set to 0 (zero). unless a range has been applied to the source DataSet). all records in the source DataSet are copied to the destination (that is. that number identifies the maximum number of records that BatchMove will process during any one call to its Execute method. if the source DataSet has the potential of containing a very large number of records. indicated when the SaveDialog's Execute method returns True. a message is displayed. you may want to specify that no more than a certain number of records can be copied. which initiates the copying. its Destination property is set to Table1.

Execute and TTable.). if a range has been defined for one or both of the tables. However. to make them both point to the same record. Using TTable. batDelete. When you call TTable. Synchonizing Tables Within a Delphi application it is possible to point two or more Table components to the same physical table. First. The following is the syntax of TTable. A second way is to directly synchronize the two tables.embarcadero. which makes use of the GotoCurrrent method of the Table class.BatchMove returns the number of records affected by the operation. All records from the source are copied. http://edn. TTable.com/kr/article/20563 19/25 . GotoCurrent has the following syntax: procedure GotoCurrent(SourceTable: TTable). you cannot specify a problem or key violation table name 2. GotoCurrent has two restrictions. the BatchMove method of the TTable class. This technique. Permit the tables for which an audit trail is desire to be edited by users only when these tables are in the cached updates mode. you specify the DataSet from which the records will be moved and the type of move to produce (batCopy. or Locate. the record being synchronized to must exist in the range of both tables. There are very limited options when you use TTable. Specifically. There are several important differences between TBatchMove.BatchMove. there is another alternative. as well as delete data from a table.BatchMove. Creating Audit Trails Using Cached Updates An audit trail is a record of changes that users make to a database.BatchMove. To implement an audit trail using cached updates you take the following steps: 1.BatchMove While the preceding example made use of a BatchMove component. but very powerful. and obviously.BatchMove: function BatchMove(ASource: TBDEDataSet. You use GotoCurrent to make two Table components point to the same record. For example. the two Table components must be associated with the same physical table. One way to synchronize these tables is to use a search method. While not all applications require an audit trail (and I personally would discourage implementing one unless there is a clear need for such a feature) the cached updates feature of data sets provides a simple yet powerful mechanism for implementing one. The GOTOREC projects demonstrates the use of GotoCurrent. The destination of the operation is always the Table that BatchMove is being called on. FindNearest. Second. Likewise. that is. etc. you cannot define a maximum number of records to be moved. consult the online help. GotoCurrent does not require that the two tables use the same index. such as FindKey. is somewhat limited. AMode: TBatchMode): Longint. There are times when it is desirable to synchronize these two table components. 1.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… For information on how to use the BatchMove component to update the data in a table.

5. or even a small group of memo fields. makes the information much more difficult to use. type of change (insert. s := 'INSERT'. and a memo with a string containing old and new values results in a much smaller table. you also have to decide whether each piece of information that you save will be stored in a separate fields of the audit trail table or in just a few fields. procedure TForm1. On the down side.Count . Specifically. s: String. Table2. type of change. how much detail will you track. Rollback the transaction if any of the records being updated cannot be applied.Edit. for i := 0 to Query1. The following code is attached to the OnUpdateRecord event handler for the Query shown in Figure 8. Regardless of how much detail you want to collect. date/time. storing all change data in a single memo. keeping only several fields.com/kr/article/20563 20/25 . This serves to rollback changes to the audit trail table as well. var UpdateAction: TUpdateAction). case UpdateKind of ukInsert: begin Table2. delete. but it will be extremely easy to analyze and search. when.Insert.Fields[1]. and to which record. There is no one approach that is suited for all situations. This simple example writes three fields to an audit trail table. On one hand you may merely want to note who made a change. On the other extreme is tracking every change. including the users name.1 do http://edn. The first two contain the user's name and date/time of posting while the third is a memo field that contains a description of the update action and all data affected by the action. Apply the updates within a transaction.Fields[0]. 4. and how convenient will it be to search the audit trail records. 3. Table2. UpdateKind: TUpdateKind. modification) as well as two fields for every field in the table whose changes are being tracked (one for the old value and one for the new) you will have a very large audit trail table. The AUDTRAIL project on the code disk demonstrates the recording of an audit trail within the context of cached updates. You will need to take into account how the audit trail will be used and the frequency with which records are added to it in making your decision.embarcadero. begin Table2. Within the OnUpdateRecord event handler write the audit trail record before updating the table for which the OnUpdateRecord event handler is executing. not only the fact that a change occurred but noting which values were changed and what they were changed to. If your audit trail table contains a field for the user's name.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… 2.Value := UserName.Value := Now. var i: Integer. date/time of change. This transaction should apply to the tables being updated as well as the audit trail tables. Use a OnUpdateRecord event handler to apply the updates.Query1UpdateRecord(DataSet: TDataSet. On the other hand.FieldDefs. There are numerous issues that you will have to address when creating an audit trail.

1 do if Query1. //case UpdateKind //Post the data to the audit table Table2. end. UpdateAction := uaApplied.Fields[2].Fields[i].Fields[i].27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… if Query1.Fields[0].Count . Table2. //ukModify end. Table2.Value := UserName. s := 'DELETE'.[' + Query1.FieldName + ']=' + Query1.Count .Fields[1].FieldName + ']=' + Query1. end.Fields[i]. http://edn.[' + Query1. pause for a millisecond to ensure //that duplicate keys are never generated.embarcadero.DisplayText. Table2. //ukInsert ukDelete: begin Table2. for i := 0 to Query1. //Since the Now function returns time to the //millisecond.NewValue. Signal completion.Value := UserName.NewValue then s := s + '.Fields[i].Value := Now. end. Table2.Value := s.Fields[2].FieldName + ']OLD=' + Query1.com/kr/article/20563 21/25 .Fields[i].Fields[2]. for i := 0 to Query1.Fields[i].FieldDefs.Post.FieldDefs. Sleep(1).Fields[i].Fields[0].Fields[1].Value := s. //ukDelete ukModify: begin Table2.Value <> Null then s := s + '.[' + Query1. end.Value := Now.Value := s.Apply(UpdateKind).Fields[i]. //Complete.Fields[i].OldValue <> Query1.Value <> Null then s := s + '.DisplayText.1 do if Query1. s := 'MODIFY'.Fields[i]. //Apply the update UpdateSQL1. Table2.Fields[i].OldValue + ':NEW=' + Query1.

The advantages of doing this include: 1. To create FieldDef definitions at design time. as shown in Figure 9. You can reduce network traffic by using FieldDefs rather than having to retrieve metadata from the database. click the ellipsis button on the Table's FieldDefs property to display the collection editor. The AUDTRAIL Project main form. Then. You can easily "borrow" the structures of existing tables. one at a time. You can visually define the structures of tables at design time as opposed to writing manual code. The Advantage of Design-Time FieldDefs Assignment In Delphi 4 and later and C++ Builder 4 and later it is possible to define FieldDefs for table components at design-time. and use the Object Inspector to define the fields properties. select each field. 2.com/kr/article/20563 22/25 . 3.embarcadero. Click the Add New button on the collection editor once for each field you want to add to the table.27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… Figure 8. http://edn.

This has the effect of defining FieldDefs based on the table to which you are referring. Oracle Press). where his column DBNavigator appears monthly. in Human Factors Psychology. Delphi In Depth (1996. there are some that you will likely find yourself using again and again.embarcadero. Right-click the table and select Update Table Definition from the displayed Speedmenu.com/. The Jensen Data Systems Inc. To create a new table at runtime. You can now create the new table at design time by right-clicking and selecting Create Table from the displayed Speedmenu. JBuilder Essentials (1998.. This paper has outlines some of those techniques that should be known to every VCL database developer. He is a popular speaker at conferences. Cary is available for onsite training when you have 6 or more people who need training in JBuilder. For more information contact Jensen Data Systems at (281) 359-3311. To borrow the structure of an existing table you must first prepare by doing the following: 1. or Oracle JDeveloper. Sybex).27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… Figure 8. While not all of these techniques are appropriate for every application. 3. and has served on four of the Inprise/Borland conference advisory boards. 2.com. http://edn. including Oracle JDeveloper 2 Starter Kit (Fall 1999. Cary has a Ph. best-selling co-author of eighteen books.jensendatasystems. Summary The VCL provides you with a wide range of solutions for database problems. call the table's CreateTable method. For training in Europe contact Susan Bennett at +44 (0) 181 789-2250. and training seminars throughout North America and Europe. Oracle JDeveloper 2 (1998. Oracle Press). 4.D. Cary is also Contributing Editor of Delphi Informant Magazine. Configure your FieldDefs using the Object Inspector. Delphi. He is an award-winning. workshops. Change the TableName and or DatabaseName to refer to the new table you want to create. a Houston-based company that provides database developers for application development and support. Osborne/McGraw-Hill). specializing in human-computer interaction. About the Author Cary Jensen is President of Jensen Data Systems. The AUDTRAIL project used design time-specified FieldDefs and IndexDefs definitions that are used to create the audit trail table when the application starts if the table does not already exist. Set the DatabaseName and TableName properties of your Table component to refer to the table that has the structure that you want to borrow. and Programming Paradox 5 for Windows (1995.com/kr/article/20563 23/25 . Osborne/McGraw-Hill). Inc. You can reach him at cjensen@compuserve. Web site is http://www.

. All rights reserved." The key is not to use the same.. Reply Posted by Filip Cruz on Jan 19 2000 Top 10 Tricks for Delphi and C++Builder VCL Database Developers by Cary Jensen Great article.embarcadero. Inc.. Reply Posted by Rafael Aguiló on Jan 27 2000 Top 10 Tricks for Delphi and C++Builder VCL Database Developers by Cary Jensen I do not completly agree when Cary Jensen says: "A second instance where data modules should typically be avoided is when you are writing multiple instance forms. The audit of deleted and inserted records does not work correctly. Maybe even more tutorials like Charlie Calvert's or even like the excelent Java tutorials on the SUN web site. It would be good to see more like it..com/kr/article/20563 Site Map 24/25 ... Anyway I've always desired to have a kind of "Data Environment" for the form (I think VFP had that.2013 Embarcadero Technologies.. Right now it is difficult to print without losng the right edge of the article..27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De… LATEST COMMENTS Move mouse over comment to see the full text Reply Posted by Gert Kello on Apr 13 2000 Top 10 Tricks for Delphi . Tutorials for Delphi that is. But there are some bugs in the AUDTRAIL project.. some bugs Have to agree with others that it's a greate article. When record is deleted or. Reply Posted by damian marquez on Mar 08 2000 Good idea Rafael I've been thinking about doing that Rafael. Serve r Re sponse fro m : ETNASC 04 Copyright© 1994 . Only request is that there be a way to display it in a "printer friendly" format... http://edn.. Reply Posted by Doug Samuel on Jan 26 2000 Top 10 Tricks for Delphi and C++Builder VCL Database Developers by Cary Jensen Excellent article. but I would like to see an ellegant solution from.

com/kr/article/20563 Top 10 Tricks for Delphi and C++Builder VCL Database De… 25/25 .27/09/2014 http://edn.embarcadero.