Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
by
Martin Rudy
One of the many data connectivity options for the Borland RAD products is dbExpress.
This set of drivers and components provide connectivity to databases for the Windows,
.NET and Linux platforms. This paper covers an introduction to using the dbExpress
technology in both Win32 and .NET.
NOTE: Not all of the material in this paper will be covered in the Conference session.
The intent is to provide more detail in this paper that can be referenced during the
session.
Contents
The major topics covered are:
the data at runtime, and how to connect the two. This is done using a DataSet,
DataSource, and data-aware controls.
dbExpress Introduction
Contents
dbExpress is a cross-platform, database-independent set of drivers and components that
provide high-performance and a small footprint. It consists of database connectivity
technologies introduced in Delphi 6 and Kylix and is one of the data connectivity options
in Delphi 2005 for both Win32 and .NET applications. For .NET applications, it is called
dbExpress.NET which is a .NET version of the same capabilities found in Delphi 7
dbExpress.
Connecting to data requires the database to be identified, the data table(s) and columns
selected and a request for the defined data. The major difference between dbExpress and
the other RAD data connectivity options is dbExpress only supports unidirectional, readonly cursors for data retrieval. This means no data is buffered and some of the features
available in the other data connectivity options are not applicable. The following are
some of the features not available with unidirectional cursors:
No batch moves
No heterogeneous queries
No cached updates
No lookup fields
At first glance you might think dbExpress is not for you, but wait, it is really a more
efficient way to retrieve and update data. The technique used by dbExpress to manipulate
data is the same as building multi-tier applications using DataSnap (formerly known as
MIDAS) you use ClientDataSets. The light-weight, fast dbExpress drivers quickly
retrieve data from the database and ClientDataSets provide the ability to modify data,
change sort orders, and maintain a change log, support lookups, filtering, plus much
more. Using dbExpress as it is designed requires you to perform a fetch of only the data
that users need to immediately use, not the entire table. No longer will a SELECT *
FROM tablename be sufficient (unless the result set is not too large). ClientDataSets are
in-memory tables, all data retrieved, as the result set is stored in the local workstations
memory. This basic architecture is called provide/resolve.
In Win32, two ini files are used to store information about drivers and database
connections. The installed driver types are contained in dbxdrivers. For each driver
installed, the required libraries (DLLs for Windows and shared objects in Linux) are
listed along with default connection parameter settings. The second ini file used,
dbxconnections contains the named configuration connection sets.
The components available in dbExpress and dbExpress.NET are essentially the same.
Figures 2 and 3 show the Component/Tool Palettes available in Delphi 7 and Delphi
2005. Table 1 lists the components on the dbExpress component palette page and a brief
description.
With dbExpress you start by using a SQLConnection component. The first step is to
define the database connection. This can be done using an existing connection definition,
creating a new connection definition, or using the Params property of the SQLConnection
to define a connection dynamically. We will start using an existing definition.
The ConnectionName property is assigned the name of an existing definition.
Connection definitions are stored in an ini file named dbxconnections. This file stores
the connection configurations settings and specifies which dbExpress driver to use. The
available drivers are maintained in an ini file named drivers. This file contains the DLL
or SO name required for connection and the default settings for all the connection
parameters. Setting the ConnectionName property can be done by selecting an entry in
the drop-down or double-clicking on the SQLConnection component which displays the
dbExpress Connection Editor. This editor is essentially the same in both Delphi 7 and 8
as shown in Figures 4 and 5.
Limitations of SimpleClientDataSet
The SimpleClientDataSet is great for quick demos and basic data retrieval. Beyond these
two uses, there are limitations that may prevent you from fully using the power of
dbExpress. Properties and events of the internal components are not surfaced. This
prevents you from creating multi-tier applications, create nested datasets, utilize any of
the DataSetProvider features and if an internal SQLConnection is used it cannot be
shared with other datasets.
A more appropriate approach is to use a ClientDataSet (CDS) and DataSetProvider.
Below is a simple example of how a button can be used to send updates to the database.
The value returned by ApplyUpdates is saved in the variable iErrCnt. Since -1 is used as
the parameter, there is no limit on the number of errors. All records that can be posted
will be committed and all error records will remain in the Data property of the CDS. If
the value is > 0 then a message is displayed along with the number of remaining errors.
procedure TfrmMain.pbUpdateClick(Sender: TObject);
var
iErrCnt: Integer;
begin
iErrCnt := cdsCustomer.ApplyUpdates(-1);
if iErrCnt > 0 then
MessageDlg('Problem in apply updates, ' + IntToStr(iErrCnt) +
' error(s).',mtWarning,[mbOK],0);
end;
Undoing changes
Changes can be reversed in a CDS to a variety of levels. They include an incremental
undo, a record undo, all changes, and refreshing the data currently in the provider.
Incremental undo is accomplished using the UndoLastChange method. This method
reverts an entire record to it previous set of values. Note, you cannot undo to the fieldlevel, only the record-level. Each call to the method reverts in sequence the previous
change to the dataset. There is one parameter used with the UndoLastChange method that
is a Boolean data type. When the value is True, the cursor is repositioned on the record
that is restored. The cursor remains in its current position when the parameter is False.
Below is an example of UndoLastChange where the cursor follows the change.
cdsCustomer.UndoLastChange(True);
Calling RevertRecord can reverse all changes for the current record. This method
removes all changes for the current record from the change log. Below is an example for
RevertRecord.
cdsCustomer.RevertRecord;
All changes can be canceled using CancelUpdates. When this method is called, every
entry in the change log is removed. Before calling CancelUpdates, the ChangeCount
property can be used to determine if there are any changes to the dataset. The following is
an example of using this property with CancelUpdates.
if cdsCustomer.ChangeCount > 0 then
begin
if MessageDlg('Are you sure you want to cancel all changes?',
mtConfirmation,[mbYes,mbNo],0) = mrYes then
begin
cdsCustomer.CancelUpdates;
cdsCustomer.Refresh;
end;
end
else
Do ' +
In this example, a preemptive test is done before calling Refresh. If there are any changes
in the log, the user is prompted to cancel all changes before refreshing the data.
of the options included in the set determines which records are displayed.
Figure 8: Reconcile Error Dialog option in the Object Repository for Delphi 7
Figure 9: Reconcile Error Dialog option in the Object Repository for Delphi 2005
.NET projects
Adding the Reconcile Error Dialog to a project requires you to do three things. First, the
unit for the dialog must be removed from the list of auto-created forms for the project,
which is done automatically for you when you add the dialog to a project unless you have
turned off the auto create feature. Second, you must modify the OnReconcileError event
of the ClientDataSet component. Below is an example of the minimum code for the
event. The last step is to add the unit for the dialog to the form or data module where the
CDS component resides. In the sample code the uses clause in the data module is
updated.
procedure TdmMain.cdsCustomerReconcileError(DataSet:
TCustomClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction);
begin
Action := HandleReconcileError(DataSet, UpdateKind, E);
end;
The Reconcile Error Dialog is displayed after a call to ApplyUpdates when one or more
error records exist. Each record that cannot be posted is displayed in the dialog box, one
error at a time. Figure 10 shows an example when there is an attempt to save a record and
another user has already made a modification to the record.
Merge
Correct
Cancel
Refresh
The action for each error can be set before clicking the OK button. Based on the type of
error, you can determine to leave the record in the change log, cancel it, merge the
updated record, or abort the entire reconcile operation.
in the WHERE clause. Lowercase characters are used to visually seeing a difference
between the field name and parameter name, but they are not required. The value of
:cust_no is automatically updated each time the master record changes which in turn
generates a new SQL statement and the appropriate detail data is returned.
Viewing the data with ClientDataSets only requires a single TDataSetProvider. It is
linked to the SQLDataSet for the master table. No provider is required for the detail data.
The data for the Sales table will automatically be made available by the nesting of the
detail data as a separate field for each master record.
The major difference in setting up master / detail using nested datasets is in setting the
ClientDataSet properties. Two CDS components are required. The first ClientDataSet,
used as the master table, is linked directly to the provider. An additional step is required
to create TField descendants for the fields in the master dataset. When this is done, a new
field is listed that does not exist in the Customer table. Figure 12 shows the TFields
Editor for the Customer CDS. The additional field contains the detail records for the
Sales table. It is a TDataSetField descendant. The name of this field is the same as the
name of the SQLDataSet.
Value
pfInUpdate
pfInWhere
pfInKey
pfHidden
Description
Field is included in update
Field is used in finding the original record to be updated
Field is used in finding the current record after an update fails
Field is included in the data packet to ensure uniqueness of the record.
The field is used to find the original record to update. The field is not
visible to the application.
Each field in the dataset used by the provider can set which of the ProviderFlags are
applicable. Changing the ProviderFlags modifies the SQL UPDATE and DELETE
statements created by the provider to change and remove records from the database.
To demonstrate the impact on updating data and the usage of the UpdateMode and
ProviderFlags properties, the following SQL statement will be used with the InterBase
Employee database. This example is in the ProviderFlags projects which is using
dbExpress.
SELECT CUST_NO, CUSTOMER, CONTACT_FIRST,
CONTACT_LAST, PHONE_NO,
ADDRESS_LINE1, ADDRESS_LINE2, CITY,
STATE_PROVINCE, COUNTRY,
POSTAL_CODE, ON_HOLD,
((CITY || ',') || STATE_PROVINCE) AS CITYSTATE
FROM CUSTOMER
All of the fields from the Customer table are returned and a calculated field combining
the City and State_Province field which is named CityState.
Any attempt to modify an existing record or delete a record generates an error with the
message Column unknown CITYSTATE. The problem is in the WHERE clause that is
generated. Below is the SQL generated for deleting a record. The dbExpress
SQLMonitor is used in the project to track SQL statements.
delete from CUSTOMER
where
CUST_NO = ? and
CUSTOMER = ? and
CONTACT_FIRST = ? and
CONTACT_LAST = ? and
PHONE_NO = ? and
ADDRESS_LINE1 = ? and
ADDRESS_LINE2 is null and
CITY = ? and
STATE_PROVINCE = ? and
COUNTRY = ? and
POSTAL_CODE = ? and
ON_HOLD = ? and
CITYSTATE = ?
The WHERE clause includes all fields listed in the SELECT statement, including
CITYSTATE. This DELETE will always fail because of the calculated field,
CITYSTATE is not a field in the base Customer table.
Changing the UpdateMode property from the default of upWhereAll to upWhereChanged
or upWhereAll prevents this type of error depending on the type of modification
performed. Either option will eliminate the column unknown error when modifying an
existing record. Below is an example of the SQL generated to update the Contact_First
field and UpdateMode is set to upWhereChanged. The WHERE clause includes the
primary key (Cust_No) and all fields changed, in this example just Contact_First.
update CUSTOMER set
CONTACT_FIRST = ?
where
CUST_NO = ? and
CONTACT_FIRST = ?
Using upWhereChanged when attempting to delete a record still causes the error. This is
due to the inclusion of all fields in the WHERE clause. To get a delete to work, you need
to set the UpdateMode to upWhereKeyOnly. With this setting, only the Cust_No field
will be used in the WHERE clause as shown below.
delete from CUSTOMER
where
CUST_NO = ?
An alternative to solving the CityState unknown column problem is to use the TField
ProviderFlags. The dataset used by the provider are the TFields that need to be changed.
Setting all the ProviderFlags to False for CityState prevents its usage in the WHERE
clause for any SQL statement. An UPDATE statement where the Contact_First is
modified now is as follows:
update CUSTOMER set
CONTACT_FIRST = ?
where
CUST_NO = ? and
CUSTOMER = ? and
CONTACT_FIRST = ? and
CONTACT_LAST = ? and
PHONE_NO is null and
ADDRESS_LINE1 = ? and
ADDRESS_LINE2 is null and
CITY = ? and
STATE_PROVINCE = ? and
COUNTRY = ? and
POSTAL_CODE = ? and
ON_HOLD is null
The ProviderFlags can also be used to reduce the field used in the WHERE clause, even
to the point of specifying just the primary key.
and sometimes business rule logic is more sophisticated than the server programming
language can support.
In a multi-tier application you have a third option which is to program constraints in the
application server. Here you can use the power of DataSnap to implement sophisticated
logic for business rules. You can take the errors generated by database server constraints
and change the messages sent back to the client, you and also add constraint routines to
further validate data sent from the client before committing it to the database server. In a
two-tier application, this same technique can be used but the code resides in the client
application.
In reality, you will use constraints at all three levels. Some of the database-server level
constraints can be passed directly to client applications from the server. You can also
have application server constraints available in the client application each time a
connection is made. This provides the best of all three levels.
For this section, the EMPLOYEE.GDB InterBase database that ships with Delphi is used
as the data source. Most of the examples use the SALES table. The server and client
projects are found in the BusRulesCnstrnts directory of the code examples.
Required property is set to True when a TTable is used and the database
schema specifies a field cannot contain a null. It will be set to False if a
TQuery is used as the dataset.
Required and ReadOnly properties set on the application server are copied
to the client persistent TFields on the initial TField creation in the client.
If you change the TField settings for these two fields on the server, you
will need to either manually change the values for the CDS TFields or
delete and re-add them to the CDS.
When the Required property is set to True, you will get one of two error
messages displayed when the record is saved and no value exists in the
field:
1. Field value required
Required property is set to True on the server
No persistent TField for CDS or field allows null values and
you manually set Required to True
2. Field <fldName> must have a value
Field cannot contain null values as defined in table create
Unless the Required property is explicitly set to False in the
CDS
If the Required property is set to True on the CDS, but the table definition
allows nulls or the server TField has Required set to False
If you want field-level validation, you need to set the CustomConstraint
property. The message displayed will be the value of the
ConstraintErrorMessage property.
specified in the DataSet property of the TDataSetProvider. The following are the TField
properties that can be used and are passed to the client based on the TField data type:
o
o
o
o
o
o
o
o
o
o
Alignment
Currency
DisplayFormat
DisplayLabel
DisplayWidth
EditFormat
EditMask
MaxValue
MinValue
Visible
Three DataSetProvider events can be used for constraint. These three are listed in Table
7 with a brief description.
Table 7 TDataSetProvider events used to program constraints
Event
Description
BeforeUpdateRecord
Before each record is applied to the remote dataset
Before the provider actually applies updates to the database of the entire
OnUpdateData
dataset received from the client
OnUpdateError
Errors on applying updates to database server
In this section each of the three events are covered with some simple examples on how
they could be used.
Using BeforeUpdateRecord
The BeforeUpdateRecord event is used when you want to validate data on an individual
record basis before changes are applied to the database. It can also be used to modify the
client data before being saved. Below are the procedure parameters and a brief
description of what is passed to the event:
BeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TClientDataSet;
UpdateKind: TUpdateKind;
var Applied: Boolean);Sender
TDataSetProvider the triggered the event
SourceDS
the source data
DeltaDS
data packet from client
UpdateKind
type of update: ukInsert, ukModify, ukDelete
Applied
indicator that you set if you apply the update in the
BeforeUpdateRecord code yourself
Dont use Edit and Post methods to modify and save data in the record
Use TField NewValue property to change the value of a field
Use TField OldValue to get original value
Use VarIsNull(Field.NewValue) to determine if the field is NULL
Use VarIsEmpty(Field.NewValue) to determine if the field has not changed since
it was retrievedIn the demo application, the BeforeUpdateRecord is used to
validate the order date field. If the order date is not empty, its value is compared to the
ship date to ensure the order date is not after the ship date. To simplify the example, only
this condition is checked, there is no check to see if the ship date is modified. The code
below shows the procedure used.
procedure TBusRulesContstraints.dspSalesBeforeUpdateRecord(Sender:
TObject;
SourceDS: TDataSet; DeltaDS: TClientDataSet; UpdateKind: TUpdateKind;
var Applied: Boolean);
begin
{ NOTE: This example is only checking for changes to Order Date. }
if UpdateKind <> ukDelete then
{ Check to see if Order Date is NULL using VarIsNull }
if ((VarIsNull(DeltaDS.FieldByName('ORDER_DATE').NewValue) =
False) and
{ Check to see if Order Date has changed using VarIsEmpty }
(VarIsEmpty(DeltaDS.FieldByName('ORDER_DATE').NewValue) =
False)) then
if DeltaDS.FieldByName('ORDER_DATE').NewValue >
DeltaDS.FieldByName('SHIP_DATE').OldValue then
raise Exception.Create('Order Date cannot be after ' +
'Ship Date. <BeforeUpdateRecord>');
end;
The first comparison is to see if the action on the record was either an insert or update,
there is no need to validate if the record is to be deleted. The UpdateKind parameter is
used to see if the action to be taken is not ukDelete (meaning either an insert or update is
to be performed). If this is true, the NewValue property of the ORDER_DATE TField is
used. NewValue is unassigned when no modification is made to the field. VarIsNull and
VarIsEmpty are used to ensure the value is assigned and not null. If this condition is true,
the OldValue property of the SHIP_DATE TField is used to compare to the updated
ORDER_DATE value. If the order date is after the ship date, an exception is raised.
In this demo, OldValue is used because there is no checking to see if the SHIP_DATE
field has also changed. In a complete validation, you would need to check to see if both
fields changed. OldValue will always be the value that is currently in the client field.
You cannot use the Value property. This property will be null when there is no change to
the field, therefore the need for the OldValue property.
OnUpdateData(Sender: TObject;
DataSet: TClientDataSet);
Sender
DataSet
Unlike the BeforeUpdateRecord event, there is no parameter that indicates the type of
update. Since you have the entire dataset, you need to look at the status for each
individual record. To determine the status for the record, UpdateStatus, a property of
TClientDataSet is used. Table 8 lists the possible values for UpdateStatus with a brief
description.
Table 8 - UpdateStatus options and their description
Value
Description
usUnmodified
Record has not been modified.
usInserted
Record is an insert.
Record has been modified. Note: this record is the second of a matching pair.
usModified
The first will have a status of usUnmodified.
usDeleted
Record to be deleted.
Before looking at a code example of the OnUpdateData event, the contents of what is
passed should be covered.
A change log is maintained by the CDS for each insert, update, and delete. The CDS
Delta property contains all records in the change log. A separate record is added to the
log for each insert and delete. When an existing record is modified, two records are
entered in the log. The first record, with a status of usUnmodified, contains all field
values for the record before any modification was made. The second record, with a status
of usModified, contains only the field values that have changed. All non-modified fields
are null in the second record. The CDS Delta property is what the provider receives as
the DataSet property in the OnUpdateData event.
In the demo client project, the second tab displays the contents of the change log. Figure
14 shows the log after performing an edit, insert, delete, and a second edit to a different
record. The UpdateStatus field does not exist in the data, it is a calculated field used to
display the status of each record.
The CDS cdsSales contains the data from the provider. The CDS for showing the change
log is named cdsSalesDelta. When the second tab is selected, the Delta property of
cdsSales is assigned to the Data property of cdsSalesDelta. The try except block is used
to display a simple message when no modification has been made to the data.
To demonstrate the use of OnUpdateData, a similar validation used in the
BeforeUpdateRecord on the ORDER_DATE field is replicated. The code for the demo
OnUpdateData event is shown below.
procedure TBusRulesContstraints.dspSalesUpdateData(Sender: TObject;
DataSet: TClientDataSet);
var
Old_ShipDate: TDateTime;
begin
with DataSet do
begin
First;
then
Date. ' +
'<OnUpdateData>');
end;
Next; // Go the next record in the delta packet
end;
end;
end;
A while loop is required to process the entire dataset. You start at the first record and
check its status. If it is a non-modified record, you know that this record is the first of a
matching pair. The first record contains the original record values; therefore the
SHIP_DATE needs to be saved. To get the updated field values, Next is called for the
dataset to move the modified record. If the ORDER_DATE NewValue is not empty,
then compare it to the saved SHIP_DATE value. An exception is raised if the order date
is after the ship date.
In this example, only modified records are checked. If a record is to be deleted or
inserted, no validation is done. The while loop forces all records to be checked until EOF
or the exception is raised.
OnUpdateError(Sender: TObject;
DataSet: TClientDataSet;
E: EUpdateError;
UpdateKind: TUpdateKind;
var Response: TResolverResponse);
Sender
DataSet
E
UpdateKind
Response
Like the previous two events, you use NewValue and OldValue TField properties. You
also use the CurValue property, which indicates the current value in the database. This
allows you to see the currently stored value, the original value the client received, and the
updated value the client wants to apply.
The exception parameter has a property named OriginalException. It allows you to get
the original exception class. If you are using the BDE and the original exception class is
EDBEngineError you can use ErrorCode property to get the error code value, otherwise
you have to parse the message.
It is important to note that you should not change the current record pointer in the
OnUpdateError event.
The Response parameter has a different default value depending on the parameter
supplied in the ApplyUpdates. If the maximum number of errors allowed is zero,
Response defaults to rrAbort otherwise rrSkip is assigned the default.
In the demo server application, OnUpdateError is used to change the error messages
returned from the database server. With Interbase, there is no specific error number to
evaluate for each individual error. You need to look at the error text and determine from
it what error is raised. Two examples are used in the demo: INTEG_65 is the error when
the ORDER_STATUS field does not equal new, open, shipped, or waiting and
INTEG_67 is the error when the order date is after the ship date.
The error message
General SQL error.
Operation violates CHECK constraint INTEG_67 on view or table SALES
is the default error message for an order date that is after the ship date. Most users would
not be able to identify the problem with the database error message.
To improve the information displayed in the error messages, the code below is used for
the OnUpdateError event:
procedure TBusRulesContstraints.dspSalesUpdateError(Sender: TObject;
DataSet: TClientDataSet; E: EUpdateError; UpdateKind: TUpdateKind;
var Response: TResolverResponse);
begin
if E.OriginalException is EDatabaseError then
begin
if Pos('INTEG_65',E.OriginalException.Message) <> 0 then
E.Message :=('Order Status must be new, open, ' +
'shipped, or waiting <OnUpdateError>')
else if Pos('INTEG_67',E.OriginalException.Message) <> 0 then
E.Message := ('Order Date cannot be after Ship Date. ' +
'<OnUpdateError>')
else
E.Message := (E.OriginalException.Message + '<OnUpdateError>');
end;
end;
The real issue is the SQL that is generated by the DSP. The first part of the statement is
as follows:
update Session se
where
The DataSetProvider attempts to create an UPDATE statement but the SQL statement
generated is incorrect and fails to execute. This empty field list generates an exception
and causes the update to fail. Even if you exclude the fields using ProviderFlags, the
error still persists. The solution to this situation is to use the BeforeUpdateRecord event
and cycle through all the fields in the datapacket for each record and exclude the record
from generating an update when no change is made to the actual data fields in the table.
Additional tables were added to the DBDEMOS database to be used for this and the next
example. These tables represent basic data for sessions at BorCon. Figure 15 shows the
tables and data types. Table 5 lists each table and how they are used.
Attendee
Speaker
AttendeeSession
TrackType
Session
SessionRoom
RoomList
NOTE: The design of these tables was for demonstration purposes only and do not
represent the exact relationships required to fully support all possible session,
speaker and attendee relationships. For example, each session only supports a
single speaker and is assigned to a single track. Only a subset of the data from
the Conference was entered, just enough to use in demonstrating the technique
being demonstrated.
The code for this example is found on the Empty Update tab of the project shown in
Figure 16. The requirement here is to display all the sessions and which room or rooms
they are going to use in the Convention Center. The RoomList table has RoomNo and
RoomName fields. The actual names used are A1, B1, etc. For BorCon, this is a
straightforward naming convention. In some hotels/convention centers the names of the
rooms are names like Rainer, Whidbey, or other non-numerical names. The intent of the
example is to show how you would support the saving of the RoomNo in the data but
display the room name in the form.
The SQL to retrieve the data from the Sessions table and create the calculated field is as
follows:
SELECT S.*, CAST(NULL as VARCHAR(100)) as RoomNames FROM Session S
Once the data is retrieved, the AfterOpen event on the ClientDataSet is used to translate
the room numbers into the full room description. The data is placed into the RoomNames
field.
Modifications to the rooms used by a session are done by clicking the ellipses button for
the row in the grid. This displays a dialog box where the rooms are selected and room
name list is placed into the RoomNames field and the room number list is assigned to the
RoomIds field.
Clicking the Open button on the Empty Update tab retrieves the data. If you immediately
click the ApplyUpdates the reconcile error dialog is displayed with the error message
Line 1: Incorrect syntax near 'se'. This is caused by the changes created in the CDS
AfterOpen. To prevent this error, code needs to be added to the DSP
BeforeUpdateRecord. Clicking the Enable BUR checkbox assigns the following code to
the DSP.
procedure TdmEmptyUpdate.dspCustomerBeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind; var Applied: Boolean);
var
bAllowApply: Boolean;
i: Integer;
begin
if UpdateKind = ukModify then
begin
bAllowApply := False;
for i := 0 to DeltaDS.FieldCount - 1 do
bAllowApply := bAllowApply or
((not VarIsClear(DeltaDS.Fields[i].NewValue)) and
(pfInUpdate in DeltaDS.Fields[i].ProviderFlags));
Applied := not bAllowApply;
end;
{NOTE: This is from an example by Jeff Overcash on TeamB }
end;
It should be first noted that this technique is based on an example from the Delphi forums
by Jeff Overcash. Jeff supplied this idea to a question raised on how to create a numeric
field that allows entry in the CDS.
The basic technique is to cycle through all the fields in the datapacket (DeltaDS) and
determines if the field is to be updated (pfInUpdate in the ProviderFlags property) and the
NewValue is assigned. If all fields are checked and there are no fields updated, then
there is no need to create an update and the Applied parameter is set to True.
An alternative to this method is to turn logging off before setting the RoomNames values,
modify the data, then turn logging back on. This is done by setting the CDS LogChanges
property to False, make the changes, then set LogChanges to True. This disables all
logging while RoomNames is assigned.
This technique can also be used when the SQL statement used to retrieve data in a one-toone relationship or a many-to-one relationship. It is possible that fields from the one
side of the relationship are updated thus cause the same condition as described above.
This same technique can be used to determine the base table has no updates but the joined
data has to be updated with an INSERT, DELETE, or UPDATE statement generated by
your code.
Summary
Contents
dbExpress and dbExpress.NET allow both Win32 and .NET Borland developers to easily
connect to many of the most popular databases used today. The power of both the
DataSetProvider and ClientDataSet enable custom control over the data retrieval and data
modification to support the needs of todays application requirement.