简体   繁体   中英

Delphi - sending TField properties along with data between ClientDataSets

I have a clientdataset with modified modified field metadata such as field visibility and friendly names. I tried saving the data via savetofile/savetostream then load the data to another clientdataset. I lose all field formatting. Is there a way to do savetostream that includes all metadata formatting?

Clientdataset.SaveToStream(stream);
Clientdataset2.LoadFromStream(stream); //loses all field formatting/friendly names

Update:

I forgot to mention that the two client datasets are in different locations. I plan to save the stream to a string then send it to another application and have it restored to another clientdataset.

I don't think that this can be done with ClientDataSets out of the box, without writing some code, because the field metadata only includes the fieldname (not the DisplayLabel), datatype and size and there doesn't seem to be any way of persuading a CDS to include any more. Unfortunately, FieldDefs don't seem to affect this.

: Contrary to what I said in an earlier version of this answer, I've found a way to transfer Field properties such as DisplayLabel between CDSs along with the data as shown in the following code. :与我在此答案的早期版本中所说的相反,我找到了一种在CDS之间传输字段属性(例如DisplayLabel)以及数据的方法,如以下代码所示。

The code does the transfer of field properties and data as a string, so should work across a socket connection via a text stream.

It works by first saving the CDS data to a Xml-format string via .SaveToStream, then using a DOM parser to access the FIELD nodes in the Xml to add the DisplayLabel property as an attribute. A minor optimisation would of course be not to add the attribute if the DisplayLabel is the same is the field's FieldName.

At the destination CDS, the CDS data is loaded from the XML and then the nodes are processed to retrieve the DisplayLabel attributes and assign them to the CDS. This needs to be done after the data is loaded into the CDS, because that will clear out its fields (unless they've been set up as persistent).

Of course, you could transfer other Field properties in the same way and I dare say with more effort one could include all published properties automatically.

The other obvious thing is that it only works because the CDS .LoadFromStream ignores attributes it isn't expecting or interested in, but then why would its author go to the trouble of making it object to them?

I used MSXML because I had it handy and because ime it's a very fast, robust DOM parser. Plus, there's the "safety in numbers" aspect of it being very widely used. Also, it supports XPath - not all Delphi DOM parsers do - which I've used as a concise way to find the FIELD nodes.

// NOTE : The unit using this code needs to use MSXML
const
  scFieldNameAttr = 'attrname';  // the name of the FieldNam in CDS XML data
  scDisplayLabel = 'DisplayLabel';
  scPathToFields = '/DATAPACKET/METADATA/FIELDS/*';  //XPath query to get the list of fields in CDS XML data

function TForm1.GetCDSData(SourceCDS : TClientDataSet) : String;
var
  SS : TStringStream;
  S : String;
  I : Integer;
  XmlDoc: IXMLDOMDocument;
  NodeList : IXmlDOMNodeList;
  NewNode : IXmlDomNode;
  Field : TField;
  FieldName : String;
begin
  SS := TStringStream.Create('');
  XmlDoc := CoDOMDocument.Create;
  XmlDoc.Async := False;

  try
    SourceCDS.SaveToStream(SS, dfXML);
    SS.Position := 0;
    S := SS.ReadString(SS.Size);
    XmlDoc.LoadXML(S);
    if xmlDoc.parseError.errorCode <> 0 then
      raise Exception.Create('XML Load error:' + xmlDoc.parseError.reason);

    NodeList := XmlDoc.documentElement.SelectNodes(scPathToFields);
    Assert(NodeList <> Nil);
    for I := 0 to NodeList.Length - 1 do begin
      FieldName := NodeList.item[I].Attributes.GetNamedItem(scFieldNameAttr).text;
      Field := SourceCDS.FieldByName(FieldName);
      NewNode := XmlDoc.CreateAttribute(scDisplayLabel);
      NewNode.text := Field.DisplayName;
      NodeList.item[I].Attributes.SetNamedItem(NewNode);
    end;

    Result := XmlDoc.DocumentElement.xml;
  finally
    SS.Free;
    NodeList := Nil;
    XmlDoc := Nil;
  end;
end;

procedure TForm1.LoadCDSData(DestCDS : TClientDataSet; const StringData : String);
var
  SS : TStringStream;
  I : Integer;
  XmlDoc: IXMLDOMDocument;
  NodeList : IXmlDOMNodeList;
  NewNode : IXmlDomNode;
  Field : TField;
  FieldName,
  DisplayLabel : String;
begin
  SS := TStringStream.Create(StringData);
  XmlDoc := CoDOMDocument.Create;
  XmlDoc.Async := False;
  try
    SS.Position := 0;
    DestCDS.LoadFromStream(SS);
    XmlDoc.LoadXml(StringData);

    DestCDS.DisableControls;
    NodeList := XmlDoc.documentElement.SelectNodes(scPathToFields);
    Assert(NodeList <> Nil);

    for I := 0 to NodeList.Length - 1 do begin
      FieldName := NodeList.item[I].Attributes.GetNamedItem(scFieldNameAttr).text;
      DisplayLabel := NodeList.item[I].Attributes.GetNamedItem(scDisplayLabel).text;
      Field := DestCDS.FieldByName(FieldName);
      Field.DisplayLabel := DisplayLabel;
    end;
  finally
    XmlDoc := Nil;
    SS.Free;
    DestCDS.EnableControls;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  LoadCDSData(CDS2, GetCDSData(CDS1));
end;

Alternatively, if you're using DataSnap, and have control of the server and client designs, you could define your own channel for transmitting the field properties using DataSnap's Remote Events.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM