1
votes

I'm having trouble working out how to add new data fields to an old dataset file. For example, an old dataset might have an ID field only. Later on we decide we need an ISACTIVE field. I want to reopen my ID-only data and then resave it with ISACTIVE values added. For example:

CDS := TClientDataset.Create(nil);
with TIntegerField.Create(CDS) do
begin
  FieldName := 'ID';
  FieldKind := fkData;
  DataSet := CDS;
end;
CDS.CreateDataSet;

CDS.Close;
with TBooleanField.Create(CDS) do
begin
  FieldName := 'ISACTIVE';
  FieldKind := fkData;
  DataSet := CDS;
end;
CDS.Open; // <--Raises EDatabaseError with message 'Field 'ISACTIVE' not found'.

I had a look for similar questions, the closest I found was one that concerned adding new calculated fields to a dataset only. This method above works fine for adding a calculated field.

At the moment the only (messy) solution I can think of is to load the ID-only data into a temporary dataset, then create a new dataset with ID and ISACTIVE fields defined, then loop through the ID-only dataset and copy the records across to the new dataset.

2
hmm, yep in a way. It is quite an oddly posed question I think?! Anyways, this answer stackoverflow.com/a/21295035/6620329 describes what I referred to as "messy" solution at the end of my question. Maybe that is the only option available to me though (bar MartynA's suggestion of editing the text file on disk).Phil B

2 Answers

3
votes

There is a simple way to do this.

If you have a CDS with an Integer ID Field and a String 80 Name field, and you save the dataset to XML, as in

AFileName := 'C:\Temp\CDSData.Xml';
CDS1.SaveToFile(AFileName, dfXML);

the resulting XML file will look like this (for D7)

<?xml version="1.0" standalone="yes"?>  
<DATAPACKET Version="2.0">
  <METADATA>
    <FIELDS>
      <FIELD attrname="ID" fieldtype="i4"/>
      <FIELD attrname="Name" fieldtype="string" WIDTH="80"/>
    </FIELDS><PARAMS CHANGE_LOG="1 0 4"/>
  </METADATA>
  <ROWDATA>
    <ROW RowState="4" ID="1" Name="one"/>
  </ROWDATA>
</DATAPACKET>

You can then use MSXML or your favourite XML processor to make the trivial change to add additional FIELD node(s) to the METADATA defining the CCDS's datapacket, to add the extra field(s). You then reload the CDS from the XML. The added field(s) values will be NULL of course and for this technique to work, you must not have persistent TFields defined on the CDS at the time you reload it from the saved XML.

Example code:

procedure TForm1.CopyWithAddedFields;
var
  SS : TStringStream;
  XMLDoc : IXmlDomDocument;
  FieldsNode : IXmlDomNode;
  FieldElement : IXmlDomElement;
begin
  SS := TStringStream.Create('');
  try
    //  Save the CDS's current contents in XML format, close it and clear any presistent fields
    CDS1.SaveToStream(SS, dfXML);
    CDS1.Close;
    CDS1.Fields.Clear;

    //  Next create an XML Document object and load the saved dataset into it
    XMLDoc := CoDomDocument.Create;
    XMLDoc.LoadXML(SS.DataString);

    //  Find the FIELDS node and add a new FIELD node to it       
    FieldsNode := XMLDoc.selectSingleNode('/DATAPACKET/METADATA/FIELDS');
    FieldElement := XMLDoc.createElement('FIELD');
    FieldElement.SetAttribute('attrname', 'Active');
    FieldElement.SetAttribute('fieldtype', 'boolean');
    FieldsNode.appendChild(FieldElement);

    // Save the XML to the stream
    SS.Size := 0;
    SS.WriteString(XmlDoc.xml);
    SS.Position := 0;

    //  Reload the ClientDataset
    CDS1.LoadFromStream(SS);
  finally
    XMLDoc.Free;
    SS.Free;
  end;
end;

Obviously, you could load the modified XML into a different CDS instead if you wanted to.

Of course, you could even add the extra FIELD nodes to the XML just by loading it into a TStringList if you were prepared to do a certain amount of string-twiddling yourself.

Fwiw, I stumbled upon this ruse when trying to modify the XML of a CDS to include extra information for each ROW node in the XML file; it turned out that the LoadFromFile & LoadFromStream processes were completely oblivious to the information I added.

-1
votes

Hm, what wrong with calculated fields? Could you please explain why you do not want to use them?

One extra hint: Have you tried InternalCalc (FieldKind = fkInternalCalc). AFAIK it should behave similar to data field (it's values are stored within the dataset record)

PS and there is no need to reload data (close and reopen dataset) when you adding a calculated field