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
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.
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.
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
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.
The following steps walk you through creating a project that includes a
ClientDataSet whose structure includes nested datasets.
procedure TForm1.FormCreate(Sender: TObject);
ClientDataSet1.FileName := ExtractFilePath(Application.ExeName) + 'data.xml';
if FileExists(ClientDataSet1.FileName) then
ClientDataSet1.LogChanges := False;
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).
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
The source code for this project can be downloaded from Code Central by clicking
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.
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
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
Name := 'VideosCDSTalentByVideo';
FieldName := 'TalentByVideo';
DataSet := VideosCDS;
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
with TStringField.Create(Self) do
Name := 'TalentByVideosID';
FieldKind := fkData;
FieldName := 'TalentID';
Size := 42;
DataSet := TalentByVideosCDS;
Required := True;
with TStringField.Create(Self) do
Name := 'TalentByVideosName';
FieldKind := fklookup;
FieldName := 'Name';
Size := 50;
DataSet := TalentByVideosCDS;
KeyFields := 'TalentID';
LookupDataSet := TalentCDS;
LookupKeyFields := 'ID';
LookupResultField := 'Name';
with TMemoField.Create(Self) do
Name := 'TalentByVideosComment';
FieldKind := fkData;
FieldName := 'Comment';
DataSet := TalentByVideosCDS;
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 the master
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.
procedure TForm1.Open1Click(Sender: TObject);
procedure TForm1.ApplyUpdates1Click(Sender: TObject);
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
with DataModule2 do
if ClientDataSet1.State in [dsEdit, dsInsert] then
if ClientDataSet1.ChangeCount > 0 then
if MessageDlg('Save changes?', mtConfirmation,
mbOKCancel, 0) = mrOK then
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
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
Referential integrity refers to the relationships of master-detail records.
You control referential integrity of 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.
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 email@example.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
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.
) 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.
Download Delphi XE6 now!
Get Free Trial
Webinars on demand!
More social media choices:
Delphi on Google+
@RADTools on Twitter
Server Response from: ETNASC04