Top 10 Tricks For Delphi and C++Builder VCL Database Developers by Cary Jensen
Top 10 Tricks For Delphi and C++Builder VCL Database Developers by Cary Jensen
Top 10 Tricks For Delphi and C++Builder VCL Database Developers by Cary Jensen
EMBAR C ADER O HO ME
LO C ATIO N | KO R EAN | LO G O N
COMMUNITIES
ARTICLES
BLOGS
RESOURCES
DOWNLOADS
HELP
RATING
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).
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
components 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
27/09/2014
http://edn.embarcadero.com/kr/article/20563
Webinars on demand!
Delphi
Like
3/25
27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De
on the data module as well. While this is often considered to be a matter of style, I prefer to place
DataSource components onto the individual forms. Doing so permits you to convert a form from using one
data module to another by simply changing the DataSet property of the DataSource. If the DataSource
component appears on the data module, 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. Depending on the
number 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 a
common cursor to a data set.
ARTICLE TAGS
restore
Figure 2. The DATAMOD project with two forms sharing a common cursor.
http://edn.embarcadero.com/kr/article/20563
4/25
27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De
sharing of a data set. The classic example of this is when you are creating reports that use Delphi data sets
for their data. These data sets should never be shared. The reason for this is that VCL-based reporting
tools must navigate a data set in order to print data. Imagine what can happen if one of these reports uses
a data set on a data module, and that same data set is used by data-aware controls on a form. The user is
viewing 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 a
catastrophic loss of performance for the report, since the data-aware controls in the user interface must be
repainted as each record is navigated to.
What is interesting about the preceding example is that it represents a "best-case senario" for data module
sharing with reports. Imagine what happens to the report if the user is currently editing a record, and that
record contains errors that prevent the cursor from leaving it. Imagine what would happen if the user prints
two reports simultaneously, and both of those reports share a data module. They would be fighting for the
control of the cursor, but the user may never know this. Clearly, reports should not share data set.While
reports provide a clear example where data set sharing is not acceptable, there are two other situations
where data modules are generally not acceptable. The first is when you have one form that uses a
completely unique view of data. That view either may involve a table that is never viewed from any other
form, or a table that makes use of a range, filter, or sort order that is not used anywhere else in the
application.
A second instance where data modules should typically be avoided is when you are writing multiple instance
forms. A multiple instance form is one where more than one copy can be displayed simultaneously. Of
course, part of such a design is that each instance displays a different record, or set or records, or different
sort order, or some similar difference. Obviously, such forms cannot share a single data set. The easiest way
to design a multiple instance form is to add the data set or data sets directly to the form. This ensures that
each instance of the form has its own data set or sets, meaning that each form has its own cursor(s), and
view(s), of the data.
For both of these last two examples it could be argued that a data module could still be used. For unique
view forms, a data module can be used, just not shared. Likewise, with multiple instance forms, each
instance of the form can be responsible for creating its own instance of a data module. However, using a
data module in these cases unnecessarily complicates your application. Why use two containers (a form and
a data module) when one will suffice. Since the primary benefit of data modules is simplicity, it seems absurd
to 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 always
use data modules, and they are always auto-created, it is likely that all of your data sets will be opened
when you start your application. This can result in long application start up times, and an unnecessary
number of table locking resources being used. I once saw an application that had a data module that was
auto-created, and it contained about 100 data sets. As you can imagine, 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.
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, just as we did to Form3 in the example presented earlier in this
article. 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 of
the 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 module
may 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 it
only when the last form requiring it is being closed.
http://edn.embarcadero.com/kr/article/20563
5/25
27/09/2014
http://edn.embarcadero.com/kr/article/20563
6/25
27/09/2014
The DISCTNRL project includes a button that scans through every record in a local Paradox table, converting
the data in the Name field to either uppercase or lowercase characters.
7/25
27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De
element of the first array corresponds to the beginning, or lowest values for the range on the first field of
the index. The value in the second element, if provided, identifies the lowest value for the range on the
second field of the index, and so on. The elements of the second array identify the ending, or highest values
of the range for each indexed field, with the first element corresponding to the first field in the index, the
second, 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 as
there are fields in the current index. For example, if the current index is based on the City, State, and
Country fields, it is acceptable to set a range only on the city field, or both the city and state fields, if
desired.
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". Assume that Table1 is a component defined for a table named
CLIENTS.DB. Furthermore, assume that this table has an index named CityIndex, which is a single field index
on the City field of CLIENTS.DB. The following statement sets the IndexName property to CityIndex, and
then 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 the
array parameters. For example, if you have a table named Invoices, and this table is using an index based
on the fields CustNo and InvoiceDate, the following statement will display all records for customer C1573 for
the dates 12/1/98 through 5/1/99:
Table1->SetRange(ARRAYOFCONST(("C1573 ", "12/1/98 ")),ARRAYOFCONST(("C1573 ", "5/1/99 "));
Using ApplyRange
An 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 explicitly
assigned their starting and ending values for the range without using an array. The following example
defines 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. This
method 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
http://edn.embarcadero.com/kr/article/20563
8/25
27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De
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";
}
}
http://edn.embarcadero.com/kr/article/20563
9/25
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, retrieve data, save a local copy of it, and then disconnect from the server. The user is
then free to use the stored copy of the data without being attached to the server. At some point the user
re-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 load
small lookup tables from the local hard disk. The ClientDataSet component stores data in an in-memory
table, making access to that data, once it is loaded, very fast. Furthermore, by loading the data from the
local hard drive you can greatly reduce network traffic.
Obviously, this technique is not appropriate for every application. For example, when the lookup tables
change 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 memory
required 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 size
and quantity, and especially in situations where network communication is slow, the ClientDataSet provides
an 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. Upon
creation 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);
end
else
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, Query2
is opened, which selects the EmpNo and Full_Name fields from the Employee table. The ClientDataSet is
then set to the same Provider as the Query, and then the Query's Provider is used to call GetRecords, which
returns an OLEVariant. This value is assigned to the ClientDataSet's Data property, thereby making it
http://edn.embarcadero.com/kr/article/20563
10/25
27/09/2014
available.
An application must provide a mechanism for updating a local lookup table is there is any chance that the
official copy will change. In this application this feature is provided by the following event handler, which is
attached to the Edit | Update Lookup menu item:
procedure TForm1.UpdateLookup1Click(Sender: TObject);
var
RecordsLoaded: Integer;
begin
with DataModule2 do
begin
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;
http://edn.embarcadero.com/kr/article/20563
11/25
27/09/2014
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.
http://edn.embarcadero.com/kr/article/20563
12/25
27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De
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 about
BDE 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 in
Delphi 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 of
work. In order to do this, you must take responsibility for initializing the BDE, as well as establishing
database handles, cursor handles, and record handles. How to do this is demonstrated in the following
section.
Packing Tables
The use of BDE calls to pack Paradox or dBASE tables in demonstrated in the project PACKTAB.DRP. (Packing
releases space from Paradox tables occupied by deleted records, and removes records marked for deletion
from 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);
http://edn.embarcadero.com/kr/article/20563
13/25
27/09/2014
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;
raise
end;
{Open a table. This returns a handle to the table's
cursor, 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 cursor
is open for a table. We can now work with the table.
The following segment shows how to pack a dBASE or
Paradox table. Note that before we can pack the Paradox
table, the table's cursor handle must be closed, otherwise
we 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;
http://edn.embarcadero.com/kr/article/20563
14/25
27/09/2014
http://edn.embarcadero.com/kr/article/20563
15/25
27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De
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 take
responsibility 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 acquire
these using BDE calls directly. Instead, you can use the DBHandle and/or the Handle properties of your
Database and/or DataSet components. This is done in the following project, which demonstrates a
technique that is very valuable in high transaction environments. Specifically, 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.
In order to improve performance, the BDE often caches edits to local tables. Consequently, a write
operation may be delayed a short while after a record has been explicitly posted. The one drawback to this
is that if there is a system failure following the record post, but prior to the BDE writing the cached edits, the
local may become corrupt.
Using BDE API calls, you can instruct the BDE to immediately write posted changes to tables, reducing the
likelihood 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 considered
acceptable with the current versions of the BDE. The acceptable version is to create an AfterPost event
handler for each of your DataSet components. From within this event handler you call DbiSaveChanges. This
function has the following syntax:
function DbiSaveChanges(hCursor): DBIResult;
The hCursor argument corresponds to the Handle property of a DataSet. The following AfterPost event
handler demonstrates this technique:
procedure TForm1.Table1AfterPost(DataSet: TDataset);
begin
DbiSaveChanges(Table1.Handle);
end;
http://edn.embarcadero.com/kr/article/20563
16/25
27/09/2014
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. Fortunately, this is also the simplest use of BatchMove. This technique is
demonstrated in the project BATDEMO, shown in Figure 7. The main form for this project provides the user
with a memo field in which to type a SQL SELECT query. The results of this query, which is executed against
the database selected in the Alias combobox when the Execute Query button is clicked, is displayed in the
Query 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 Table
button. 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;
http://edn.embarcadero.com/kr/article/20563
17/25
27/09/2014
This code starts by ensuring that the Query component is active. If it is, the code displays the Save File
common dialog box using a SaveDialog component. If the user selects or enters the name of the file to copy
the query result records to, indicated when the SaveDialog's Execute method returns True, the selected
filename is assigned to the Table component Table1. Next, the BatchMove's Source property is set to
Query1, its Destination property is set to Table1, and its Mode property is set to batCopy. Its Execute
method is then called, which initiates the copying. Finally, a message is displayed, indicating how many
records 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 already
exist (this is also true when Mode is set to batAppend, despite what it says in the online help description). If
the destination table is an existing table, it is replaced by the new table when Mode is set to batCopy, and
added to if Mode is set to batAppend. In each case where Mode is set to batCopy, the destination table is
not keyed. If you want to apply an index to this table you must use the AddIndex method of the TTable
class.
http://edn.embarcadero.com/kr/article/20563
18/25
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, as well as delete
data from a table, consult the online help.
Using TTable.BatchMove
While 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 between
TBatchMove.Execute and TTable.BatchMove.
1. There are very limited options when you use TTable.BatchMove. For example, you cannot define a
maximum number of records to be moved. All records from the source are copied. Likewise, you cannot
specify 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 the
type of move to produce (batCopy, batDelete, etc.). TTable.BatchMove returns the number of records
affected by the operation.
Synchonizing Tables
Within 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 both
point 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 GotoCurrrent
method 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 two
restrictions. First, and obviously, the two Table components must be associated with the same physical
table. Second, if a range has been defined for one or both of the tables, the record being synchronized to
must exist in the range of both tables. However, GotoCurrent does not require that the two tables use the
same index.
GotoCurrent has the following syntax:
procedure GotoCurrent(SourceTable: TTable);
The GOTOREC projects demonstrates the use of GotoCurrent.
http://edn.embarcadero.com/kr/article/20563
19/25
27/09/2014
Top 10 Tricks for Delphi and C++Builder VCL Database De
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 well
as the audit trail tables.
4. Within the OnUpdateRecord event handler write the audit trail record before updating the table for which
the OnUpdateRecord event handler is executing.
5. Rollback the transaction if any of the records being updated cannot be applied. This serves to rollback
changes to the audit trail table as well.
There are numerous issues that you will have to address when creating an audit trail. Specifically, how much
detail will you track, and how convenient will it be to search the audit trail records. On one hand you may
merely want to note who made a change, when, and to which record. On the other extreme is tracking
every change, not only the fact that a change occurred but noting which values were changed and what
they were changed to.
Regardless of how much detail you want to collect, 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. If
your 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 (one
for the old value and one for the new) you will have a very large audit trail table, but it will be extremely
easy 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 much
smaller table. On the down side, storing all change data in a single memo, or even a small group of memo
fields, 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 audit
trail 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 of
cached updates. This simple example writes three fields to an audit trail table. 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. The following code is attached to the OnUpdateRecord
event 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
http://edn.embarcadero.com/kr/article/20563
20/25
27/09/2014
http://edn.embarcadero.com/kr/article/20563
21/25
27/09/2014
http://edn.embarcadero.com/kr/article/20563
22/25
27/09/2014
Summary
The VCL provides you with a wide range of solutions for database problems. This paper has outlines some
of those techniques that should be known to every VCL database developer. While not all of these
techniques are appropriate for every application, there are some that you will likely find yourself using again
and again.
http://edn.embarcadero.com/kr/article/20563
23/25
27/09/2014
LATEST COMMENTS
Move mouse over comment to see the full text
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 key is not to
use 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 Charlie
Calvert's or even like the excelent Java tutorials on the SUN web site. Tutorials for Delphi
that is...
Serve r Re sponse fro m : ETNASC 04
http://edn.embarcadero.com/kr/article/20563
Site Map
24/25
27/09/2014
http://edn.embarcadero.com/kr/article/20563
25/25