Sei sulla pagina 1di 37

Getting Started with dbExpress

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:

Data Connectivity Overview


dbExpress Introduction
Basic Data Connectivity
Sending Updates to a Database The Basics
Reconciling Errors
Master / Detail NestedDataSets
Setting UpdateMode and ProviderFlags properties
DataSetProvider Properties and Events
Empty Field List for Update Statement
Summary

Data Connectivity Overview


Contents
Borland RAD tools provide connectivity to a variety of data file formats. You can
connect to virtually any type of data if a driver is available for the data format.
dbExpress is a set of lightweight, SQL drivers for InterBase, Microsoft SQL Server,
MYSQL, Oracle, SQL AnyWhere and DB2.
A basic understanding of the foundational concepts of data connectivity is essential
before efficiently using many of the database features. These concepts include the
definition of terminology and the basic steps to display data in forms. To view and
modify data, you need to define the source of the data you intend to use, how to display

the data at runtime, and how to connect the two. This is done using a DataSet,
DataSource, and data-aware controls.

Figure 1: Basic requirements to display data in a form


Figure 1 shows the dependencies between a form and the data displayed in the form. The
data files are defined using a DataSet component. The data can be in a table, the result of
a query, or defined by a stored procedure in a SQL database server. The form contains
data-aware components that display the data. A DataSource is the component used to
connect data-aware components to DataSet components.

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:

Forward movement only, cannot use Last and Prior methods


No editing of data
No filter support

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.

Figure 2: dbExpress components in Delphi 7

Figure 3: dbExpress components in Delphi 2005


Table 1 dbExpress components
Component Name
Description
SQLConnection
Defines a connection to a database, similar to TDatabase
SQLDataSet
General-purpose unidirectional dataset that executes the
SQL statement defined by the CommandText property.
This can be a SELECT statement that returns a dataset, a
SQL statement that does not return data or executes a
stored procedure.
SQLQuery
Supports SQL statements to be executed that return a
unidirectional result set or update data or database
schemas.
SQLStoredProc
Executes a stored procedure. If there is a result set, it is
unidirectional.
SQLTable
Provides unidirectional access to a database table.
SQLMonitor
Used to intercept and display messages passed between a
SQLConnection and database.
SimpleClientDataSet Combines a SQLDataSet and DataSetProvider internally
in the component to support data cached in memory.
The SQLConnection is where the database connectivity is defined. One of the next four
components in the list is used to specify the data to be retrieved if you intend to use a
unidirectional cursor. If data is to be edited and browsed, the SimpleClientDataSet needs
to be used, or a ClientDataSet component which is covered later. In the next section the
basics of retrieving data using dbExpress is covered.

Basic Data Connectivity


Contents

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.

Figure 4: dbExpress Connection Editor dialog Delphi 7

Figure 5: dbExpress Connection Editor dialog Delphi 2005


In this dialog you select the database drive, connection name and modify properties used
to connect to the database. The Driver Name combo box specifies which connections are
displayed in the Connection Name list box. By default, all defined connections are listed.
Changing Driver Name value reduces the Connection Name listing to only those for the
selected driver. The Connection Settings vary depending on the driver selected. Any of
the entries in the Value column can be modified. You cannot enter new Key values or
delete any of the existing ones.

Selecting component to retrieve data


There are four main unidirectional dataset components on the dbExpress component
palette: SQLDataSet, SQLQuery, SQLStoredProc and SQLTable. The last three are
similar to the BDE, ADO and InterBase Express components. The SQLDataSet
component is a general-purpose component and is the recommended component to use.
All four components have a SQLConnection property that links to a SQLConnection
component.
The SQL statement to execute for a SQLDataSet is defined in the CommandText
property. You can enter or paste an SQL statement into the property or click the ellipses
button to display a CommandText Editor dialog to select from the list of tables and fields.
Figure 6 shows a completed dialog selecting all rows and columns from the CUSTOMER
table.

Figure 6: CommandText Editor in Delphi 7 and Delphi 2005


One of easiest components to use for displaying data is the DBGrid. It is located on the
Data Controls tab. If you attempt to connect a DataSource and a DBGrid to a
unidirectional dataset, you get an exception raised with the text Operation not allowed
on unidirectional dataset. A grid requires the ability to scroll forwards and backwards
through the dataset, which cannot be done with a unidirectional dataset. If you connect
other data-aware components (e.g. DBEdit), the exception is not raised until you attempt
to go to the prior or last record in the dataset.

Using the SimpleClientDataSet component in Delphi


The SimpleClientDataSet component is a single component to use for bi-directional
movement through a result set. This component uses an internal SQLDataSet and
TDataSetProvider for retrieving data and also supports data modification. The
DataSetProvider creates datapackets based on the SQL statement defined by the
CommandText property and used by the SQLDataSet. The datapackets are cached in
memory. When edits are sent back to the database, the DataSetProvider processes
datapackets containing just the modified records; it creates the appropriate SQL
statements that are sent to the database. Any error records returned from the database are
packaged by the DataSetProvider and made available for review in the
SimpleClientDataSet. This provide/resolve cycle continues until the records are accepted
by the database or the edits are canceled.
The SimpleClientDataSet requires the same two basic pieces of information as the
SQLDataSet which database connection and the SQL statement. The Connection
property is used to identify the SQLConnection to use. Alternatively, you can use the
ConnectionName property to select from the connection list. This will create an internal
SQLConnection component. The SQL statement is defined in the same manner as the
SQLDataSet component using the CommandText property. Figure 7 shows a basic
setup in design mode.

Figure 7: Basic dbExpress app in design mode


Retrieving data is done when the Active property of the SimpleClientDataSet is set to
True. This can be done at design time or when the form is created. In addition to issuing
SimpleClientDataSet.Active := True, the Open method (SimpleClientDataSet.Open)
can be used on the SimpleClientDataSet which is the equivalent of setting the Active
property to True. When Active is set to True, if the SQLConnection has not opened
access to the database, it will initiate a connection, attempt to set its Connected property
to True. The SQLConnection component also has a LoginPrompt property. By default
this property is True, indicating a login dialog will be displayed where a user name and
password can be entered before attempting to connect to the database. You can also
include those values in the connection parameters and remove the login dialog by setting
the LoginPrompt property to False.

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.

Connecting to data using a CDS and DSP


Both the CDS and DSP components are located on the Data Access component palette.
A SQLDataSet component from the dbExpress palette is used to define the result set.
The SQLConnection property of the SQLDataSet is assigned to the SQLConnection
component name. The SQLDataSet CommandText property defines the SQL statement.
The DataSet property of the DSP is set to the name of the SQLDataSet component. The
CDS component has a ProviderName property which is set to the DSP name. The last
property value change is to the DataSource DataSet property which should be assigned to
the CDS component name. Setting the CDS Active property to True (or using the Open
method) retrieves the data.
At first glance you might feel this is too much work compared to the
SimpleClientDataSet. As we cover more of the CDS and DSP features you gain a better
understanding of why you will use a DSP and CDS instead of the SimpleClientDataSet.

Sending Updates to a Database The Basics


Contents
Running the application shows you can move through the data, insert, edit and delete
data. When you close the application, no data is saved. You need to explicitly save the
data using the ApplyUpdates method. This method sends to the provider all inserted,
deleted, and modified records in the change log from the CDS. ApplyUpdates is a
function that takes a single parameter and returns an integer value.
The ApplyUpdates parameter indicates the maximum number of errors allowed before
the update process is canceled. A value of -1 indicates any number of errors can occur.
Any record that could not be posted is returned to the client in the data packet. When zero
is used, the first record that generates an error causes the entire update process to be
aborted. No records are removed from the change log; they are all retained by the CDS.
Also, no changes are written to the database. Any number greater than zero used for the
parameter indicates the maximum number of errors that can occur before the entire
update process is aborted. If there are less error records than the value of the parameter,
all records that were successfully saved are committed and the error records are still
retained by the CDS.
The value returned by ApplyUpdates indicates the number of records that could not be
posted. This allows you to check the result of an ApplyUpdates statement to conditionally
proceed based on the existence or non-existence of any errors.
When ApplyUpdates results in records being successfully saved, the Data property is
automatically updated. Any error records from the update along with the newly updated
records are placed in the updated Data property.

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

MessageDlg('There are no changes to undo.',mtInformation,[mbOK],0);

Refreshing client data


Two methods can be used to refresh the data without closing the dataset: Refresh and
RefreshRecord. Refresh returns the current values from the server. An exception is raised
if there are any records in the change log. It is best to check for any changes in the log
using ChangeCount first, then either cancels all changes or prompt users to okay clearing
the change log. RefreshRecord updates the current record to those currently in the
provider. RefreshRecord also carries a warning when there have been updates to the
record. Update conflicts can exist which means no reconcile error will occur even when a
conflict exists. Below is an example of using the Refresh method.
if cdsCustomer.ChangeCount > 0 then
begin
if MessageDlg('Current change log has not been applied.
' you want to cancel all changes?',mtConfirmation,
[mbYes,mbNo],0) = mrYes then
begin
dmMain.cdsCustomer.CancelUpdates;
dmMain.cdsCustomer.Refresh;
end
else
pbUpdate.SetFocus;
end
else
dmMain.cdsCustomer.Refresh;

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.

Limiting record display based on update status


Each record that is modified, inserted and deleted gets marked with a status value. The
StatusFilter property can be set to limit the types of records displayed. StatusFilter can be
set to one of more of the values in Table 2.
Table 2 StatusFilter options and descriptions
Value
Description
usUnmodified
Record has not been modified.
usInserted
Include records that have been inserted.
Limit records displayed to only those that have been modified. Two
records are displayed for each original record modified: the original
usModified
record along with a second record containing all modifications for
each posting.
usDeleted
Show records that have been deleted.
StatusFilter can be an empty set, one of the values, or multiple values. When StatusFilter
is an empty set, all records are displayed. If more than one value is used, the combination

of the options included in the set determines which records are displayed.

Reconciling Errors Using the Built-in Dialog


Contents
Another aspect of data entry is correcting records that cannot be saved. There are many
reasons for error records. Application validation, business rules, database constraints, and
data modified by another user are some of the main reasons. Reconciling these types of
errors can be handled in the application, the database, or a combination of both. This next
section introduces the basics of a built-in feature to provide error reconciliation in the
application.
The Object Repository contains a dialog box that provides the basic reconciliation code
already completed. Figure 8 shows the entry on the Dialogs tab in the Object Repository
for Delphi 7. The Object Repository for Delphi 2005 is shown in Figure 9.

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.

Figure 10: Reconcile Error Dialog at runtime


The dialog shows errors for records you have inserted, modified and attempted to delete.
The Update Type label indicates which of the three actions you took on the record. The
grid has a maximum of three columns. They are the value you are attempting to save
(Modified Value), the value of the field as it is currently in the database (Conflicting
Value) and the value when the client application originally received the data packet
(Original Value).
The grid changes display based on the type of problem, the update action, and which of
the two check boxes at the bottom of the dialog are checked. The first checkbox, Show
conflicting fields only, limits the records in the grid to only those fields that have been
changed and are in conflict with current database values. Figure 10 shows an example of
this selection. If the Show changed fields only checkbox is checked, the fields in all
records that have been changed are listed. If there is no conflicting value, the Conflicting
Value column contains <Unchanged>. If you attempt to delete a record that has already
been deleted by another user, only the Modified Value column is displayed. The radio
buttons in the upper right provide the type of action you can with the error record. Table
3 contains a description that is directly from the Delphi Help system.
Table 3 Action options in the Reconcile Error Dialog
Value
Description
Skips updating the record that raised the error condition, and leaves the
Skip
unapplied changes in the change log.
Abort
Aborts the entire reconcile operation.

Merge
Correct
Cancel
Refresh

Merges the updated record with the record on the server.


Replaces the current updated record with the value of the record in the
event handler.
Backs out all changes for this record, reverting to the original field values.
Backs out all changes for this record, replacing it with the current values
from the server.

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.

Master / Detail - NestedDataSets


Contents
There are two options for creating master/detail forms using ClientDataSets. You can use
the option of linking the results of two datasets after the data is retrieved using the
MasterSource and MasterFields properties or define the master detail relationship with
SQLDataSets and use the feature called nested datasets. The second option is what will
be described next.
The major difference with nested datasets over using the MasterSource and MasterFields
properties is a single DSP component encapsulates both the master and detail data. The
detail data is actually a column within the master dataset. Another feature of nested
datasets is all updates are wrapped in a single transaction. Figure 11 shows the form in
design mode.

Figure 11: Nested dataset master / detail setup in design mode


The first step to setup up master / detail relationship is to select two unidirectional dataset
components. In this example, two SQLDataSet components are used to define which
data to retrieve. A DataSource component is also needed to link the two datasets. The
first SQLDataSet (named sdsCustomer) retrieves all the records from the Customer table
using select * from Customer as the SQL statement. The DataSource component
(named dsCust) uses the SQLDataSet for the Customer data as its DataSet.
The second dataset (named sdsSales) is used for the detail data from the Sales table. To
link the two SQLDataSet components, the DataSource property of the Sales SQLDataSet
component is used along with the WHERE clause of the SQL statement. The DataSource
property is set to dsCust, the name of the DataSource component for the Customer table.
This defines the dataset to use as the master data. The linkage between the two tables is
defined in the SQL property using the WHERE clause as follows:
SELECT * FROM Orders
WHERE CUST_NO = :cust_no
In this example, all of the columns from the Sales table are selected. The rows are limited
to the value in the cust_no parameter. The key to using this technique is the parameter
names must match the names of the linking fields in the master table. In this example,
CUST_NO is the field name in both tables. Therefore, :cust_no is used as the parameter

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.

Figure 12: Fields Editor dialog box


A second CDS is used for the detail data. Instead of linking the CDS used for the detail
data to a provider, the DataSetField property of the ClientDataSet is used. This property
is set to the TDataSetField instantiated in the master data Fields Editor. When both CDS
components are opened the data for the Customer and Sales are displayed. Figure 13
shows an example of using nested datasets in a DBGrid.

Figure 13: Visual display for nested datasets


In the DBGrid the column for the detail dataset is displayed similar to memo and graphic
data. The text (DATASET) represents the values for each record in the dataset. An
ellipsis button is displayed with a double-click or F2 keypress. Clicking the button
displays another window containing the detail data displayed in a grid. This window will
always stay on top. You can also see in Figure 13 that the bottom grid in the form
displays the same data. You are not required to include the DataSetField in the master
table. The TField Visible property can be set to False and, using a second CDS and
DataSource, all detail can be displayed in a grid.
Another difference when ClientDataSets are used instead of SQLClientDataSets is in
saving the data. The Save Customer button on the form is the same as in example
projects using SQLClientDataSets. When changes are made here and the ApplyUpdates
is executed, all modifications for both the master and detail data sets are wrapped into a
single transaction and committed or rolled back based on the parameter passed in the
ApplyUpdates method.

Setting UpdateMode and ProviderFlags properties


Contents
Control of the type of optimistic locking that is used in an application is established using
the dataset UpdateMode property. The UpdateMode setting dictates the fields in the
dataset used to find the original record. Specifically, this property sets what fields are
used in the WHERE clause of the UPDATE and DELETE statement generated by the
provider. If the record is not found with the same values as when the record was
originally read, the update fails and an exception is raised. Where you set this property is
based on which components you use. Using ClientDataSets, the UpdateMode property is
on the DataSetProvider. If you use one of the combination CDS/DSP components (e.g.
SimpleClientDataset), those components have an UpdateMode property because of the
internal DataSetProvider. Table 4 lists the three values for UpdateMode and their
description.
Table 4 UpdateMode property settings
Value
Description
upWhereAll
Every column in the record is used to find the record. This is the
Delphi default.
upWhereChanged Only the columns in the record being edited that have changed are
used to find the record.
upWhereKeyOnly Only the columns that define the key are used to find the record.
Careful consideration should be given to the value selected for the UpdateMode. Each
setting has its good and bad side. The default, upWhereAll, is the most restrictive and has
the worst performance. Using all the fields in the WHERE clause to locate the original
record ensures the record has not been changed since it was originally read. If you get an
error message stating the query is too complex when you attempt an update, the SQL
database is indicating it cannot handle a WHERE clause with all the fields in the table
being used. The upWhereKeyOnly is the least restrictive. This allows anyone to change
any field (except the primary key) without consideration of what the original values
where. In between these two property options is the upWhereChanged. This can be a
problem where the values in multiple fields taken together have a specific meaning, like
in a multi-field primary key where users are allowed to change the values. Having a good
database design and a well thought out set of business rules assist in determining the
correct UpdateMode setting.
Testing this feature can be done by changing the UpdateMode property and running two
instances of an application making modifications in each.
The ProviderFlags properties for TField components are also available to use in
determining how an update is to be processed. Table 5 lists the possible options.
Table 4 UpdateMode property settings

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.

DataSetProvider Properties and Events


Contents
The TDataSetProvider (DSP) is the conduit between the database and the client. It
connects to a dataset component that provides the data from the database. The
DataSetProvider is responsible for packaging the data packets and sending them to the
client application when a request is received. It also receives the updates from the client
and attempts to post the data. Any error record that is generated is sent back to the client
application.
The expectation of those attending the session for this paper have a basic understanding
of how to use the DataSetProvider with a TDataSet descendant and a TClientDataSet.
The remainder of the introduction covers topics that are just beyond the basics of how
connect the DSP in an application.

How and Where to Define Constraints


In a multi-tier application there are many places where constraints can be placed client,
database server, or the application server. In a two-tier application it has to be at the
client or at the server. Each level has its advantages and disadvantages.
In you put constraints in the client application, you get instant notification of data
problems without server inquiries and you have decentralized the business rules. This
means the validation checks are programmed in each application. If you have more than
one application accessing the data, the constraints have to be replicated in each client
application. This becomes a maintenance nightmare.
Most database servers provide some level of constraint definition. This varies from
schema definition level to programming domains, rules, triggers and stored procedures
that are called to further test data modifications. Constraints defined in the database
provide the most centralized set of rules tied directly to the data. No matter how data is
modified, these rules are applied. There are two issues that result in the possibility of
augmenting server-based rules: cryptic error messages from the servers displayed to users

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.

TField properties automatically applied


Some of the TField properties are automatically passed to the client from the application
server. Most of the property values are defined at design time, with one of the properties
defined based on the type of TDataSet control used and the tables schema definition. To
use the automatic TField property feature, you must create persistent TField objects on
the TDataSet component. In this example, a query control for the SALES table has
persistent TFields with some of the properties modified as described in Table 5.
Table 5 TField properties automatically sent to client application
Property
Description
ReadOnly
Indicates the field cannot be modified
Required
Value must be placed in the field before the record can be posted
Default value for the field for any inserted records
DefaultExpression
Example: San Diego
Defines a custom field-level constraint using SQL Where clause type
expressions. Any validation rule added here augments the server-level
validation rules.
CustomConstraint
Example: x > 10 and x < 1000
The message that is displayed on the client machine when a value does not
ConstraintErrorMessage meet the custom constraint.

Example: Value must be between 10 and 1,000


All of these TField properties are available in single tier, two-tier, and multi-tier
applications, there is nothing built into DataSnap to utilize this capability. This set of
TField properties reduces the amount of coding required to support additional constraints
not supplied by the database server and allow for ease in setting custom error messages.
Error messages displayed based on violation of the properties differ based on the data
type and what property is set. The following are a few notes that you should be aware of
when using these properties:

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.

Additional TField properties used for constraints


The Options property on the TDataSetProvider supports the passing of additional TField
properties. Setting poIncFieldProps to True adds to the data packet TField properties

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

Using TDataSetProvider properties


In addition to including TField properties, the TDataSetProvider Options property also
includes the ability to set constraints for the entire dataset. Table 6 lists the options and a
brief description.
Table 6 TDataSetProvider Options property settings for dataset-level constraints
Property
Description
poReadOnly
Read-only result set
poDisableInserts
Client cannot insert records
poDisableEdits
Client cannot edit records
poDisableDeletes
Client cannot delete records
The poReadOnly setting prevents any modifications to the data. No visible indicator or
message is given to the user; the data just cannot be modified.
When poDisableInserts, poDisableEdits, or poDisableDeletes are set to True, an
exception is raised when users attempt to insert, modify, or delete records. The exception
message that is displayed contains the name of the CDS and Inserts are not allowed for
Inserts, Modifications are not allowed for edits, and Deletes are not allowed for
deletes.
If you do not want to have the exception dialog displayed, you need to trap for the
exception and change how the error is displayed. For example the OnEditError or
OnDeleteError events can be used for edits and deletes. In the client application, there
are events for the OnEditError and OnDeleteError that exist in the code but the events
have not been assigned. You can assign them yourself to see how you might trap for the
error and display a customized message.

Using TDataSetProvider events

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

Some dos and donts for this event:

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.

Using OnUpdateData event


The OnUpdateData event occurs once in the event stream as updates start to be applied in
the application server. You have access to all the records in the data packet that is
received from the client. Like the BeforeUpdateRecord, you can validate data, but you
have the entire dataset, not just one record. Below are the procedure parameters and a
brief description of what is passed to the event:

OnUpdateData(Sender: TObject;
DataSet: TClientDataSet);

Sender
DataSet

TDataSetProvider the triggered the event


data packet from client

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.

Figure 14: Client form displaying change log


The first two records are a matching pair. The first record contains all the values of the
record before any changes were made. The second record, with an UpdateStatus of
Modified, contains the values for every field in the record that changed. In this example,
the SHIP_DATE field is changed from 3/6/1991 to 3/7/1991.
The next two records are for an inserted and deleted record. On an insert, any field where
data is entered is placed in the change log. For deleted records, the entire original field
values are placed in the log.
The last two records are again a matching pair. Here two fields are changed, the
SALES_REP and the SHIP_DATE.
Displaying the change log requires an extra CDS in the application and a small amount of
code. The code that is used in the demo client is as follows:
procedure TfrmMain.PageControl1Change(Sender: TObject);
begin
if PageControl1.ActivePage = tbsDelta then
with dmMain do
try
cdsSalesDelta.Close;
cdsSalesDelta.Data := cdsSales.Delta;
cdsSalesDelta.Open;
except
MessageDlg('No delta records exist.',mtWarning,[mbOK],0);
end;
end;

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

while not EOF do


begin
if UpdateStatus = usUnmodified then
{ In this demo, only the modified records are being evaluated.
There will be two records in the data packet for each
modification.
The first record is the values of all fields in the record
before
any changes are made. The second record of the pair contains
fields that are modified. Any field that did not change is
null. }
begin
{ Save the old Ship_Date field from the first record to
compare
to the modified Order_Date field. }
Old_ShipDate := DataSet.FieldByName('SHIP_DATE').NewValue;
Next;
{ Check to see if the modified record has Order Date
modified. If so, then validate the difference and
raise an exception if invalid. }
if not VarIsEmpty(DataSet.FieldByName('ORDER_DATE').NewValue)
if FieldByName('ORDER_DATE').NewValue > Old_ShipDate then
raise Exception.Create('Order Date cannot be after Ship

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.

Using OnUpdateError event


The provider triggers the OnUpdateError event if an error condition exists in the update
of a record. In this event you can process the error, attempt to fix it or change the type of
message that is displayed to the client. Below are the procedure parameters and a brief
description of what is passed to the event:

OnUpdateError(Sender: TObject;
DataSet: TClientDataSet;
E: EUpdateError;
UpdateKind: TUpdateKind;
var Response: TResolverResponse);

Sender
DataSet
E
UpdateKind
Response

TDataSetProvider that triggered the event


temporary dataset to access error record
exception object
type of update
what action to take on the error when the event exits

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 OriginalExcpetion property of the E parameter is first checked to determine if an


EDatabaseError occurred. If this is true, the Message property is searched for a unique
text string matching each of the database errors where a different error message is to be
displayed. Using the Interbase integrity identifiers (INTEG_65 and INTEG_67) a
distinction can be made. If any other database error occurs that database error message is
displayed. Replacing the contents of the Message property of the EUpdateError
parameter fully supports the reconcile error dialog box from the Object Repository.

Empty field list for UPDATE statements


Contents
Some application requirements dictate the need to have fields available for input or
assignment on the client but the data in the fields do not update the database. These
fields can be created on the client side as internal calculated fields or they can be created
as calculated fields in the SQL statement retrieving the data. If these fields are the only
data that is modified in the CDS, executing ApplyUpdates results in a datapacket sent to
the database but there are no fields to place in the UPDATE statement which results in
the following error reconcile dialog message using SQL Server:
Incorrect syntax near 'se'
Running the InterBase DSP_Demo project generates the following error message in a
reconcile error dialog:
Token unknown - line 2, char -1
where

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.

Figure 15: Data tables used for final two topics


Table 5 Table name and description for example
Table
Description

Attendee
Speaker
AttendeeSession
TrackType
Session
SessionRoom
RoomList

Attendee names and id


Conference speaker list
Sessions selected for an attendee
Tracks offered (e.g. Delphi, JBuilder, StarTeam, CalilberRM, etc)
Available sessions, title, speaker, room, time, session type
Rooms used by each session
List of available rooms at the Convention Center

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.

Figure 16: Empty Update tab

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.

Potrebbero piacerti anche