You are on page 1of 13

Nesting DataSets in ClientDataSets http://dn.codegear.

com/print/29825

Nesting DataSets in ClientDataSets

By: Cary Jensen

Abstract: Like the name suggests, a nested dataset is a dataset within a dataset. By nesting
one dataset inside another, you can reduce your overall storage needs, increase the efficiency
of network communications, and simplify data operations.

To put it simply, nested datasets are one-to-many relationships embodied in a single ClientDataSet
memory store. When the ClientDataSet is associated with a local file, saved as either a CDS binary file or
XML, this related data is stored in a single CDS or XML file. When associated with a ClientDataSet that
obtains its data through a DataSetProvider, the data packet is assembled from data retrieved through two
or more related datasets.

Nested datasets provide you with a number of important advantages. For one, they typically reduce the
amount of memory required to hold your data, whether it is the in-memory data store itself, or the data file
saved to disk through calls to the ClientDataSet's SaveToFile method.

Second, when used with DataSnap, Borland's multitier development framework, nested datasets reduce
network traffic. Specifically, nested datasets permit data from two or more related datasets to be packaged
in a single data packet, which can then be transmitted between the DataSnap server and client more
efficiently. For the same reason, nested datasets reduce the overall size of data stored by ClientDataSets
in local files.

While these are important characteristics, a third characteristic of nested datasets is the one that is
commonly considered their most valuable. Nested datasets permit the acquisition of data from, and
resolution of changes to, two or more underlying tables using a single ClientDataSet.

While developers who use nested datasets value this capability, it is more difficult to appreciate if you
have never worked with them before. Consider this: using nested datasets you can access data in two or
more tables by calling the Open method of a single ClientDataSet. Furthermore, when you are through
making changes, all updates are saved or applied with just one call to SaveToFile (for local files) or
ApplyUpdates (for data obtained from a database server). In addition, if you are saving changes by calling
ApplyUpdates, the changes to the two or more involved tables can be applied in an all-or-none fashion in
a single transaction.

But there is more. When nested datasets are involved, data being applied as a result of a call to
ApplyUpdates is resolved to the underlying datasets in the appropriate order, respecting the needs of
master-detail relationships. For example, a newly created master table record is inserted before any
related detail table records are inserted. Deleted records, by comparison, are removed from detail tables
prior to the deletion of any master table records.

A single ClientDataSet can have up to 15 nested datasets. Each of these nested datasets can, in turn,
contain up to 15 nested datasets, which in turn can have nested datasets as well. In other words, nested
datasets permit you to represent a complex hierarchical relationship using one ClientDataSet. In practice,
however, nested datasets greater than two levels deep are uncommon.

Creating Nested DataSets

1 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

There are two distinct ways to create nested datasets, depending on how the structure of the
ClientDataSet is obtained. If you are creating your ClientDataSet's structure at runtime by invoking
CreateDataSet, nested datasets are defined using TFields of the type TDataSetField. These TDataSetField
instances can be instantiated either at design-time or at runtime, depending on the needs of your
application.

The second way to create nested datasets is to load a data into a ClientDataSet from a DataSetProvider. If
the DataSetProvider is pointing to the master dataset of a master-detail relationship, and that relationship
is defined using properties, the data packet returned to the ClientDataSet contains one nested dataset for
each detail table linked to the master table. Each of these two approaches are covered in the following
sections.

Defining TDataSetFields
When a ClientDataSet's structure needs to be created at runtime by calling CreateDataSet, you define its
structure using TFields, where each of the nested datasets are represented by TDataSetField instances. If
you are defining your structure at design time, you add the TFields that define the table's structure using
the Fields Editor. If you need to define your structure at runtime, you call the constructor for each TField
you want added to your table, setting the necessary properties to associate the newly created field with
the ClientDataSet. In this second case, each nested dataset field is created by a call to the TDataSetField
constructor.

Defining a ClientDataSet's structure using TFields was discussed at length in a previous article in this
series titled "Defining a ClientDataSet's Structure Using TFields." While that article, which you can view
by clicking here, mentioned the general steps used to define nested datasets using TFields, this section
goes further, providing you with a step-by-step demonstration of the technique.

First, let's review the steps required to define the structure of a ClientDataSet to include nested datasets.

1. Using the Fields Editor, create one field of data type Data for each regular field in the dataset (such
as Customer Name, Title, Address1, Address2, and so forth).
2. For each nested dataset, add a new field, using the same technique that you use for the other data
fields, but set its Data Type to DataSet.
3. For each DataSet field that you add to your first ClientDataSet, add an additional ClientDataSet.
Associate each of these secondary ClientDataSets with one of the primary ClientDataSet's DataSet
fields using the secondary ClientDataSet's DataSetField property.
4. Define the fields of each nested dataset by adding individual TFields to each secondary
ClientDataSets you added in step 3. Just as you did with the initial ClientDataSet, you add these
fields using the Fields Editor.

The following steps walk you through creating a project that includes a ClientDataSet whose structure
includes nested datasets.

1. Begin by creating a new project.


2. On the main form, add two Panels from the Standard page of the Component Palette. In each panel
place a DBNavigator and a DBGrid. These components are located on the Data Controls page.
Align both DBNavigators to alTop, and both DBGrids to alClient. Set the Align property of the first
Panel to alTop, and the second to alClient. (If you want to be really fancy, after you align the first
panel to alTop, but before aligning the second panel, place a TSplitter from the Additional page of
the Component Palette on your form, and align it to top as well. The splitter permits the user to
customize the percentage of the form occupied by each panel at runtime.) Your form should look
something like the following.

2 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

3. Add to this form two DataSources and two ClientDataSets from the Data Access page. Set the
DataSource property of the DBNavigator and DBGrid in the top panel to DataSource1, and the
DataSource property of the DBNavigator and DBGrid appearing in the bottom panel to
DataSource2. In addition, set the DataSet property of DataSource2 to ClientDataSet1, and the
DataSet property of DataSource2 to ClientDataSet2.
4. Right-click ClientDataSet1 and select Fields Editor. Add five fields to the Field Editor, one at a
time, by either pressing Ctrl-N or right-clicking the Fields Editor and selecting Add field. Use the
following table to set the Name and Type fields on the New Field dialog that each of these TFields
require. Make sure that the Field Type radio group is set to Data. Accept the default values for all
of the remaining fields
Name Type
Invoice No Integer
Invoice Date Date
Customer No Integer
Employee No Integer
Details DataSet

5. Close the Fields Editor for ClientDataSet1.


6. Select ClientDataSet2 and set its DataSetField property to ClientDataSet1Details.
7. Right-click ClientDataSet2 and select Fields Editor. Add three fields to the Field Editor, one at a
time, by either pressing Ctrl-N or right-clicking the Fields Editor and selecting Add field. Use the
following table to set the Name and Type field properties on the New Field dialog that each of these
TFields require. Make sure that the Field Type radio group is set to Data. Accept the default values
for all of the remaining fields. Close the Fields Editor for ClientDataSet2.
Name Type
Part No Integer
Quantity Integer

3 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

Price Currency

8. With ClientDataSet2 still selected, set its DataSetField property to ClientDataSet1Details.


9. Add the following event handler to the OnCreate property of the main form.
procedure TForm1.FormCreate(Sender: TObject);
begin
ClientDataSet1.FileName := ExtractFilePath(Application.ExeName) + 'data.xml';
if FileExists(ClientDataSet1.FileName) then
ClientDataSet1.Open
else
ClientDataSet1.CreateDataSet;
ClientDataSet1.LogChanges := False;
end;

10. Run the form. If you have not yet added any data, this form should look something like the
following.

11. Now, add some data to both tables. Once some data is added, it might look something like the
following.

4 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

12. Notice that if you click the dataset field named Details associated with ClientDataSet1 twice, you
will get a small grid that you can use to enter, edit, and view the nested dataset, as shown in the
following figure.

If you inspected the OnCreate event handler for the main form of this project, you will have noticed that
any data that you enter is stored in a file named data.xml. The following image shows how the data in this
XML file looks like, once it has been formatted by the FormatXMLData function, which is found in the
XMLDoc unit (this unit only ships with Enterprise and Architect versions of Delphi 6 and later).

5 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

As you can see, the <FIELDS> element of this XML file contains one empty <FIELD> element for each
field in the primary dataset. In addition, the <FIELD> with the attrname attribute value of Details itself
contains a <FIELDS> element, which in turn contains the empty <FIELD> elements that describe this
nested dataset's structure.

Likewise, the <ROWDATA> element, which contains the actual data, contains one empty <ROW>
element for each field in the primary dataset. Here we find the <Details> element, which holds the data
for the Detail nested dataset.

The source code for this project can be downloaded from Code Central by clicking here.

Created TDataSetFields at Runtime

One of the facts that you learn pretty early in your Delphi development is that if you can perform a task at
design time you probably can perform that same task at runtime. This is certainly true with respect to
nested datasets. In short, you create a nested dataset by performing the following tasks in code.

1. Call the constructor of the TDataSetField class, assigning the necessary values to the properties of
the resulting object. As is the case with all TField that you create dynamically, one of the more
important properties is the DataSet property, a property that identifies which TDataSet instance this
field is to be associated with.
2. Assign the resulting TDataSetField to the DataSetField of the ClientDataSet that you will use to
display and edit the data stored in the nested dataset.

In the article that I published previously concerning defining the structure of a ClientDataSet using
TFields, I described a complicated project named VideoLibrary. This project includes two examples of a
nested dataset's runtime construction. You can download this project from Code Central by clicking on

6 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

this link.

All of the essential code for the process of creating a nested dataset and associating it with a
ClientDataSet can be found in the OnCreate event handler of the data module for this project. The
following segment, taken from this event handler, demonstrates the construction of a TDataSetField
instance, including the setting of its properties.
//Note: For TDataSetFields, FieldKind is fkDataSet by default
with TDataSetField.Create(Self) do
begin
Name := 'VideosCDSTalentByVideo';
FieldName := 'TalentByVideo';
DataSet := VideosCDS;
end;

Associating this field with a ClientDataSet is even simpler. This process is demonstrated in the
DataSetField property assignment appearing at the top of the following code segment. The remaining lines
demonstrate the creation of the actual fields of the nested dataset.
//TalentByVideosCDS
TalentByVideosCDS.DataSetField :=
TDataSetField(FindComponent('VideosCDSTalentByVideo'));
with TStringField.Create(Self) do
begin
Name := 'TalentByVideosID';
FieldKind := fkData;
FieldName := 'TalentID';
Size := 42;
DataSet := TalentByVideosCDS;
Required := True;
end; //ID
with TStringField.Create(Self) do
begin
Name := 'TalentByVideosName';
FieldKind := fklookup;
FieldName := 'Name';
Size := 50;
DataSet := TalentByVideosCDS;
KeyFields := 'TalentID';
LookupDataSet := TalentCDS;
LookupKeyFields := 'ID';
LookupResultField := 'Name';
end; //ID
with TMemoField.Create(Self) do
begin
Name := 'TalentByVideosComment';
FieldKind := fkData;
FieldName := 'Comment';
DataSet := TalentByVideosCDS;
end; //ID

Creating Nested DataSets Using Dynamically Linked DataSets


Nested datasets are automatically created when a dataset provider's DataSet property points to the master
dataset of a master-detail relationship. A master-detail relationship, as the term is being used here, exists
when one dataset, the detail dataset, is linked to another, the master dataset, through properties of the
detail dataset.

For example, a master-detail relationship exists when a BDE Table component is linked to another via the
MasterSource and MasterFields properties. Likewise, a master-detail relationship exists when a
SQLQuery component is linked to another dataset using the DataSource property in conjunction with a
parameterized query (where one or more parameter names in the detail table query match field names in

7 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

the master dataset).

When a DataSetProvider points to the master table one of these mater-detail relationships, the data packet
that it provides to a ClientDataSet includes one DataSetField for each detail dataset.

Creating nested datasets through dynamically linked datasets is not limited to BDE and dbExpress
datasets. Nested datasets can also be created using IBExpress, ADO, and MyBase datasets, as well as
many third-party TDataSet descendants. For example, the ADSTable and ADSQuery components
provided by Extended System to connect to the Advantage Database Server can by linked dynamically,
which will then produce nested datasets when their data is provided through a DataSetProvider.

The following steps demonstrate how to create nested datasets using dynamically linked datasets.

1. Create a new project.


2. Design the main form to look similar to the one created earlier in this article. Specifically, add two
Panels from the Standard page of the Component Palette. In each panel place a DBNavigator and a
DBGrid. These components are located on the Data Controls page. Align both DBNavigators to
alTop, and both DBGrids to alClient. Set the Align property of the first Panel to alTop, and the
second to alClient. (Again, if you want a better interface, after you align the first panel to alTop, but
before aligning the second panel, place a TSplitter from the Additional page of the Component
Palette on your form, and align it to top as well. The splitter permits the user to customize the
percentage of the form occupied by each panel at runtime.) Also add two DataSources from the
Data Access page of the Component Palette onto this form. Set the DataSource property of the top
DBNavigator and DBGrid to DataSource1, and the DataSource property of the bottom
DBNavigator and DBGrid to DataSource 2. Your form should look something like the following.

3. Select File | New | Data Module to add a data module to your project. From the BDE page of the
Component Palette add two Tables, and from the Data Access page add one DataSetProvider, one
DataSource, and two ClientDataSets. Your data module should look something like the following.

8 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

4. Set the DatabaseName property of Table1 and Table2 to DBDEMOS. Set the TableName property
of Table1 to customer.db, and the TableName property of Table2 to orders.db. Also, set the
IndexName property of Table2 to CustNo. Next, set the DataSet property of DataSource1 to
Table1.
5. Now it is time to create the dynamic link. Select the MasterFields property of Table2 and click the
ellipsis to display the Field Link Designer. Select CustNo in both the Detail Fields and Master Fields
lists, and then click the Add button. After you click the Add button, the string CustNo -> CustNo
will appear in the Joined Fields list. Click OK to close the Field Link Designer.

6. Set the DataSet property of the DataSetProvider to Table1, and set the ProviderName property of
ClientDataSet1 to DataSetProvider1.
7. At this point, if you make ClientDataSet1 active it will contain one TField for each field in Table1,
as well as an additional DataSetField for the associated records of Table2. These detail records can
be associated with ClientDataSet2 at runtime by assigning the DataSetField property of
ClientDataSet2 to this nested dataset. To do this at design time, however, you must create persistent
fields for ClientDataSet1.
8. To create persistent field for ClientDataSet1, right-click ClientDataSet1 and select Fields Editor.
Right-click in the Fields Editor and select Add all fields. After you add all fields to the Fields Editor,
you will see the DataSetField at the end of the list, as shown in the following figure.

9 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

9. Select ClientDataSet2 and set its DataSetField property to ClientDataSet1Table2.


10. Return to the main form. Add a main menu to this form. Right-click the main menu and select Menu
Designer. Set the Caption property of the top-level menu item to File. Add two menu items under
File, with the first one having the caption Open, and the second having the Caption Apply Updates.
The menu designer should look something like the following.

11. Close the menu designer.


12. Select File | Use unit, and add the data module's unit to the main form's uses clause.
13. From the main form, select File | Open to add an OnClick event handler to this menu item. Edit the
event handler to look like the following:
procedure TForm1.Open1Click(Sender: TObject);
begin
DataModule2.ClientDataSet1.Open;
end;

14. Now select File | Apply Updates from the main form's menu to create an OnClick event handler for
it. Edit this event handler to look like the following.
procedure TForm1.ApplyUpdates1Click(Sender: TObject);
begin
DataModule2.ClientDataSet1.ApplyUpdates(0);
end;

15. Finally, add an OnClose event handler to the main form. Edit this event handler to look like this:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
with DataModule2 do
begin

10 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

if ClientDataSet1.State in [dsEdit, dsInsert] then


ClientDataSet1.Post;
if ClientDataSet1.ChangeCount > 0 then
if MessageDlg('Save changes?', mtConfirmation,
mbOKCancel, 0) = mrOK then
ClientDataSet1.ApplyUpdates(0);
end;
end;

16. Set the DataSet property of DataSource1 to DataModule2.ClientDataSet1, and the DataSet property
of DataSource to DataModule2.ClientDataSet2.
17. Finally, since you cannot call Refresh on a ClientDataSet that does not have a DataSetProvider (and
ClientDataSet2 does not have a DataSetProvider, remove the nbRefresh flag from the
VisibleButtons property of DBNavigator2.
18. Save your project, and then run it. After you select File | Open from the main form's main menu,
your form should look something like the following.

Just as you do when using TFields to create nested datasets, the master ClientDataSet is used to control
both the master and all detail tables. Specifically, when you open ClientDataSet1, both ClientDataSet1
and ClientDataSet2 are populated with data. Similarly, you call ApplyUpdates only on ClientDataSet1.
Doing so saves all changes, even those made through ClientDataSet2.

You can even use the State and ChangeCount properties of ClientDataSet1 to determine the condition of
all datasets. You will notice this if you run the project, select File | Open, and then make a single change
to one of the records in the detail table. Before posting this change, try to close the application. This
causes the OnClose event handler to trigger, where the code will determine that an unposted record needs
to be posted, and then ask you whether you want your changes saved or not. In other words, the State
property of ClientDataSet1 is in dsEdit state when a unposted change appears in a nested dataset, and
calling ApplyUpdates applies all changes, even those posted to the nested datasets.

You can download the source code for this project from Code Central by clicking here.

Nested DataSets and Referential Integrity


Referential integrity refers to the relationships of master-detail records. You control referential integrity of

11 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

nested datasets using the flags of the Options property of the DataSetProvider. The following figure shows
the expanded Options property of a DataSetProvider displayed in the Object Inspector.

Add poCascadeDeletes to cause a master record deletion to delete the corresponding detail records. When
poCascadeDeletes is not set, master records cannot be deleted if there are associated detail records.

Add poCascadeUpdates to Options to propagate changes to the master table key fields to associated detail
records. If poCascadeUpdates is not set, you cannot change master table fields involved in the master-
detail link.

Note also that the Options property also contains a poFetchDetailsOnDemand property. If this flag is set,
detail records are not automatically loaded when you open the master ClientDataSet. In this case, you
must explicitly call the master ClientDataSet's FetchDetails method in order to load the nested datasets.

About the Author


Cary Jensen is President of Jensen Data Systems, Inc., a Texas-based training and consulting company
that won the 2002 Delphi Informant Magazine Readers Choice award for Best Training. He is the author
and presenter for Delphi Developer Days (www.DelphiDeveloperDays.com), an information-packed
Delphi (TM) seminar series that tours North America and Europe, and Delphi Developer Days Power
Workshops, focused Delphi (TM) training. Cary is also an award-winning, best-selling co-author of
eighteen books, including Building Kylix Applications (2001, Osborne/McGraw-Hill), Oracle JDeveloper
(1999, Oracle Press), JBuilder Essentials (1998, Osborne/McGraw-Hill), and Delphi In Depth (1996,
Osborne/McGraw-Hill). For information about onsite training and consulting you can contact Cary at
cjensen@jensendatasystems.com, or visit his Web site at www.JensenDataSystems.com.

Click here for a listing of upcoming seminars, workshops, and conferences where Cary Jensen is
presenting.

12 of 13 20.11.2008 17:22
Nesting DataSets in ClientDataSets http://dn.codegear.com/print/29825

New!: Stay informed, stay in touch. Register online to receive the free Developer Days ELetter:
information, observations, and events for the Delphi developer by Cary Jensen. Each Developer Days
ELetter includes Delphi tips and tricks, .NET information, links to recent articles posted to the Borland
Developers Network site, and events in the Delphi community. Click here to sign up now.

Copyright ) 2003 Cary Jensen, Jensen Data Systems, Inc.


ALL RIGHTS RESERVED. NO PART OF THIS DOCUMENT CAN BE COPIED IN ANY FORM
WITHOUT THE EXPRESS, WRITTEN CONSENT OF THE AUTHOR.

Published on: 3/10/2003 9:58:58 AM

Server Response from: BDN9A

Copyright© 1994 - 2008 Embarcadero Technologies, Inc. All rights reserved.

13 of 13 20.11.2008 17:22