top 10 tricks for delphi and c++builder vcl database developers by cary jensen

Upload: ip-man

Post on 09-Oct-2015

45 views

Category:

Documents


2 download

DESCRIPTION

Programming Delphi

TRANSCRIPT

  • EDN Delphi Database

    Top 10 Tricks for Delphi and C++Builder VCL DatabaseDevelopers by Cary JensenBy: David Intersimone

    Abstract: This article provides an overview of a number of important techniques in general Delphi andC++Builder VCL database development.

    Top 10 Tricks for Delphi and C++Builder VCL Database Developersby Cary JensenJensen 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 databasedevelopment. These include the components on the Data Access page of the component palette, as well asthose on the Data Controls page. Additional database development components appear on the Midas pageof the component palette, but these are generally associated with multi-tier development, which is a specialcase, and are largely ignored in this paper.

    This paper provides you with an overview of a number of important techniques in general VCL databasedevelopment. If you are currently developing database applications that use the VCL you will not doubt befamiliar with some of these techniques. Therefore, the explicit goal of this paper is to provide you with a listof techniques that should be familiar to all active VCL database developers, assuring your awareness ofthese operations.

    It should be noted in advance that some of the technique described here are appropriate for bothclient/server applications as well as those that are not (including both stand alone applications as well asthose that run on a network).

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

    COMMUNITIES ARTICLES BLOGS RESOURCES DOWNLOADS HELP

    RATING

    Download Delphi XE7now!

    Get Free Trial

    Special Offer

    Download Trial

    Buy Now

    LOG ON | |EMBARCADERO HOME KOREANLOCATION

    Share This

    Watch, Follow, & Connect with Us

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • manipulation language (DML) SQL statements, it is generally necessary to prepare the query or storedprocedure prior to its execution. Likewise, since the act of preparing a query or stored procedure involvesthe allocation of resources on the server, it is necessary to unprepare the query or stored procedure whendone.

    This preparation and unpreparation can be performed through explict calls to TQuery and TStoredProccomponent s Prepare and UnPrepare methods, or they can be performed automatically by thesecomponents. This is how it works: When you make a TQuery or TStoredProc active (by setting its Activeproperty to True or calling its Open method) an implicit call to Prepare will be generated if the query orstored procedure has not already been explicitly prepared. Likewise, when the query or stored procedure ismade inactive (by setting Active to False or calling the Close method) the UnPrepare method is implicitlycalled. 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 toactivating the object, an implicit call to UnPrepare does not take place. In these cases it is necessary for youto 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 networkround trip as well as resource allocation on the server), it is best to minimize the number of times theseoperations 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 itafter it is de-activated for the last time.

    The significance of explicit versus implicit invocation of Prepare and UnPrepare is important when the queryor 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 theDelphi project named PREPARE.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • Figure 1. The PREPARE project main form

    Judicious Use of Data Modules, and When to Avoid Them A data module is a form-like container. Unlike a form, however, a data module is never visible to the user.Instead, its sole purpose is to hold one or more components that can be shared by other parts of yourprogram. One of the most common uses for a data module is to hold data sets (including theTClientDataSet, which is available in the client/server editions of Delphi 3 and 4), permitting two or moreforms within the application to share the properties and methods defined for those data sets.

    The alternative to using a data module is to place a different set of data set components on each form inyour application. While there is certainly nothing fundamentally wrong with this approach, it means thatevery form contains data set components that must be individually configured. If two or more forms need todisplay the same data or event handlers (for providing client-side data validation, for example), placingthose data sets on a single data module that is shared by the two or more forms provides for easierdevelopment and maintenance.

    But data sets are not the only components that can be used with data modules. In fact, a data module canhold any component that does not descend from TControl. This includes MainMenus, PopupMenus,OLEContainers, IBEventAlerters, Timers, as well as any components on the Data Access and Dialogs pagesof the component palette, to name a few.

    The most common components to place on a data module are datasets (including TTable, TQuery,TStoredProc, and TClientDataSet components). Many developers also like to place TDataSource components

    Webinars on demand!

    Delphi

    16,694 people like Delphi.

    LikeLike

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • on the data module as well. While this is often considered to be a matter of style, I prefer to placeDataSource components onto the individual forms. Doing so permits you to convert a form from using onedata module to another by simply changing the DataSet property of the DataSource. If the DataSourcecomponent appears on the data module, switching a form from using one data module to another requiresthat the DataSource property for every data-aware control on the form be changed. Depending on thenumber of data-aware controls, this may be a major task.

    The DATAMOD project on the code disk demonstrates how a data module permits two forms to share acommon cursor to a data set.

    Figure 2. The DATAMOD project with two forms sharing a common cursor.

    Should You Always Use Data ModulesThe answer is "No," you do not always put data sets on data modules. Yes, data modules are great. Yes,they provide you with a single repository for configurations and event handlers that can be shared. But theyare not appropriate in every situation.

    Data modules are a perfect solution for those situations where two or more forms, or other similarcontainers, need to share a common set of components. The project example built earlier in this article is agood example of that. Since both Form1 and Form3 needed to share a common view of Table1, including anyranges, filters, sort orders, calculated fields, and so on, the data module provided an easy and effectivemeans for this. This sharing is not limited to single Tables either. There is not reason why two or more formscannot share a multitude of data sets, dialogs, timers, and the like, placed on one or more data modules.

    In fact, there are a number of situations where you must use a data module. For example, if you are usingthe MIDAS technology found in Delphi 3 and Delphi 4 client/server editions, you must place your BDEDataSetcomponents, as well as any provider components that you need, onto a remote data module. Remote datamodules are special data modules that implement certain interfaces necessary for the cross-applicationcommunication that is required by MIDAS.

    Another example where you are required to use a data module can be found with the Web Brokercomponents. These components, also available in Delphi 3 and Delphi 4 client/server, as well as availableseparately from Inprise, make use of a WebModule (a TDataModule descendant). Using the WebModule youdefine the actions to which your web server extension can respond. (If you use a WebDispatch component,a web module is not necessary.)

    But there are situations where a data module can be used, but doing so unnecessarily complicates yourapplications. The general rule of thumb is that you do not use a data module when there should be no

    Facebook social plugin

    More social media choices:

    Delphi on Google+

    @RADTools on Twitter

    ARTICLE TAGS

    restore

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • sharing of a data set. The classic example of this is when you are creating reports that use Delphi data setsfor their data. These data sets should never be shared. The reason for this is that VCL-based reportingtools must navigate a data set in order to print data. Imagine what can happen if one of these reports usesa data set on a data module, and that same data set is used by data-aware controls on a form. The user isviewing the form and then prints the data set. The next thing the user sees is their form scrolling frantically,as the report navigates the data set. Not only can this be confusing to the user, but is causes acatastrophic loss of performance for the report, since the data-aware controls in the user interface must berepainted as each record is navigated to.

    What is interesting about the preceding example is that it represents a "best-case senario" for data modulesharing with reports. Imagine what happens to the report if the user is currently editing a record, and thatrecord contains errors that prevent the cursor from leaving it. Imagine what would happen if the user printstwo reports simultaneously, and both of those reports share a data module. They would be fighting for thecontrol of the cursor, but the user may never know this. Clearly, reports should not share data set.Whilereports provide a clear example where data set sharing is not acceptable, there are two other situationswhere data modules are generally not acceptable. The first is when you have one form that uses acompletely unique view of data. That view either may involve a table that is never viewed from any otherform, or a table that makes use of a range, filter, or sort order that is not used anywhere else in theapplication.

    A second instance where data modules should typically be avoided is when you are writing multiple instanceforms. A multiple instance form is one where more than one copy can be displayed simultaneously. Ofcourse, part of such a design is that each instance displays a different record, or set or records, or differentsort order, or some similar difference. Obviously, such forms cannot share a single data set. The easiest wayto design a multiple instance form is to add the data set or data sets directly to the form. This ensures thateach instance of the form has its own data set or sets, meaning that each form has its own cursor(s), andview(s), of the data.

    For both of these last two examples it could be argued that a data module could still be used. For uniqueview forms, a data module can be used, just not shared. Likewise, with multiple instance forms, eachinstance of the form can be responsible for creating its own instance of a data module. However, using adata module in these cases unnecessarily complicates your application. Why use two containers (a form anda data module) when one will suffice. Since the primary benefit of data modules is simplicity, it seems absurdto use a data module when it increases complexity.

    A final note about data modules is certainly in order here. By default, they are auto-created. If you alwaysuse data modules, and they are always auto-created, it is likely that all of your data sets will be openedwhen you start your application. This can result in long application start up times, and an unnecessarynumber of table locking resources being used. I once saw an application that had a data module that wasauto-created, and it contained about 100 data sets. As you can imagine, one reason that the client askedme to look at this application in the first place was that they were unhappy with the load time.

    The solution to problems caused by auto-created data modules is to remove them from the Auto-createdforms list on the Project Options dialog box, just as we did to Form3 in the example presented earlier in thisarticle. Once you do this, however, you must take responsibility for creating your data modules on-the-fly,prior to displaying a form that makes use of the components on the data module. This can be complicated,however. If one data module can be used by two or more form, each form must test for the pre-existence ofthe data module upon the form's creation. If the data module does not yet exist, it must be created.Releasing the data module, if this is desired, also requires more coding. Specifically, since one data modulemay be used by more than one form, it is not enough to simply free the data module when a form is closing.Instead, you must implement some form of reference counting for the data module, so that you release itonly when the last form requiring it is being closed.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • When and How to Disable DataSetsData sets, including tables, queries, stored procedures, and client datasets encapsulate methods thatperform data access. You use a data source when you want data controls (such as DBGrids, DBNavigators,DBEdits, and so forth) to be able to view and/or manipulate this data. The data source and data setcomponents communicate with one and other, permitting the data source to instruct a data control torepaints itself following changes to data, as well as permitting the data source to attempt to place a datasource in an edit mode in response to a users interaction with a data control.

    The communication between a data set and a data source is not always desirable, however. For example, ifyou code needs to temporarily leave the current record to examine data in some other location of a tablebefore returning to the original record you probably do not want a DBGrid displaying the data to show theuser this operation. In instances like these you should disable the data sets communication with the datasource. You do this by invoking the data set s DisableControls method, invoking EnableControls to restorethe communication.

    There is one general rule of thumb for using DisableControls. Specifically, you must provide some assurancethat the EnableControls method is executed following a call to DisableControls. One way to do this is toimmediately following a call to DisableControls with a try-finally, including the EnableControls in the finallyblock. The following Object Pascal pseudo code demonstrates this technique:

    Table1.DisableControls;try

    // Perform some operation on Table1finally

    Table1.EnableControls;end;

    This technique is demonstrated in the DISCNTRL project on the code disk.

    Figure 3. The DISCNTRL project s main form

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • The DISCTNRL project includes a button that scans through every record in a local Paradox table, convertingthe data in the Name field to either uppercase or lowercase characters.

    The Power of Ranges A range limits the records in a data set to a subset of all records. A critical feature of a range is that itmakes use of an index, providing high performance in both local and client/server applications. Bycomparison, filters created using the Filter property of a data set, do not use indexes, making themacceptable only when working with relatively small data sets.

    Client/server developers tend to avoid ranges since they are only available with TTable components. This isunfortunate. While it is generally a better to use either SELECT queries or stored procedures to extractsubsets of records from a remote database server, the speed and simplicity afforded by ranges makes thema useful tool in two-tier applications. In fact, I have seen instances where ranges substantially out-performqueries for operations such performing summary calculations on a small subset of records.

    You have two options when it comes to applying a range. The first, and easiest to use, is the SetRangemethod. This method has the following syntax:

    void __fastcall SetRange(const System::TVarRec * StartValues, const int StartValues_Size, constSystem::TVarRec * EndValues, const int EndValues_Size);

    Both arrays that are passed as parameters must have the same number of elements. The value in the first

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • element of the first array corresponds to the beginning, or lowest values for the range on the first field ofthe index. The value in the second element, if provided, identifies the lowest value for the range on thesecond field of the index, and so on. The elements of the second array identify the ending, or highest valuesof the range for each indexed field, with the first element corresponding to the first field in the index, thesecond, if provided, for the second field in the index, and so on.

    The arrays that you pass to the SetRange statement do not have to have the same number of elements asthere are fields in the current index. For example, if the current index is based on the City, State, andCountry fields, it is acceptable to set a range only on the city field, or both the city and state fields, ifdesired.

    The following demonstrates how SetRange can be used to limit the display of records in a table to thosewhere the city field contains "New York". Assume that Table1 is a component defined for a table namedCLIENTS.DB. Furthermore, assume that this table has an index named CityIndex, which is a single field indexon the City field of CLIENTS.DB. The following statement sets the IndexName property to CityIndex, andthen sets a range to display only those clients whose records contain New York in the City field:

    Table1->IndexName = "CityOrder ";Table1->SetRange(ARRAYOFCONST(("New York ")),ARRAYOFCONST(("New York ")));

    To set a range based on a multi-field index, include more than one set of starting and ending values in thearray parameters. For example, if you have a table named Invoices, and this table is using an index basedon the fields CustNo and InvoiceDate, the following statement will display all records for customer C1573 forthe dates 12/1/98 through 5/1/99:

    Table1->SetRange(ARRAYOFCONST(("C1573 ", "12/1/98 ")),ARRAYOFCONST(("C1573 ", "5/1/99 "));

    Using ApplyRangeAn alternative to using SetRange is to use the methods SetRangeStart, SetRangeEnd, and ApplyRange.While these statements also require an index (either primary or secondary), it permits fields to be explicitlyassigned their starting and ending values for the range without using an array. The following exampledefines the same range as that demonstrated in the preceding listing:

    Table1->SetRangeStart();

    Table1->FieldByName("CustNo ").AsString = "C1573 ";Table1->FieldByName("InvoiceDate")->AsString = "12/1/98";

    Table1->SetRangeEnd();Table1->FieldByName("CustNo")->AsString = "C1573 ";

    Table1->FieldByName("InvoiceDate")->AsString = "5/1/99";Table1->ApplyRange();

    Removing a range is much easier than applying one. To remove a range use the method CancelRange. Thismethod has the following syntax:

    void __fastcall CancelRange(void);

    In general you should issue a Refresh to a Table after calling CancelRange.

    The use of SetRange is demonstrated in the RANGE project, shown in Figure 4. The following includes all of

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • the relevent code, found on the OnClick event handler for the Button named RangeButton.:

    void __fastcall TForm1::Button1Click(TObject *Sender)

    { if (Button1->Caption == "Apply Range") {

    Table1->SetRange(ARRAYOFCONST((Edit1->Text)), ARRAYOFCONST((Edit1->Text)));

    Button1->Caption = "Cancel Range"; } else {

    Table1->CancelRange(); Table1->Refresh();

    Button1->Caption = "Apply Range"; }

    }

    Figure 4. The RANGE Project

    Creating Local Lookup Tables with ClientDataSetThe ClientDataSet component, while normally associated with thin client applications in a multi-tierenvironment, have the additional capability of being able to read and write directly to the local hard drive.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • This feature is often used to create briefcase applications > those that permit a user to connect to the

    application server, retrieve data, save a local copy of it, and then disconnect from the server. The user isthen free to use the stored copy of the data without being attached to the server. At some point the userre-connects to the server and uploads any changes made to the data since it was downloaded.

    There is another, albeit similar use for ClientDataSet components. This is to use the ClientDataSet to loadsmall lookup tables from the local hard disk. The ClientDataSet component stores data in an in-memorytable, making access to that data, once it is loaded, very fast. Furthermore, by loading the data from thelocal hard drive you can greatly reduce network traffic.

    Obviously, this technique is not appropriate for every application. For example, when the lookup tableschange frequently the use of local copies increases the likelihood that a user will not see a valid value.Furthermore, if you have great number of lookup tables, with some of them being very large, the memoryrequired to hold this data may reduce your application's performance due to swapping.

    However, in those situations where the lookup tables do not change frequently, are of a reasonable sizeand quantity, and especially in situations where network communication is slow, the ClientDataSet providesan attractive solution.

    The use of a ClientDataSet as a local lookup table is demonstrated in the CDSET project shown in Figure 5.This simple project displays data from the SALES table in database pointed to by the IBLOCAL alias. Uponcreation of the application's data module, shown in Figure 6 the following OnCreate event handler executes:

    procedure TDataModule2.DataModule2Create(Sender: TObject);

    var RecordsLoaded: Integer;

    begin//initialize CDSFile variable

    CDSFile := ExtractFilePath(Application.ExeName)+'emplkup.cds';if not FileExists(CDSFile) then

    begin Query2.Open;

    //Get the Query's provider interface ClientDataSet1.Provider := Query2.Provider; //Load all records

    ClientDataSet1.Data := Query2.Provider.GetRecords(-1,RecordsLoaded); Query2.Close;

    //Save file to disk for future use ClientDataSet1.SaveToFile(CDSFile);

    endelse

    ClientDataSet1.LoadFromFile(CDSFile);//Open the main table

    Query1.Open;end;

    If the local copy of the Employee lookup table is not found in the same directory as the application, Query2is opened, which selects the EmpNo and Full_Name fields from the Employee table. The ClientDataSet isthen set to the same Provider as the Query, and then the Query's Provider is used to call GetRecords, whichreturns an OLEVariant. This value is assigned to the ClientDataSet's Data property, thereby making it

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • available.

    An application must provide a mechanism for updating a local lookup table is there is any chance that theofficial copy will change. In this application this feature is provided by the following event handler, which isattached to the Edit | Update Lookup menu item:

    procedure TForm1.UpdateLookup1Click(Sender: TObject);var

    RecordsLoaded: Integer;begin

    with DataModule2 dobegin

    Query2.Open; ClientDataSet1.Provider := Query2.Provider; ClientDataSet1.Data := Query2.Provider.GetRecords(-1,RecordsLoaded);

    Query2.Close; ClientDataSet1.SaveToFile(CDSFile);

    StatusBar1.SimpleText := IntToStr(RecordsLoaded) + ' lookup records loaded';end;

    end;

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • Figure 5. 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, a Provider, and a ClientDataSet.

    Making Direct BDE CallsData-aware controls encapsulate calls to the Borland Database Engine (BDE). There are times, however,

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • when you need to get information from, and control features of, the BDE not surfaced by these controls.This can be done by making BDE calls directly. This section describes where you can find information aboutBDE calls, and provides several demonstrations of the techniques you can use.

    Delphi and C++ Builder come with several sources of help for using the BDE. The first is the BDE help file.BDE32.HLP is located in folder C:Program FilesCommon FilesBorland SharedBDE.

    Another source of information about the BDE can be found in the BDE interface unit. This unit is BDE.PAS inDelphi and BDE.HPP in C++ Builder.

    There are two general approaches for working with the BDE. The first is to provide for all BDE calls directly,without the intervention of data-aware controls. Accessing BDE functions and procedures this way is a lot ofwork. In order to do this, you must take responsibility for initializing the BDE, as well as establishingdatabase handles, cursor handles, and record handles. How to do this is demonstrated in the followingsection.

    Packing TablesThe use of BDE calls to pack Paradox or dBASE tables in demonstrated in the project PACKTAB.DRP. (Packingreleases space from Paradox tables occupied by deleted records, and removes records marked for deletionfrom dBASE tables.)

    The critical code for the main form's unit is shown in following listing.

    procedure TForm1.Button1Click(Sender: TObject);var

    Tab: PChar;begin

    if FileListbox1.FileName = '' then begin

    MessageDlg('No table select',mtError,[mbOK],0); Exit;

    end; GetMem(Tab,144);

    try StrPCopy(Tab,FileListBox1.FileName);

    PackTable(Sender,Tab); finally Dispose(tab);

    end;end;

    procedure TForm1.PackTable(Sender: TObject; TabName: PChar);

    var hDb :hDBIDb;

    hCursor :hDBICur; dbResult :DBIResult;

    PdxStruct :CRTblDesc;begin

    {Initialize the BDE.}dbResult := DbiInit(nil);

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • Check(dbResult) ;

    {Open a Database.}

    dbResult := DbiOpenDatabase('','STANDARD',dbiREADONLY,dbiOPENSHARED,'', 0,nil,nil,hDB);

    try {Check raises an exception if the BDE call

    returns an error code other than DBIERR_NONE. The DBTables unit must be in the uses clause to use Check.

    In Delphi 2 this procedure is located in the DB unit.} Check(dbResult);

    except DbiExit;

    raiseend;

    {Open a table. This returns a handle to the table'scursor, which is required by many of the BDE calls.}

    dbResult := DbiOpenTable(hDB, TabName, '','','',0,dbiREADWRITE, dbiOPENEXCL,xltNONE,False,nil,hCursor);

    try Check(dbResult);

    except DbiCloseDatabase(hDB);

    DbiExit; raise

    end;

    {The BDE is initialized, a database is open, and a cursoris open for a table. We can now work with the table.

    The following segment shows how to pack a dBASE orParadox table. Note that before we can pack the Paradox

    table, the table's cursor handle must be closed, otherwisewe would get a 'Table in use' error.}

    try Panel1.Caption := 'Packing '+ FileListBox1.FileName;

    Application.ProcessMessages; if AnsiUpperCase(ExtractFileExt(FileListBox1.FileName)) = '.DB' then

    begin {Close the Paradox table cursor handle.}

    DbiCloseCursor(hCursor); {The method DoRestructure requires a pointer to a record

    object of the type CRTblDesc. Initialize this record.} FillChar(PdxStruct, SizeOf(CRTblDesc),0);

    StrPCopy( PdxStruct.szTblName,FileListBox1.Filename); PdxStruct.bPack := True;

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • dbResult := DbiDoRestructure(hDB,1,@PdxStruct,nil,nil,nil,False); if dbResult = DBIERR_NONE then

    Panel1.Caption := 'Table successfully packed' else

    Panel1.Caption := 'Failure: error '+IntTostr(dbResult); end

    else begin

    {Packing a dBASE table is much easier.} dbResult := DbiPackTable(hDB, hCursor,'','',True);

    if dbResult = DBIERR_NONE then Panel1.Caption := 'Table successfully packed'

    else Panel1.Caption := 'Failure: error '+IntTostr(dbResult);

    end;finally

    DbiCloseCursor(hCursor); DbiCloseDatabase(hDB);

    DbiExit;end;end;

    Before any calls to the BDE can be made, you must give your unit access to the BDE import unit. You mustadd this unit to your Delphi uses clause or include it in C++ Builder. In this Delphi example this unit is listedin the Interface uses clause. Also, the DBTables unit must be added to the Interface uses clause. This unitdefined the Check procedure, which, when called, automatically raises an exception if passed a return codefrom a BDE call that not successful.

    Since this form contains no data-aware components, all access to the BDE must be performed through code.This is achieved through the use of three basic BDE calls, DbiInit, DbiOpenDatabase, and DbiOpenTable.Each of these three calls are demonstrated in the procedure PackTable. In this case, the table that isselected from the main form is opened for exclusive use, which means that no other user can access thistable while this procedure has the table opened. It is also possible, and usually desirable, to open a tablefor shared use.

    The PackTable procedure demonstrates how to pack a Paradox table use the function DbiDoRestructure,while a dBASE table requires the use of DbiPackTable. An example of both of these procedures isdemonstrated. The use of DbiDoRestructure in this example is as simple as this function can get. To actuallychange the structure of a Paradox table with this function would require a much more complex argumentlist.

    Once your work with the BDE is done, it is necessary to cleanup after the application. In this case, both thetable cursor handle and the database handle need to be released (using DbiCloseCursor andDbiCloseDatabase), and then the BDE must be deactivated (using DbiExit).

    Preventing Data CorruptionAs you can see from the preceding example, managing all of the access to the BDE is a lot of work. It ismuch easier if you permit data-aware components to do some of this work for you. For example, if you placea Table component onto a form, and then open that table, the BDE will already be initialized, a databasehandle will be established, and a cursor handle will also exist. (The database handle can be obtained from

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • the DBHandle property of the table, and the cursor handle can be obtained from the Handle property.)Furthermore, when the application no longer needs the BDE, the data-aware components takeresponsibility for releasing the handles and deactivating the BDE.

    While a number of BDE calls require a database handle, a table handler, or both, you do not need to acquirethese using BDE calls directly. Instead, you can use the DBHandle and/or the Handle properties of yourDatabase and/or DataSet components. This is done in the following project, which demonstrates atechnique that is very valuable in high transaction environments. Specifically, it can all but eliminate indexout of date errors when Paradox tables are used in multi-user applications where there are manysimultaneous postings to the tables. This project is named DBISAVE.

    In order to improve performance, the BDE often caches edits to local tables. Consequently, a writeoperation may be delayed a short while after a record has been explicitly posted. The one drawback to thisis that if there is a system failure following the record post, but prior to the BDE writing the cached edits, thelocal may become corrupt.

    Using BDE API calls, you can instruct the BDE to immediately write posted changes to tables, reducing thelikelihood that a system failure will damage tables.

    There used to be two techniques that you could use for this purpose, but only one of these is consideredacceptable with the current versions of the BDE. The acceptable version is to create an AfterPost eventhandler for each of your DataSet components. From within this event handler you call DbiSaveChanges. Thisfunction has the following syntax:

    function DbiSaveChanges(hCursor): DBIResult;

    The hCursor argument corresponds to the Handle property of a DataSet. The following AfterPost eventhandler demonstrates this technique:

    procedure TForm1.Table1AfterPost(DataSet: TDataset);

    begin DbiSaveChanges(Table1.Handle);

    end;

    Using TBatchMove and BatchMove BatchMove is a feature that permits you to quickly move records into a Table component. The source ofthese records can be any DataSet. For example, after executing a query that returns a cursor to a set ofrecords, you can use BatchMove to create a permanent table with those records. Likewise, BatchMove canbe used to quickly make a copy of some or all of the records from one table, placing the copies records intoanother.

    The capabilities of the BatchMove component appear simple enough. It permits you to move, or copy,records from a DataSet to a table. But this simplicity belies its usefulness. Like a SQL INSERT query,BatchMove can be used to insert records from any DataSet (Table, Query, or StoredProc) into an existingtable. Unlike an INSERT query, however, the table that you are copying the records to does not alreadyneed to exist. But this is just the beginning.

    There are, in fact, four major capabilities of the BatchMove component: creating a table and placing thecurrent records of a DataSet in it, deleting records from a Table that correspond to those in a DataSet,inserting records from a DataSet to a Table, and updating existing records in a table based on those in aDataSet. Before continuing, let's consider the essential properties and the sole method of this component.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • Using the BatchMove ComponentThere are three essential properties for the use of the BatchMove component. These are Source,Destination, and Mode. The Source property can be assigned any DataSet component. In other words, youcan assign either a Table, a Query, or a StoredProc component name to this property. While any Tablecomponent is acceptable, you should only assign one of the other DataSet descendants to this property ifthey return a cursor to a dataset. For example, it would reasonable to assign a Query component thatcontains a SQL SELECT statement to the Source property, but it would not make sense to assign to thisproperty a Query containing an ALTER TABLE statement.

    The Destination property is always assigned a Table component. This table is where the records of thesource DataSet records are copied, deleted, inserted, or updated.

    The third essential property, Mode, defines the type of operation performed by the BatchMove component.There are five different modes. These are shown in the following table, along with whether the tableassigned to the Destination property must exist prior to calling the Execution method of BatchMove, as wellas whether the destination table must be indexed.

    Mode Destination Must Exist? Must be Indexed?batAppend (default) No No

    batAppendUpdate Yes YesbatCopy No No

    batDelete Yes YesbatUpdate Yes Yes

    One of the most common uses for a BatchMove component is to create a new table containing the recordsreturned by a Query or StoredProc. Fortunately, this is also the simplest use of BatchMove. This technique isdemonstrated in the project BATDEMO, shown in Figure 7. The main form for this project provides the userwith a memo field in which to type a SQL SELECT query. The results of this query, which is executed againstthe database selected in the Alias combobox when the Execute Query button is clicked, is displayed in theQuery Result DBGrid on the form.

    After executing the query, the results can be written to a new table by clicking on the Copy Result to Tablebutton. The following is the code associated with this button:

    procedure TForm1.Button1Click(Sender: TObject);begin

    if Query1.Active = False then Exit;

    if SaveDialog1.Execute then begin Table1.TableName := SaveDialog1.Filename;

    with BatchMove1 do begin Source := Query1;

    Destination := Table1; Mode := batCopy;

    Execute; ShowMessage(IntToStr(MovedCount)+' records copied');

    end;end;

    end;

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • This code starts by ensuring that the Query component is active. If it is, the code displays the Save Filecommon dialog box using a SaveDialog component. If the user selects or enters the name of the file to copythe query result records to, indicated when the SaveDialog's Execute method returns True, the selectedfilename is assigned to the Table component Table1. Next, the BatchMove's Source property is set toQuery1, its Destination property is set to Table1, and its Mode property is set to batCopy. Its Executemethod is then called, which initiates the copying. Finally, a message is displayed, indicating how manyrecords were actually copied based on the BatchMove's MovedCount property.

    When the Mode property is set to batCopy, BatchMove will create the destination table if it does not alreadyexist (this is also true when Mode is set to batAppend, despite what it says in the online help description). Ifthe destination table is an existing table, it is replaced by the new table when Mode is set to batCopy, andadded to if Mode is set to batAppend. In each case where Mode is set to batCopy, the destination table isnot keyed. If you want to apply an index to this table you must use the AddIndex method of the TTableclass.

    Figure 7. The BATDEMO Project main form.

    Under normal conditions, all records in the source DataSet are copied to the destination (that is, unless arange has been applied to the source DataSet). There may be times that you want to place an upper limiton the number of records that BatchMove can process. For example, if the source DataSet has the potentialof containing a very large number of records, in the millions, for example, and the user has the ability torequest that all of these records be processed by BatchMove, you may want to specify that no more than acertain number of records can be copied. You can do this with the RecordCount property.

    When RecordCount is set to 0 (zero), its default value, all records referred to by the source DataSet areprocessed by the BatchMove's Execute method. When you set this property to any positive integer, thatnumber identifies the maximum number of records that BatchMove will process during any one call to itsExecute method.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • For information on how to use the BatchMove component to update the data in a table, as well as deletedata from a table, consult the online help.

    Using TTable.BatchMoveWhile the preceding example made use of a BatchMove component, there is another alternative. Specifically,the BatchMove method of the TTable class. There are several important differences betweenTBatchMove.Execute and TTable.BatchMove.

    1. There are very limited options when you use TTable.BatchMove. For example, you cannot define amaximum number of records to be moved. All records from the source are copied. Likewise, you cannotspecify a problem or key violation table name

    2. The destination of the operation is always the Table that BatchMove is being called on.

    The following is the syntax of TTable.BatchMove:

    function BatchMove(ASource: TBDEDataSet; AMode: TBatchMode): Longint;

    When you call TTable.BatchMove, you specify the DataSet from which the records will be moved and thetype of move to produce (batCopy, batDelete, etc.). TTable.BatchMove returns the number of recordsaffected by the operation.

    Synchonizing TablesWithin a Delphi application it is possible to point two or more Table components to the same physical table.There are times when it is desirable to synchronize these two table components, that is, to make them bothpoint to the same record. One way to synchronize these tables is to use a search method, such as FindKey,FindNearest, or Locate.

    A second way is to directly synchronize the two tables. This technique, which makes use of the GotoCurrrentmethod of the Table class, is somewhat limited, but very powerful.

    You use GotoCurrent to make two Table components point to the same record. GotoCurrent has tworestrictions. First, and obviously, the two Table components must be associated with the same physicaltable. Second, if a range has been defined for one or both of the tables, the record being synchronized tomust exist in the range of both tables. However, GotoCurrent does not require that the two tables use thesame index.

    GotoCurrent has the following syntax:

    procedure GotoCurrent(SourceTable: TTable);

    The GOTOREC projects demonstrates the use of GotoCurrent.

    Creating Audit Trails Using Cached UpdatesAn audit trail is a record of changes that users make to a database. While not all applications require anaudit trail (and I personally would discourage implementing one unless there is a clear need for such afeature) the cached updates feature of data sets provides a simple yet powerful mechanism forimplementing one.

    To implement an audit trail using cached updates you take the following steps:

    1. Permit the tables for which an audit trail is desire to be edited by users only when these tables are in thecached updates mode.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • 2. Use a OnUpdateRecord event handler to apply the updates.

    3. Apply the updates within a transaction. This transaction should apply to the tables being updated as wellas the audit trail tables.

    4. Within the OnUpdateRecord event handler write the audit trail record before updating the table for whichthe OnUpdateRecord event handler is executing.

    5. Rollback the transaction if any of the records being updated cannot be applied. This serves to rollbackchanges to the audit trail table as well.

    There are numerous issues that you will have to address when creating an audit trail. Specifically, how muchdetail will you track, and how convenient will it be to search the audit trail records. On one hand you maymerely want to note who made a change, when, and to which record. On the other extreme is trackingevery change, not only the fact that a change occurred but noting which values were changed and whatthey were changed to.

    Regardless of how much detail you want to collect, you also have to decide whether each piece ofinformation that you save will be stored in a separate fields of the audit trail table or in just a few fields. Ifyour audit trail table contains a field for the user's name, date/time of change, type of change (insert,delete, modification) as well as two fields for every field in the table whose changes are being tracked (onefor the old value and one for the new) you will have a very large audit trail table, but it will be extremelyeasy to analyze and search. On the other hand, keeping only several fields, including the users name,date/time, type of change, and a memo with a string containing old and new values results in a muchsmaller table. On the down side, storing all change data in a single memo, or even a small group of memofields, makes the information much more difficult to use.

    There is no one approach that is suited for all situations. You will need to take into account how the audittrail will be used and the frequency with which records are added to it in making your decision.

    The AUDTRAIL project on the code disk demonstrates the recording of an audit trail within the context ofcached updates. This simple example writes three fields to an audit trail table. The first two contain theuser's name and date/time of posting while the third is a memo field that contains a description of theupdate action and all data affected by the action. The following code is attached to the OnUpdateRecordevent handler for the Query shown in Figure 8.

    procedure TForm1.Query1UpdateRecord(DataSet: TDataSet; UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);

    var i: Integer;

    s: String;begin

    Table2.Edit;Table2.Insert;case UpdateKind of

    ukInsert: begin

    Table2.Fields[0].Value := UserName; Table2.Fields[1].Value := Now;

    s := 'INSERT'; for i := 0 to Query1.FieldDefs.Count - 1 do

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • if Query1.Fields[i].Value Null then

    s := s + ',[' + Query1.Fields[i].FieldName + ']=' + Query1.Fields[i].DisplayText; Table2.Fields[2].Value := s;

    end; //ukInsert ukDelete:

    begin Table2.Fields[0].Value := UserName;

    Table2.Fields[1].Value := Now; s := 'DELETE';

    for i := 0 to Query1.FieldDefs.Count - 1 do if Query1.Fields[i].Value Null then

    s := s + ',[' + Query1.Fields[i].FieldName + ']=' + Query1.Fields[i].DisplayText;

    Table2.Fields[2].Value := s; end; //ukDelete

    ukModify: begin

    Table2.Fields[0].Value := UserName; Table2.Fields[1].Value := Now; s := 'MODIFY';

    for i := 0 to Query1.FieldDefs.Count - 1 do if Query1.Fields[i].OldValue Query1.Fields[i].NewValue then

    s := s + ',[' + Query1.Fields[i].FieldName + ']OLD=' + Query1.Fields[i].OldValue + ':NEW=' +

    Query1.Fields[i].NewValue; Table2.Fields[2].Value := s;

    end; //ukModifyend; //case UpdateKind

    //Post the data to the audit tableTable2.Post;

    //Since the Now function returns time to the//millisecond, pause for a millisecond to ensure

    //that duplicate keys are never generated.Sleep(1);

    //Apply the updateUpdateSQL1.Apply(UpdateKind);

    //Complete. Signal completion.UpdateAction := uaApplied;end;

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • Figure 8. The AUDTRAIL Project main form.

    The Advantage of Design-Time FieldDefs AssignmentIn Delphi 4 and later and C++ Builder 4 and later it is possible to define FieldDefs for table components atdesign-time.

    The advantages of doing this include:

    1. You can reduce network traffic by using FieldDefs rather than having to retrieve metadata from thedatabase.

    2. You can visually define the structures of tables at design time as opposed to writing manual code.3. You can easily "borrow" the structures of existing tables.

    To create FieldDef definitions at design time, click the ellipsis button on the Table's FieldDefs property todisplay the collection editor. Click the Add New button on the collection editor once for each field you wantto add to the table. Then, select each field, one at a time, and use the Object Inspector to define the fieldsproperties, as shown in Figure 9.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • Figure 8. Configure your FieldDefs using the Object Inspector.

    To borrow the structure of an existing table you must first prepare by doing the following:

    1. Set the DatabaseName and TableName properties of your Table component to refer to the table thathas the structure that you want to borrow.

    2. Right-click the table and select Update Table Definition from the displayed Speedmenu. This has theeffect of defining FieldDefs based on the table to which you are referring.

    3. Change the TableName and or DatabaseName to refer to the new table you want to create.4. You can now create the new table at design time by right-clicking and selecting Create Table from the

    displayed Speedmenu. To create a new table at runtime, call the table's CreateTable method.

    The AUDTRAIL project used design time-specified FieldDefs and IndexDefs definitions that are used tocreate the audit trail table when the application starts if the table does not already exist.

    SummaryThe VCL provides you with a wide range of solutions for database problems. This paper has outlines someof those techniques that should be known to every VCL database developer. While not all of thesetechniques are appropriate for every application, there are some that you will likely find yourself using againand again.

    About the AuthorCary Jensen is President of Jensen Data Systems, Inc., a Houston-based company that provides databasedevelopers for application development and support. He is an award-winning, best-selling co-author ofeighteen books, including Oracle JDeveloper 2 Starter Kit (Fall 1999, Oracle Press), Oracle JDeveloper 2(1998, Oracle Press), JBuilder Essentials (1998, Osborne/McGraw-Hill), Delphi In Depth (1996,Osborne/McGraw-Hill), and Programming Paradox 5 for Windows (1995, Sybex). Cary is also ContributingEditor of Delphi Informant Magazine, where his column DBNavigator appears monthly. He is a popularspeaker at conferences, workshops, and training seminars throughout North America and Europe, and hasserved on four of the Inprise/Borland conference advisory boards. Cary has a Ph.D. in Human FactorsPsychology, specializing in human-computer interaction. You can reach him at [email protected] Jensen Data Systems Inc. Web site is http://www.jensendatasystems.com/.

    Cary is available for onsite training when you have 6 or more people who need training in JBuilder, Delphi,or Oracle JDeveloper. For more information contact Jensen Data Systems at (281) 359-3311. For training inEurope contact Susan Bennett at +44 (0) 181 789-2250.

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • Copyright 1994 - 2013 Embarcadero Technologies, Inc. All rights reserved. Site Map

    Move mouse over comment to see the full text

    Reply Posted by Gert Kello on Apr 13 2000

    Top 10 Tricks for Delphi ... some bugs

    Have to agree with others that it's a greate article. But there are some bugs in the AUDTRAILproject. The audit of deleted and inserted records does not work correctly. When record isdeleted or...

    Reply Posted by damian marquez on Mar 08 2000

    Good idea Rafael

    I've been thinking about doing that Rafael... Anyway I've always desired to have a kind of"Data Environment" for the form (I think VFP had that, but I would like to see an ellegantsolution from...

    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 modulesshould typically be avoided is when you are writing multiple instance forms." The key is not touse the same...

    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. Only request is that there be a way to display it in a "printer friendly"format. Right now it is difficult to print without losng the right edge of the article.

    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. It would be good to see more like it. Maybe even more tutorials like CharlieCalvert's or even like the excelent Java tutorials on the SUN web site. Tutorials for Delphithat is...

    Server Response from: ETNASC04

    LATEST COMMENTS

    27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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

  • 27/09/2014 Top 10 Tricks for Delphi and C++Builder VCL Database De

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