Using a client dataset with a provider

A client dataset uses a provider to supply it with data and apply updates when

  • It caches updates from a database server or another dataset.
  • It represents the data in an XML document.
  • It stores the data in the client portion of a multi-tiered application.

For any client dataset other than TClientDataSet, this provider is internal, and so not directly accessible by the application. With TClientDataSet, the provider is an external component that links the client dataset to an external source of data.

An external provider component can reside in the same application as the client dataset, or it can be part of a separate application running on another system. For more information about provider components, see Chapter 30, "Using provider components." For more information about applications where the provider is in a separate application on another system, see Chapter 31, "Creating multi-tiered applications."

When using an (internal or external) provider, the client dataset always caches any updates. For information on how this works, see "Using a client dataset to cache updates" on page 29-16.

The following topics describe additional properties and methods of the client dataset that enable it to work with a provider.

Specifying a provider

Unlike the client datasets that are associated with a data access mechanism, TClientDataSet has no internal provider component to package data or apply updates. If you want it to represent data from a source dataset or XML document, therefore, you must associated the client dataset with an external provider component.

The way you associate TClientDataSet with a provider depends on whether the provider is in the same application as the client dataset or on a remote application server running on another system.

• If the provider is in the same application as the client dataset, you can associate it with a provider by choosing a provider from the drop-down list for the ProviderName property in the Object Inspector. This works as long as the provider has the same Owner as the client dataset. (The client dataset and the provider have the same Owner if they are placed in the same form or data module.) To use a local provider that has a different Owner, you must form the association at runtime using the client dataset's SetProvider method

If you think you may eventually scale up to a remote provider, or if you want to make calls directly to the IAppServer interface, you can also set the RemoteServer property to a TLocalConnection component. If you use TLocalConnection, the TLocalConnection instance manages the list of all providers that are local to the application, and handles the client dataset's IAppServer calls. If you do not use TLocalConnection, the application creates a hidden object that handles the IAppServer calls from the client dataset.

• If the provider is on a remote application server, then, in addition to the

ProviderName property, you need to specify a component that connects the client dataset to the application server. There are two properties that can handle this task: RemoteServer, which specifies the name of a connection component from which to get a list of providers, or ConnectionBroker, which specifies a centralized broker that provides an additional level of indirection between the client dataset and the connection component. The connection component and, if used, the connection broker, reside in the same data module as the client dataset. The connection component establishes and maintains a connection to an application server, sometimes called a "data broker". For more information, see "The structure of the client application" on page 31-4.

At design time, after you specify RemoteServer or ConnectionBroker, you can select a provider from the drop-down list for the ProviderName property in the Object Inspector. This list includes both local providers (in the same form or data module) and remote providers that can be accessed through the connection component.

Note If the connection component is an instance of TDCOMConnection, the application server must be registered on the client machine.

At runtime, you can switch among available providers (both local and remote) by setting ProviderName in code.

Requesting data from the source dataset or document

Client datasets can control how they fetch their data packets from a provider. By default, they retrieve all records from the source dataset. This is true whether the source dataset and provider are internal components (as with TBDEClientDataSet, TSimpleDataSet, and TIBClientDataSet), or separate components that supply the data for TClientDataSet.

You can change how the client dataset fetches records using the PacketRecords and FetchOnDemand properties.

Incremental fetching

By changing the PacketRecords property, you can specify that the client dataset fetches data in smaller chunks. PacketRecords specifies either how many records to fetch at a time, or the type of records to return. By default, PacketRecords is set to -1, which means that all available records are fetched at once, either when the client dataset is first opened, or when the application explicitly calls GetNextPacket. When PacketRecords is -1, then after the client dataset first fetches data, it never needs to fetch more data because it already has all available records.

To fetch records in small batches, set PacketRecords to the number of records to fetch. For example, the following statement sets the size of each data packet to ten records:

ClientDataSetl.PacketRecords := 10;

This process of fetching records in batches is called "incremental fetching". Client datasets use incremental fetching when PacketRecords is greater than zero.

To fetch each batch of records, the client dataset calls GetNextPacket. Newly fetched packets are appended to the end of the data already in the client dataset. GetNextPacket returns the number of records it fetches. If the return value is the same as PacketRecords, the end of available records was not encountered. If the return value is greater than 0 but less than PacketRecords, the last record was reached during the fetch operation. If GetNextPacket returns 0, then there are no more records to fetch.

Warning Incremental fetching does not work if you are fetching data from a remote provider on a stateless application server. See "Supporting state information in remote data modules" on page 31-19 for information on how to use incremental fetching with stateless remote data modules.

Note You can also use PacketRecords to fetch metadata information about the source dataset. To retrieve metadata information, set PacketRecords to 0.

Fetch-on-demand

Automatic fetching of records is controlled by the FetchOnDemand property. When FetchOnDemand is True (the default), the client dataset automatically fetches records as needed. To prevent automatic fetching of records, set FetchOnDemand to False. When FetchOnDemand is False, the application must explicitly call GetNextPacket to fetch records.

For example, Applications that need to represent extremely large read-only datasets can turn off FetchOnDemand to ensure that the client datasets do not try to load more data than can fit into memory. Between fetches, the client dataset frees its cache using the EmptyDataSet method. This approach, however, does not work well when the client must post updates to the server.

The provider controls whether the records in data packets include BLOB data and nested detail datasets. If the provider excludes this information from records, the FetchOnDemand property causes the client dataset to automatically fetch BLOB data and detail datasets on an as-needed basis. If FetchOnDemand is False, and the provider does not include BLOB data and detail datasets with records, you must explicitly call the FetchBlobs or FetchDetails method to retrieve this information.

Getting parameters from the source dataset

There are two circumstances when the client dataset needs to fetch parameter values:

  • The application needs the value of output parameters on a stored procedure.
  • The application wants to initialize the input parameters of a query or stored procedure to the current values on the source dataset.

Client datasets store parameter values in their Params property. These values are refreshed with any output parameters when the client dataset fetches data from the source dataset. However, there may be times a TClientDataSet component in a client application needs output parameters when it is not fetching data.

To fetch output parameters when not fetching records, or to initialize input parameters, the client dataset can request parameter values from the source dataset by calling the FetchParams method. The parameters are returned in a data packet from the provider and assigned to the client dataset's Params property.

At design time, the Params property can be initialized by right-clicking the client dataset and choosing Fetch Params.

Note There is never a need to call FetchParams when the client dataset uses an internal provider and source dataset, because the Params property always reflects the parameters of the internal source dataset. With TClientDataSet, the FetchParams method (or the Fetch Params command) only works if the client dataset is connected to a provider whose associated dataset can supply parameters. For example, if the source dataset is a table type dataset, there are no parameters to fetch.

If the provider is on a separate system as part of a stateless application server, you can't use FetchParams to retrieve output parameters. In a stateless application server, other clients can change and rerun the query or stored procedure, changing output parameters before the call to FetchParams. To retrieve output parameters from a stateless application server, use the Execute method. If the provider is associated with a query or stored procedure, Execute tells the provider to execute the query or stored procedure and return any output parameters. These returned parameters are then used to automatically update the Params property.

Passing parameters to the source dataset

Client datasets can pass parameters to the source dataset to specify what data they want provided in the data packets it sends. These parameters can specify

  • Input parameter values for a query or stored procedure that is run on the application server
  • Field values that limit the records sent in data packets

You can specify parameter values that your client dataset sends to the source dataset at design time or at runtime. At design time, select the client dataset and double-click the Params property in the Object Inspector. This brings up the collection editor, where you can add, delete, or rearrange parameters. By selecting a parameter in the collection editor, you can use the Object Inspector to edit the properties of that parameter.

At runtime, use the CreateParam method of the Params property to add parameters to your client dataset. CreateParam returns a parameter object, given a specified name, parameter type, and datatype. You can then use the properties of that parameter object to assign a value to the parameter.

For example, the following code adds an input parameter named CustNo with a value of 605:

with ClientDataSet1.Params.CreateParam(ftInteger, 'CustNo', ptInput) do Aslnteger := 605;

If the client dataset is not active, you can send the parameters to the application server and retrieve a data packet that reflects those parameter values simply by setting the Active property to True.

Sending query or stored procedure parameters

When the client dataset's CommandType property is ctQuery or ctStoredProc, or, if the client dataset is a TClientDataSet instance, when the associated provider represents the results of a query or stored procedure, you can use the Params property to specify parameter values. When the client dataset requests data from the source dataset or uses its Execute method to run a query or stored procedure that does not return a dataset, it passes these parameter values along with the request for data or the execute command. When the provider receives these parameter values, it assigns them to its associated dataset. It then instructs the dataset to execute its query or stored procedure using these parameter values, and, if the client dataset requested data, begins providing data, starting with the first record in the result set.

Note Parameter names should match the names of the corresponding parameters on the source dataset.

Limiting records with parameters

If the client dataset is

  • a TClientDataSet instance whose associated provider represents a TTable or TSQLTable component
  • a TSimpleDataSet or a TBDEClientDataSet instance whose CommandType property is ctTable then it can use the Params property to limit the records that it caches in memory. Each parameter represents a field value that must be matched before a record can be included in the client dataset's data. This works much like a filter, except that with a filter, the records are still cached in memory, but unavailable.

Each parameter name must match the name of a field. When using TClientDataSet, these are the names of fields in the TTable or TSQLTable component associated with the provider. When using TSimpleDataSet or TBDEClientDataSet, these are the names of fields in the table on the database server. The data in the client dataset then includes only those records whose values on the corresponding fields match the values assigned to the parameters.

For example, consider an application that displays the orders for a single customer. When the user identifies the customer, the client dataset sets its Params property to include a single parameter named CustID (or whatever field in the source table is called) whose value identifies the customer whose orders should be displayed. When the client dataset requests data from the source dataset, it passes this parameter value. The provider then sends only the records for the identified customer. This is more efficient than letting the provider send all the orders records to the client application and then filtering the records using the client dataset.

Handling constraints from the server

When a database server defines constraints on what data is valid, it is useful if the client dataset knows about them. That way, the client dataset can ensure that user edits never violate those server constraints. As a result, such violations are never passed to the database server where they would be rejected. This means fewer updates generate error conditions during the updating process.

Regardless of the source of data, you can duplicate such server constraints by explicitly adding them to the client dataset. This process is described in "Specifying custom constraints" on page 29-7.

It is more convenient, however, if the server constraints are automatically included in data packets. Then you need not explicitly specify default expressions and constraints, and the client dataset changes the values it enforces when the server constraints change. By default, this is exactly what happens: if the source dataset is aware of server constraints, the provider automatically includes them in data packets and the client dataset enforces them when the user posts edits to the change log.

Note Only datasets that use the BDE can import constraints from the server. This means that server constraints are only included in data packets when using TBDEClientDataSet or TClientDataSet with a provider that represents a BDE-based dataset. For more information on how to import server constraints and how to prevent a provider from including them in data packets, see "Handling server constraints" on page 30-13.

Note For more information on working with the constraints once they have been imported, see "Using server constraints" on page 25-23.

While importing server constraints and expressions is an extremely valuable feature that helps an application preserve data integrity, there may be times when it needs to disable constraints on a temporary basis. For example, if a server constraint is based on the current maximum value of a field, but the client dataset uses incremental fetching, the current maximum value for a field in the client dataset may differ from the maximum value on the database server, and constraints may be invoked differently. In another case, if a client dataset applies a filter to records when constraints are enabled, the filter may interfere in unintended ways with constraint conditions. In each of these cases, an application may disable constraint-checking.

To disable constraints temporarily, call the DisableConstraints method. Each time DisableConstraints is called, a reference count is incremented. While the reference count is greater than zero, constraints are not enforced on the client dataset.

To reenable constraints for the client dataset, call the dataset's EnableConstraints method. Each call to EnableConstraints decrements the reference count. When the reference count is zero, constraints are enabled again.

Tip Always call DisableConstraints and EnableConstraints in paired blocks to ensure that constraints are enabled when you intend them to be.

Refreshing records

Client datasets work with an in-memory snapshot of the data from the source dataset. If the source dataset represents server data, then as time elapses other users may modify that data. The data in the client dataset becomes a less accurate picture of the underlying data.

Like any other dataset, client datasets have a Refresh method that updates its records to match the current values on the server. However, calling Refresh only works if there are no edits in the change log. Calling Refresh when there are unapplied edits results in an exception.

Client datasets can also update the data while leaving the change log intact. To do this, call the RefreshRecord method. Unlike the Refresh method, RefreshRecord updates only the current record in the client dataset. RefreshRecord changes the record value originally obtained from the provider but leaves any changes in the change log.

Warning It is not always appropriate to call RefreshRecord. If the user's edits conflict with changes made to the underlying dataset by other users, calling RefreshRecord masks this conflict. When the client dataset applies its updates, no reconcile error occurs and the application can't resolve the conflict.

In order to avoid masking update errors, you may want to check that there are no pending updates before calling RefreshRecord. For example, the following AfterScroll refreshes the current record every time the user moves to a new record (ensuring the most up-to-date value), but only when it is safe to do so.:

procedure TForm1.ClientDataSet1AfterScroll(DataSet: TDataSet); begin if ClientDataSetl.UpdateStatus = usUnModified then ClientDataSetl.RefreshRecord;

end;

Communicating with providers using custom events

Client datasets communicate with a provider component through a special interface called IAppServer. If the provider is local, IAppServer is the interface to an automatically-generated object that handles all communication between the client dataset and its provider. If the provider is remote, IAppServer is the interface to a remote data module on the application server, or (in the case of a SOAP server) an interface generated by the connection component.

TClientDataSet provides many opportunities for customizing the communication that uses the IAppServer interface. Before and after every IAppServer method call that is directed at the client dataset's provider, TClientDataSet receives special events that allow it to communicate arbitrary information with its provider. These events are matched with similar events on the provider. Thus for example, when the client dataset calls its ApplyUpdates method, the following events occur:

1 The client dataset receives a BeforeApplyUpdates event, where it specifies arbitrary custom information in an OleVariant called OwnerData.

2 The provider receives a BeforeApplyUpdates event, where it can respond to the OwnerData from the client dataset and update the value of OwnerData to new information.

3 The provider goes through its normal process of assembling a data packet (including all the accompanying events).

4 The provider receives an AfterApplyUpdates event, where it can respond to the current value of OwnerData and update it to a value for the client dataset.

5 The client dataset receives an AfterApplyUpdates event, where it can respond to the returned value of OwnerData.

Every other IAppServer method call is accompanied by a similar set of BeforeXXX and AfterXXX events that let you customize the communication between client dataset and provider.

In addition, the client dataset has a special method, DataRequest, whose only purpose is to allow application-specific communication with the provider. When the client dataset calls DataRequest, it passes an OleVariant as a parameter that can contain any information you want. This, in turn, generates an is the OnDataRequest event on the provider, where you can respond in any application-defined way and return a value to the client dataset.

Overriding the source dataset

The client datasets that are associated with a particular data access mechanism use the CommandText and CommandType properties to specify the data they represent. When using TClientDataSet, however, the data is specified by the source dataset, not the client dataset. Typically, this source dataset has a property that specifies an SQL statement to generate the data or the name of a database table or stored procedure.

If the provider allows, TClientDataSet can override the property on the source dataset that indicates what data it represents. That is, if the provider permits, the client dataset's CommandText property replaces the property on the provider's dataset that specifies what data it represents. This allows TClientDataSet to specify dynamically what data it wants to see.

By default, external provider components do not let client datasets use the CommandText value in this way. To allow TClientDataSet to use its CommandText property, you must add poAllowCommandText to the Options property of the provider. Otherwise, the value of CommandText is ignored.

Note Never remove poAllowCommandText from the Options property of TBDEClientDataSet or TIBClientDataSet. The client dataset's Options property is forwarded to the internal provider, so removing poAllowCommandText prevents the client dataset from specifying what data to access.

The client dataset sends its CommandText string to the provider at two times:

  • When the client dataset first opens. After it has retrieved the first data packet from the provider, the client dataset does not send CommandText when fetching subsequent data packets.
  • When the client dataset sends an Execute command to provider.

To send an SQL command or to change a table or stored procedure name at any other time, you must explicitly use the IAppServer interface that is available as the AppServer property. This property represents the interface through which the client dataset communicates with its provider.

Was this article helpful?

+3 -4

Responses

  • marko
    How to use PacketRecords and FetchOnDemand in clientdataset in delphi?
    9 years ago
  • Peter
    How to use Tlocalconnection?
    7 years ago
  • Adaldrida
    How to use clientdataset commandtext delphi?
    7 years ago
  • elina
    When do i use clientdataset post delphi?
    7 years ago

Post a comment