Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #139
See Also: Dr.Bob's Delphi Papers and Columns

DataSnap Client
In this article, I'll write a DataSnap Client application for the DataSnap Server based on the Advantage Database Server from last month.

For the DataSnap Client application, we need a new project. This can be any kind of project, from a regular VCL application to an IntraWeb application or even a console application or whatever type you want to use. For this article, the easiest way to demonstrate the required steps and implementation details, is to use a VCL forms application. Adding one to the existing project group (with the RegistrationServer already included as first project) makes it easy to switch from working on one project to working on the other.
The new project is saved in RegistrationClient, with the main form in file ClientForm.pas

In order to connect to the DataSnap server in the RegistrationServer service, we need a TSQLConnection component. This can be placed on a data module, or – in this simple example – on the main form itself. This TSQLConnection component has a Driver property that must be set to “DataSnap". This will allow us to expand the Driver property, and configure all DataSnap specific settings like the BufferKbSize (32 by default), the CommunicationProtocol (set to tcp/ip), the HostName (set to localhost by default), the Port (211 by default), and the DSAuthUser and DSAuthPassword in case we included Authentication to the DataSnap Server.
Obviously, the HostName subproperty should point to the real server where the DataSnap Server (or service) is deployed, and not localhost which points to the same machine where the client runs; the development machine. Also, in real-world situations, the Port will be set to a different number than 211, and we must ensure that both the DataSnap Server and DataSnap Client agree on the same port number of course.
In our situation, the tcp/ip connection on port 211 of localhost needs no further customization, so we can set the Connected property of the TSQLConnection to True to verify that we can make a connection to the DataSnap Server.
For our example project, the properties of the TSQLConnection are configured as follows:

object SQLConnection1: TSQLConnection
  DriverName = 'Datasnap'
  LoginPrompt = False
  Params.Strings = (
    'DriverUnit=DBXDataSnap'
    'HostName=localhost'
    'Port=211'
    'CommunicationProtocol=tcp/ip'
    'DatasnapContext=datasnap/'

      'DriverAssemblyLoader=Borland.Data.TDBXClientDriverLoader,Borland' +
      '.Data.DbxClientDriver,Version=15.0.0.0,Culture=neutral,PublicKey' +
      'Token=91d62ebb5b0d1b1b'
    'Filters={}')
  Connected = True
end

Although we can now right-click on the TSQLConnection component to produce a list of server methods by calling the generate DataSnap client classes menu option, we can use a more powerful technique to work with the exposed datasets from the ServerMethodsUnit (where we connected the Advantage Database Server components to a TDataSetProvider for example, and also wrote a server method GetEvents to expose the events in a read-only way).

TsqlServerMethod
Let’s start with the read-only list of events, as provided by the GetEvents server method. In order to call this server method from the client, we need to place a TsqlServerMethod component next to the TSQLConnection and connect the formers SQLConnection property to the latter. The next step involves selecting the name of the ServerMethod that we want to call here, by opening the drop-down combobox for the ServerMethodName property. We need to pick the TServerMethods1.GetEvents server method here:

This particular server method does not have any parameters, but otherwise we would have seen them in the Params collection, which now only lists the ReturnParameter (containing the TDataSet with the events).
The TsqlServerMethod component needs to be connected to a local TDataSetProvider followed by a TClientDataSet and a TDataSource if we want to place the data somewhere on the form (for example in a read-only TDBGrid). The “chain" of components in this case is TSQLConnection - TsqlServerMethod – TDataSetProvider – TClientDataSet and TDataSource, followed by the data-aware controls such as TDBGrid, TDBNavigator and/or TDBEdit.
For people who’ve been using Delphi for some time, may notice the new TaqlServerMethod component, which is part of the new DataSnap framework. The properties for our example are as follows:

object SqlServerMethod1: TSqlServerMethod
  GetMetadata = False
  Params = <
    item
      DataType = ftDataSet
      Name = 'ReturnParameter'
      ParamType = ptResult
      Value = 'TDataSet'
    end>
  SQLConnection = SQLConnection1
  ServerMethodName = 'TServerMethods1.GetEvents'
end

The TDataSetProvider’s DataSet property should be pointing to the TsqlServerMethod component. The TClientDataSet’s ProviderName property should be pointing to the TDataSetProvider, and the TDataSource’s DataSet property should be connected to the TClientDataSet. Finally, the DataSource property of the data-aware controls can be connected to the TDataSource component, allowing the visual controls to display the data from the imported ADS table.

This will give an overview of all previous events in the database, for which users could register themselves. Note that the DataSnap Client does not connect directly to the ADS tables by itself, but only to the DataSnap Server. The client is turned into a thin (or smart) client, and can even be deployed as a stand-alone executable!

If you do not want to keep the connection open at design-time, but rather write one line of code to activate the TClientDataSet and request the data from the DataSnap server when the application starts, all you need is the following implementation of the FormCreate event:

  procedure TForm1.FormCreate(Sender: TObject);
  begin
    cdsEvents.Open
  end;

That will ensure that the cdsEvents TClientDataSet is opened, which will make the dspEvents TDataSetProvider activate the SqlServerMethod1 to call the GetEvents method at the DataSnap server side, opening the connection using the TSQLConnection component.

TDSProviderConnection
While the read-only list of events offers useful information, we may want to see more information. Specifically, I would like to see the actual people who registered for a given event. And I also want to be able to modify the data of the registered users (for example to fix typos or other details).
This requires another new Delphi DataSnap component, the TDSProviderConnection. Like the TsqlServerMethod, the TDSProviderConnection is connected to the TSQLConnection component. But this time, instead of selecting a server method to execute (at the server), the TDSProviderConnection is used as a gateway for TClientDataSet components who want to connect to an exported TDataSetProvider from the DataSnap Server.
The TDSProviderConnection properties for our example are as follows:

object DSProviderConnection1: TDSProviderConnection
  ServerClassName = 'TServerMethods1'
  Connected = True
  SQLConnection = SQLConnection1
end

The only tricky part here is the specification of the ServerClassName, which is a string (so it’s possible to make typing mistakes here). Other than that, we only need to connect to the TSQLConnection component and the TDSProviderConnection’s Connected property will just pass on the value of the TSQLConnection’s Connected property.
For our example, we want to connect to the dspEventRegistration TDataSetProvider, exported from the DataSnap Server. In order to do that, place a TClientDataSet component on the form, set its name to cdsEventRegistrations, set the RemoteServer property to DSProviderConnection1 and the ProviderName property to dspEventRegistrations. This will allow the TClientDataSet to retrieve all data from the exported TDataSetProvider.
In order to see what fields we get – including the nested detail field – double-click on the cdsEventRegistration TClientDataSet to start the Fields Editor, and right-click in the Fields Editor and select “Add All Fields" to generate the persistent fields that will contain all selected fields from the Events table as well as the Registrations for that event (in a nested dataset):

The aqRegistrations field here is of type TDataSetField. We actually have to use a second TClientDataSet to work with the individual records from the aqRegistration dataset. In order to do that, place a second TClientDataSet on the form, call it cdsRegistrations, and point its DataSetField property to cdsEventRegistrationsaqRegistrations (which is the combination of the cdsEventRegistrations TClientDataSet name and the aqRegistrations field inside that TClientDataSet).
We also need two TDataSources now, one for each TClientDataSet, and a second TDBGrid to display the detail Registration records that belong to a selected event from the master table.

There is one final step needed to make sure we can actually change and modify the data from these tables: sending updates back to the server. These updates are not limited to just “updates" (changes within a record) but can also include “inserts" (new records) as well as “deletes" (removal of record) of course.
The important detail here is that we have two TClientDataSets: a master cdsEventRegistrations and the detail cdsRegistrations. In order to apply the updates back to the server, we need the master to call the ApplyUpdates method. Even if the changes were only made in the detail TClientDataSet.

So, in order to implement this functionality in our example application, place a TButton on the form, set the caption to “Apply Updates" (as can be seen in the previous screenshot already) and implement the OnClick event handler as follows:

  procedure TForm1.btnApplyUpdatesClick(Sender: TObject);
  begin
    cdsEventRegistrations.ApplyUpdates(0)
  end;

Just a single line of code that will send all possible updates, inserts and deletes and send them over to the server where they will be applied on the tables in the Advantage Database Server.

In case updates fail – for example if a record was already removed, or changed by another user – we can write some code in the OnReconcileError event handler of the cdsEventRegistrations TClientDataSet. This falls beyond the scope of this article, but see my DataSnap courseware manual for more details on DataSnap error handling.

DataSnap Summary
In this article, I’ve described and demonstrated how we can use Advantage Database Server and the Advantage data access components for Delphi to work together with the DataSnap architecture in Delphi XE to build multi-tier applications. The Application Server tier is using Advantage specific data access components, while the thin/smart clients are database independent (and do not even need to know they are connected to an Advantage Database Server).
All further capabilities and benefits of DataSnap, including filters for compression and encryption, TCP/IP as well as HTTP(S) transport protocols to connect Servers and Clients, and Authentication plus Authorizaation can be used as well to extend the DataSnap example application. The bottom line, however, is that DataSnap is not limited to the data access technologies like dbExpress (DBX4) and dbGo for ADO found in Delphi itself, but can also be used with the Advantage Database Server and the Advantage data access components for Delphi.
So apart from moving from the BDE to ADS, we can go all the way and produce a multi-tier application with ADS as the foundation.


This webpage © 2011 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.