Creative Solutions Using ClientDataSet

By: Eric Whipple

Abstract: ClientDataSets can be used for much more than displaying rows and columns from a database. See how they solve applications issues from selecting options to process, progress messages, creating audit trails for data changes and more.

Eric Whipple is the director of Internal Development for Barden Entertainment, makers of the Digital Video Jukebox@trade; and other distributed kiosk applications. He is responsible for the planning, design, and implementation of Web services and other distributed architectures. He is also the author of Kylix 2 Development (Wordware Publishing) and numerous articles for Delphi Informant and the Borland Developer Network.

ewhipple@bardenent.com

Creative Solutions

Using ClientDataSets

by

Martin Rudy

ClientDataSets can be used for much more than displaying rows and columns from a database. They can be used to solve applications issues from selecting options to process, progress messages, creating audit trails for data changes and more.

This session shows techniques where ClientDataSets can be used for a variety of application solutions where data is to be created, stored, displayed, and used for internal processing that users never see. The intent of the session is to expand developer usage of ClientDataSets beyond the standard row/column usage.

Contents

The major topics covered are:

File selection and progress messages

Contents

ClientDataSets (CDS) provide an easy-to-use data structure that is handy for many application tasks. The first example shown is how to use a CDS to display a list of files to selection for processing and then display progress messages as each file is processed. Figure 1 shows an example of the sample form from the Filelist project.

Figure 1: File list selection and progress messages

The concept here is to retrieve a list of files to be imported from a specified directory. The Get File List button retrieves a list of files and initially selects each file name retrieved. The checkmark in the Select column indicates the file is to be selected. Users can remove any of the files form the list by changing the select column to N before selecting the Import Files button.

For each file imported, the Process Message column is updated indicating the progress of the import process and any problems with the import. In this example, the file Order1404.xml had a problem with the file indicating there is an issue with the xml formatting. As each file is processed, the grid is updated with the progress  when the import starts and after finishing the import the final result.

NOTE: In this example the components that ship with Delphi were used. Third-party grids provide a checkbox option for the Select column and a multi-line cell for the Process Message column giving an improved display for messages.

The structure of the CDS contains only three fields: SelectRcd, FileName and ProcessMsg. Records are initially inserted into the CDS by retrieving all files in a specific directory and setting the SelectRcd field to Y on Post. A hidden TFileListBox is used to easily get the files in the directory. The code to load the data is shown below.

procedure TfrmFileListExample.pbGetFileListClick(Sender: TObject);
var
  I: Integer;
  CurCursor: TCursor;
begin
  CurCursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  try
    cdsFileList.Close;
    cdsFileList.CreateDataSet;
    cdsFileList.Open;
    cdsFileList.LogChanges := cbxLogChanges.Checked;
    // put list of all available files in grid
    for i:=0 to FileListBox.Items.Count - 1 do
      begin
        cdsFileList.Append;
        cdsFileList.FieldByName('FileName').AsString :=
          FileListBox.Items.Strings[i];
        { Ensure the SelectRcd field is set last because there is an
          OnChange event which posts the record.  This event will ensure
          at runtime the record is not left in edit when user changes the
          record selection by clicking on the checkbox.

          This also means the Post method below is not necessary.
        }
        cdsFileList.FieldByName('SelectRcd').AsString := 'Y';
//        cdsFileList.Post;    // THIS IS NOT REQUIRED, DONE IN OnChange
                               // EVENT FOR SelectRcd Field
                               // See AfterOpen event below
      end;
    cdsFileList.First;
  finally
    Screen.Cursor := CurCursor;
  end;
end;

In this example, the Select column is the only field that the user can modify. When the Select column value is changed, the record in automatically posted. In the demo there is no real processing of the file, there is only a simulation of processing and displaying the progress. Normally a checkbox control would be used but the DBGrid does not support that feature. The demo project has modified the grid to include an OnDblClick event which toggles the value between Y and N.

The concept of this example is basic but using a CDS makes the creation of the selection and UI very easy to implement. After the import processing is complete, saving the CDS in an XML format creates a file with the contents shown in Figure 2:

Figure 2: CDS contents as an XML file after getting file list import simulation

There are two main sections of the xml file: data structure and actual data. The METADATA section defines the fields that are in the file (also called data packet). The field name, data type and width are included. The second section, ROWDATA, contains the values for each field and the RowState value. The RowState shows that status of the data row. Table 1 shows the values and their meaning. Any row can have a combination of RowState values to indicate multiple changes made to a row.

RowState Value

 

Description

1

Original record

2

Deleted record

4

Inserted record

8

Updated record

64

Detail updates

Table 1: RowState values and Description

By default, a CDS will create a change log for every modification made to the data. Each insert, delete and update is logged. If logging of the changes is not necessary, you can set the CDS LogChanges property to False. In the Filelist project, if the Log Changes checkbox is unchecked, the contents of the XML file after getting the file list and simulating the import is shown in Figure 3.

Figure 3: CDS contents with no logging

You can also achieve both  the ability to log changes and before saving merge all changes to each row into a single record. This is done using the MergeChangeLog method. Executing the MergeChangeLog method before saving the file creates the same output if no logging was performed.

Creating master lookup tables

Contents

Lookup tables provide an excellent way to ensure valid values are entered into fields and UI support for combo and list boxes to select from. Using the built-in data-aware components that support this feature requires separate datasets for each component and sometimes DataSource components are required.

For some application requirements, creating a table that is essentially a grouping of tables can eliminate this need for multiple tables in the database. This next example, named MstrLookup, demonstrates how you can have a master lookup table and how to use CDS components and the cloning feature to support multiple lookup datasets without individual tables for each type of lookup.

The first step is to create a table in the database that contains the lookup values for the various types of groupings. The design used here is to have a field that groups the data, a field for the lookup code, and a field for a description. The following is an example of the create statement for a table named MasterLookup.

CREATE TABLE [MasterLookup] (
    [LookupGroup] [char] (10) NOT NULL ,
    [LookupCode]  [char] (10) NOT NULL ,
    [LookupDesc]  [varchar] (65) NOT NULL )

There are three fields in the table. LookupGroup is used as the grouping or table name. The lookup code is in the LookupCode field. LookupDesc field contains the description for the code value. If the lookup table has codes that are self-explanatory, the code and description fields will be the same.

In this example, the ORDERS table is used from. There are two fields that can use this lookup feature: ShipVia and PaymentMethod.

The general technique is to retrieve the values from the MasterLookup table into a CDS. A separate CDS is placed in the client data module for each lookup table. The rows for each lookup CDS are set in the OnCreate of the data module. The code to create the two tables for ShipVia and PaymentMethod is shown below.

procedure TForm1.GetLkupData;
begin
 { Get lookup data into CDS }
  with cdsMasterLookup do
  begin
    { Ensure MasterLookup is open }
    Open;
    Filter := 'LookupGroup = ''ShipVia''';
    Filtered := True;
    cdsLkupShipVia.CloneCursor(cdsMasterLookup,False,True);
    Filter := 'LookupGroup = ''PayType''';
    cdsLkupPayType.CloneCursor(cdsMasterLookup,False,True);
    Filtered := False;
    Filter := '';
    cdsLkupShipVia.Open;
    cdsLkupPayType.Open;
  end;
end;

A CDS named cdsMasterLookup is used for all rows in the MasterLookup table. Its source of data is a DataSetProvider (DSP) using a Table component as the DataSet. Since the number of rows is very small in MasterLookup, filtering is used to restrict the values before the CDS is cloned. Each group requires a new filter on the master table. The CloneCursor method is called to make a copy of the filtered rows. CloneCursor allows a separate lookup CDS to share the data belonging to the CDS for MasterLookup. Each of the cloned CDS does not have a DataSetProvider.

In this example the DBLookupComboBox component is uses to provide the list of lookup values. Attempting the assignment of the KeyField property generates an error indicating the CDS does not have the ProviderName assigned. Therefore, the lookup properties cannot be set. The solution to this problem is to initially use the CDS for the MasterLookup table, assign the KeyField and ListField properties then reassign the ListSource property back to the DataSource for the lookup CDS. When this is complete, the combo boxes work properly.

Creating a record-copy routine

Contents

Some applications have a requirement to provide a copy record feature or at least copy all but a few of the fields. ClientDataSets provide an easy solution for this type of requirement. We will look at two solutions: the first uses an existing CDS, the second is a generic function where the CDS is instantiated, used and destroyed. The code below contains the first example.

procedure TfrmCopyRcd.pbCopyRcdClick(Sender: TObject);
var
  I: Integer;
begin
  with ClientDataSet1 do
    begin
      Open;
      Insert;
      for I:=0 to fieldcount-1 do
        Fields[I].Assign(ADODataSet1.FindField(Fields[I].FieldName));
    end;

  with ADODataSet1 do
    begin
      Insert;
      for I:=0 to fieldcount-1 do
        Fields[I].Assign(ClientDataSet1.FindField(Fields[I].FieldName));
    end;
  ClientDataSet1.Cancel;
end;

The basic concept here is to use the CDS as a temporary buffer to hold the data. This first example uses an existing CDS that is the same structure of the table used to copy to and from.

After putting the CDS into the Insert state, the FieldCount property is used to cycle through all the fields in the CDS and copy matching fields from the existing dataset. In this example ADO is used as the database connectivity option. After all fields are copied, a new record is inserted into the ADO dataset and the record is copied from the CDS, leaving the ADO dataset in Insert mode.

For a known structure this works but is not to helpful from a generic standpoint. A general purpose function where the CDS is instantiated as needs is more useful. The function CopyDataSetRcd shown below supports this requirement.

function TfrmCopyRcd.CopyDataSetRcd(DataSet: TDataSet): Boolean;
var
  I: Integer;
  cds: TClientDataSet;
begin
  result := False;
  cds := TClientDataSet.Create(nil);
  try
    cds.FieldDefs.Assign(DataSet.FieldDefs);
    cds.CreateDataSet;
    with cds do
      begin
        Open;
        Insert;
        for I:=0 to fieldcount-1 do
          Fields[I].Assign(DataSet.FindField(Fields[I].FieldName));
      end;

    with DataSet do
      begin
        Insert;
        for I:=0 to fieldcount-1 do
          Fields[I].Assign(cds.FindField(Fields[I].FieldName));
      end;
    cds.Cancel;
    result := True;
  finally
    cds.Free;
  end;
end;

In the CopyDataSetRcd, the CDS is called using the dataset that needs the current record copied. An instance of a CDS is created at the beginning of the function. The fields are assigned based on the dataset passed to the function. This allows the dynamic creation of the CDS to match any dataset passed. The same basic technique as the first example is used for the remainder of the function.

Most applications do not have a need to create exact duplicates of existing records but this basic concept can be useful where there is a need to copy many of the fields from the previous record. The basic technique can be used to create a copy of the record and either delete or prevent copying field(s) that are part of the primary key. You can also add an additional parameter which specifies the fields to copy or the fields not to copy making the duplication process generic but specific to the fields to include or exclude.

Custom audit trail using change log

Contents

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 project, the third tab displays the contents of the change log. Figure 4 shows the log after performing an edit on one row, an insert, and a delete. Note, the UpdateStatus field does not exist in the data, it is a calculated field used to display the status of each record.

Figure 4: Change log display

The last two records are for a deleted and inserted record. On an insert, any field where data is entered is placed in the change log. For deleted records, all the original field values are placed in the log.

The first tow records are matching pair. The first record of the pair 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 values of both Addr1 and Addr2 fields have been modified.

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
    try
      cdsCustDelta.Close;
      cdsCustDelta.Data := cdsCustomer.Delta;
      cdsCustDelta.Open;
    except
      MessageDlg('No delta records exist.',mtWarning,[mbOK],0);
    end;
end;

The CDS cdsCustomer contains the data from the provider. The CDS for showing the change log is named cdsCustDelta. When the third tab is selected, the Delta property of cdsCustomer is assigned to the Data property of cdsCustDelta. The try except block is used to display a simple message when there are no modifications to the data.

The value for the calculated UpdateStatus field is assigned using the CDS UpdateStatus method. Table 2 lists the four return values for UpdateStatus and a description.

UpdateStatus Value

 

Description

usModified

Modifications made to record

usInserted

Record has been inserted

usDeleted

Record has been deleted

usUnModified

Original record

Table 2: RowState values and Description

The OnCalcFields for the CDS is shown below.

procedure TdmMain.cdsCustomerDeltaCalcFields(DataSet: TDataSet);
begin
  with DataSet do
  begin
    case UpdateStatus of
      usModified   : FieldByName('UpdateStatus').AsString := 'M';
      usInserted   : FieldByName('UpdateStatus').AsString := 'I';
      usDeleted    : FieldByName('UpdateStatus').AsString := 'D';
      usUnModified : FieldByName('UpdateStatus').AsString := 'U';
    end;
  end;
end;

Using the CopyDataSetRcd function described in the previous section along with the CDS logging feature, you can create a custom audit trail process in your applications. The project CDS_Audit is used for this example.

The basic concept here is to copy the change log before the ApplyUpdates is executed then save the changes to an existing table. The following are two code snippets used in the example.

procedure TForm4.cdsCustBeforeApplyUpdates(Sender: TObject;
  var OwnerData: OleVariant);
begin
  { Save Delta }
  CDSLog.Data := cdsCust.Delta;
end;

procedure TForm4.pbShowChangeLogClick(Sender: TObject);
begin
  CDSLog.Data := cdsCust.Delta;

  Screen.Cursor := crHourGlass;
  try
    tblCustAudit.Open;
    CDSLog.First;
    while not CDSLog.Eof do
    begin
      try
        CopyDataSetRcd(CDSLog,tblCustAudit);
        tblCustAudit.FieldByName('ModType').AsString :=
          CDSLog.FieldByName('UpdateStatus').AsString;
        tblCustAudit.FieldByName('ModDate').AsDateTime := Date;
        tblCustAudit.Post;
      except
        if tblCustAudit.State = dsBrowse then
          tblCustAudit.Cancel;
        raise;
      end;
      CDSLog.Next;
    end;
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TForm4.CDSLogCalcFields(DataSet: TDataSet);
begin
  with DataSet do
  begin
    case UpdateStatus of
      usModified   : FieldByName('UpdateStatus').AsString := 'M';
      usInserted   : FieldByName('UpdateStatus').AsString := 'I';
      usDeleted    : FieldByName('UpdateStatus').AsString := 'D';
      usUnModified : FieldByName('UpdateStatus').AsString := 'U';
    end;
  end;
end;

The first procedure is used by a DSP to automatically copy the change log to an existing CDS before the updates are applied. This keeps a copy of the changes before they are applied to the database.

The second procedure is used to actually save the change log to a table. Each record in the change log is copied to the audit table. Additionally the type of modification and date of modification is added to the audit table record. The UpdateStatus field is a calculated field added to the log dataset. It is assigned using the UpdateStatus method of a CDS. The third procedure above show how the calculated UpdateStatus field is generated.

Using xml format for CDS development

Contents

Incremental design is part of many application development cycles. ClientDataSets provide an easy tool for structure modifications during the prototyping and proof-of-concept phases for dataset design. Using the XML format, fields can easily be added, deleted or modified. Data can easily be added or changed without having to use a database backend.

You can start designing a dataset with a new CDS. One of the options is to use is the Fields Editor to add new fields. After adding the fields, you right-mouse click on the CDS and select Create DataSet from the speed menu. This creates the in-memory dataset which is static in the form or data module.

To start getting data into the CDS, a small application is needed. This is basically a grid, DataSource and a Button containing a SaveToFile call using the XML format as follows:

CDS.SaveToFile('CDS.XML',dfXML);

The first parameter specifies the file name with optional full path. The second parameter indicates the saved format is to be XML. After entering a few records, the output is as shown in Figure 5.

Figure 5: Saved CDS data in XML

The change log is always generated by default. You can change this default by adding to the OnCreate for the form a line setting the LogChanges property of the CDS to False or use the MergeChangeLog method to combine the data and change log before saving. The latter is used here to support undo of changes during data entry.

Saving the structure now allows both fields and data to be added. The XML file can but updated to include new fields and records, any existing data can be modified provided it follows the metadata definition.

There are two keys to getting this technique to work for you: 1) need to know what is required for the data type values; and 2) need to know if there are any formatting issues for non-text data types. Figure 6 shows lists some of the commonly used data types, the value used in the XML metadata and sample on how to format the data.

Figure 6: Common data types as shown in XML metadata

This technique is demonstrated further in the next section.

Using ClientDataSet as an internal data structure

Contents

This section picks up from the previous topics and expands on the usage of ClientDataSets in the development phase. The example used is an application that was a prototype for a Sunday School game for the Books of the Bible where users would test their knowledge of putting the books in the correct order and correct sections on a bookshelf.

Part of the example is how to use ClientDataSets during the development of the concept and as internal data for drag/drop information to ensure an order sequence as new UI items are added to the display dynamically and randomly at runtime.

Below is the prototype form for the example. Two grids are used: one to create the data used in the random creation of the books to display and second shows the internal data stored as the user placed the new book on the shelf in the correct position. The two white rectangles represent the separate shelves in a bookcase. When the Create Book button is clicked, a new book is created and placed next to the top shelf. The width of the component was based on the BookWidth field in the first CDS. The user then drags the book to the appropriate place and, based on the data in the internal CDS, a comparison is made if the drop of the book was in the correct sequential location.

Figure 7: Application using CDS for application prototype

NOTE: The original prototype was done with Delphi 7 and third-party components. These components did not exist with Delphi 9 so modifications were made to use what shipped with Delphi. A TMemo control replaced the third-party component used to represent the book and the book width had to be excluded.

Two ClientDataSets where used to create the prototype. The first CDS was used to store the books. It started with only the BookNo and BookName fields. The data for the books was loaded using the technique described in the previous section. To assist the drag and drop processing and development, an internal data structure was required. The second CDS was created for this purpose primarily to use a DBGrid for data display of the assigned values. This made debugging the process easier because values where displayed as they were assigned. The Fields Editor was used to create the fields for the second dataset and CreateDataSet was added to the forms OnCreate.

During the prototype creation, fields where added to both ClientDataSets as needed using the technique described in the previous section. For example, the ObjectName field was added to the second CDS. This value is the Name property for each book added. It consists of the text Book and the book BookNo numeric value converted to a two character text value. The ObjectName value is used with a call to FindComponent during the insertion process of a new book. Another example is the BookWidth property in the first CDS. This was used to provide data to set the width of the component representing the book when it was created. This gave a visual representation of the relative size of the book when the third-party control was used.

In the prototype, all books are listed in the upper-right grid. The final version is to randomly generate which book to be placed which is not in the prototype version. Generating a book to be placed is done by either clicking the Create Book button or a double-click on the book grid. The currently selected record in the grid will be the book created to place in the shelf. This is placed to the left of the first shelf. The following code is used to generate a book.

procedure TForm1.pbCreateBookClick(Sender: TObject);
var
  bk: TMemo;
begin
  { Generate a new book object for selected book }
  bk := TMemo.Create(self);
  bk.Parent := self;
  bk.Tag := cdsBibleBooks.FieldByName('BookNo').AsInteger;
  if bk.Tag < 10 then
    bk.Name := 'Book0' + IntToStr(bk.Tag)
  else
    bk.Name := 'Book' + IntToStr(bk.Tag);
  bk.Lines[0] := (cdsBibleBooks.FieldByName('BookName').AsString);
  bk.Left := 2;
  bk.Top := 5;
  bk.Height := 160;
  bk.Width := 14;
  bk.Alignment := taCenter;
  bk.DragMode := dmAutomatic;
  bk.Color := clBlue;
  bk.Font.Color := clWhite;
  bk.Font.Name := 'Courier New';
end;

The list of books is stored in the CDS named cdsBibleBooks. The current record in the CDS is used to set property values of the TMemo instance created. The Tag property is assigned the BookNo field which is a unique value. The Name property is assigned to a unique value using the BookNo field. Some of the TMemo properties assigned are to get the vertical text display. Automatic drag mode is used to simplify the prototype creation for drag-and-drop.

When a user drags the new book to a location to place in on the self, the following code is executed:

procedure TForm1.Shape1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  L,T,S: Integer;
begin
  with Sender as TShape do
  begin
    L := Left;
    T := Top;
    S := Tag;
  end;
  if Source is TMemo then
    with Source as TMemo do
    begin
      { Look to see if book dropped in correct location }
      if BookLocOK(S,Tag,X+L) then
      begin
        if cdsBkShlf.RecordCount > 0 then
          TMemo(Source).Left := X + L
        else
          TMemo(Source).Left := 8;
        TMemo(Source).Top := 4 + T;
        { Locate BookName, if found, update position.
          If not found, insert into table.
        }
        if cdsBkShlf.Locate('BookName',TMemo(Source).Text,[]) then
          begin
            cdsBkShlf.Edit;
            cdsBkShlf.FieldByName('ShelfNo').AsInteger := S;
            cdsBkShlf.FieldByName('ShelfLeftPos').AsInteger := X;
          end
        else
          begin
            cdsBkShlf.Append;
            cdsBkShlf.FieldByName('ShelfNo').AsInteger := S;
            cdsBkShlf.FieldByName('BookNo').AsInteger := Tag;
            cdsBkShlf.FieldByName('ShelfLeftPos').AsInteger := X;
            cdsBkShlf.FieldByName('BookName').AsString := TMemo(Source).Text;
            cdsBkShlf.FieldByName('ObjectName').AsString := Name;
          end;
        cdsBkShlf.Post;
        ResuffleBooks(S);
      end
      else
        MessageDlg('Book location incorrect.',mtWarning,[mbOK],0);
    end;
end;

After checking if the Sender is a TMemo, there is a check to see if the book is dropped in the correct position. The call to BookLocOK indicates if the position was. If the result is True, the book is placed in the correct position otherwise a simple message is displayed indicating the position is incorrect. For each new book added, there is a check if the book already has been placed. In this prototype, there is no check for the correct shelf the book is to be placed in. Therefore, a book that was originally placed in the first shelf can be moved to the second shelf and the ShelfNo and ShelfLeftPos fields need to be updated. If the book is newly added, a new record is appended to the CDS.

The data stored in cdsBkShlf is only used during the running of the application. The grid showing the contents is for development purposes only. It provides instant feedback on the values assigned and is a tool that can be adjusted during the development process to find flaws in the basic design. Other data structures that are more efficient can be used, but in a prototype, fast-paced development cycle, a CDS and DBGrid can expedite the process.

Storing error codes and messages during development

Contents

Some applications have a need to support error and message codes with associated text for the messages. ClientDataSets can be used to create a repository for storing both the codes and text for each message. The XML format is used in this example which allows for easy insertion and modification of error codes and descriptions. This XML file is loaded at runtime and functions are shown which use this data. Figure 8 shows the basic structure of the XML file. This data can then be moved to the shipping database when appropriate. A technique is also shown how to store this data statically in the application as a CDS thus allowing the same functions to be used when the XML was loaded at startup.

Figure 8: Sample structure for error codes and messages with sample data

A CDS is loaded with the error code and message data when the data module is created using the following:

cdsErrMsg.LoadFromFile('ErrMsg.XML');

The function in the following code retrieves the text for the specified error code.

function TdmMain.GetErrMsg(ErrCd: Integer): String;
begin
  if cdsErrMsg.Locate('ErrorCode',ErrCd,[]) then
    result := cdsErrMsg.FieldByName('ErrorMessage').AsString
  else
    result := 'Invalid Error Code';
end;

This function can be used in any validation process where the business rule maps to a specific error code. The following is an example of using the function in the BeforePost event of the CDS.

procedure TfrmValidation.ClientDataSet1BeforePost(DataSet: TDataSet);
begin
  { Ensure required fields are entered }
  with DataSet do
  begin
    if FieldByName('Customer').AsString = '' then
      raise Exception.Create(dmMain.GetErrMsg(101));
    if FieldByName('Contact_First').AsString = '' then
      raise Exception.Create(dmMain.GetErrMsg(102));
    if FieldByName('Contact_Last').AsString = '' then
      raise Exception.Create(dmMain.GetErrMsg(103));
    if (FieldByName('Address_Line1').AsString = '') and
       (FieldByName('Address_Line2').AsString <> '') then
      raise Exception.Create(dmMain.GetErrMsg(104));
  end;
end;

As additional business rules are added, requiring the increase in error codes, new entries can easily be inserted into the XML file. This can be done using any text editor or by creating a small application that loads the existing XML file, supports modifications to the data and saves the error codes and messages back to the XML file. This process continues during the development phase.

When time comes to ship the application, most likely you will not want to ship an XML file that is loaded on startup. Two options can be used to replace the loading of the XML data. The data can be moved to a table in the database and retrieved as the application opens or the data can be made static in the exe itself.

Moving data to the applications database requires a little bit of extra effort. You can write a small application that inserts the data from the CDS XML file by either directly inserting into the table or you can use a DataSetProvider, a second CDS and have the records inserted into the database using ApplyUpdates. Another modification is required at startup to retrieve the data into the CDS, replacing the LoadFromFile call that previously loaded the XML file.

The second option is to load the CDS at design-time and make the data part of the exe. You right-click on the CDS and select the Load from MyBase table menu option and choose the saved XML file. When the project is saved, the data becomes stored in the form/data module containing the CDS and becomes static for the released exe. Provided you dont change the Active property of the CDS, the error codes and messages exist for each exe created. When a change is required, the XML file is updated and you re-load at design time the new data.

Using the technique of an XML file provides another option during development. The developers can use the file for coding and the documentation department can review the error messages and update per their standards. Adding another field to the CDS structure for notes allows both developers and documenters the ability to provide further information on the error and further detail about potential cause and correction if needed. This information can be added into the final documentation or provide online information for the final product.

Summary

ClientDataSets, they are not just for multi-tier applications any more. They can be used in applications based on Paradox or Access data, InterBase, Oracle, SQL Server, and any other database, for XML based applications and any combination of local storage to multi-tier. Any time an application needs some type of data structure to store data where the data needs to be viewed and manipulated, ClientDataSets provide an excellent solution.


  Latest Comments  View All Add New RSS ATOM

Move mouse over comment to see the full text

Server Response from: ETNASC03