2016-10-05 4 views
0

Je n'arrive pas à déterminer comment ajouter de nouveaux champs de données à un ancien fichier de jeu de données. Par exemple, un ancien jeu de données peut avoir un champ ID uniquement. Plus tard, nous décidons que nous avons besoin d'un champ ISACTIF. Je souhaite rouvrir mes données d'ID uniquement, puis les réenregistrer avec les valeurs ISACTIVE ajoutées. Par exemple:Delphi ClientDataSet - Comment ajouter de nouveaux champs de données à un ensemble de données existant?

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'. 

J'ai eu un coup d'oeil pour des questions similaires, le plus proche que j'ai trouvé un qui concerne l'ajout de nouveaux champs calculés à un seul ensemble de données. Cette méthode ci-dessus fonctionne très bien pour ajouter un champ calculé. À l'heure actuelle, la seule solution (désordonnée) que je puisse imaginer est de charger les données ID uniquement dans un jeu de données temporaire, puis de créer un nouveau jeu de données avec ID et ISACTIVE définis, puis de boucler le jeu de données ID-only. et copiez les enregistrements dans le nouvel ensemble de données.

+0

Est-ce que cette aide vous ? http://stackoverflow.com/questions/21293186/delphi-change-fields-definitions-of-a-tclientdataset-that-has-data – Graymatter

+0

hmm, oui d'une certaine façon. C'est une question assez bizarre que je pense ?! Quoi qu'il en soit, cette réponse http://stackoverflow.com/a/21295035/6620329 décrit ce que j'ai appelé une solution "désordonnée" à la fin de ma question.Peut-être que c'est la seule option qui s'offre à moi (la suggestion de MartynA d'éditer le fichier texte sur le disque). –

Répondre

1

Il existe un moyen simple de le faire.

Si vous avez un CDS avec un ID entier champ et une chaîne 80 champ Nom, et vous enregistrez l'ensemble de données XML, comme dans

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

le fichier XML résultant ressemblera à ceci (pour 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> 

Vous pouvez ensuite utiliser MSXML ou votre processeur XML favori pour faire le changement trivial d'ajouter nœud FIELD supplémentaire (s) à la METADATA définition datapacket du CCDS, pour ajouter le champ supplémentaire (s). Vous rechargez ensuite le CDS à partir du XML. Les valeurs des champs ajoutés seront NULL bien sûr et pour que cette technique fonctionne, vous ne devez pas avoir de TFields persistants définis sur le CDS au moment où vous le rechargez à partir du XML sauvegardé.

code Exemple:

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; 

De toute évidence, vous pouvez charger le fichier XML modifié en un autre lieu CDS si vous voulez.

Bien sûr, vous pouvez même ajouter les noeuds FIELD supplémentaires au code XML en les chargeant simplement dans un TStringList si vous étiez prêt à faire un certain nombre de changements de chaîne vous-même.

Fwiw, je suis tombé sur cette ruse lorsque vous essayez de modifier le code XML d'un CDS pour inclure des informations supplémentaires pour chaque nœud ROW dans le fichier XML; il s'est avéré que les processus LoadFromFile & LoadFromStream étaient complètement inconscients de l'information que j'avais ajoutée.

+0

C'est une réponse valide, et en fait plus facile à mettre en œuvre que je l'imaginais. Cependant, je ne vois pas vraiment d'avantage à cette approche par rapport à la solution "désordonnée" que j'ai décrite ci-dessus. –

+0

Chacun à son propre, je suppose. La raison pour laquelle je préfère cette approche est que vous la réécrivez comme une routine "boîte à outils", puis tout ce que vous avez besoin d'entrer est un peu de XML définissant les champs à ajouter (et éventuellement les valeurs de champs initiales). – MartynA

-1

Hm, quel est le problème avec les champs calculés? Pourriez-vous s'il vous plaît expliquer pourquoi vous ne voulez pas les utiliser?

Un indice supplémentaire: Avez-vous essayé InternalCalc (FieldKind = fkInternalCalc). Autant que je sache, il doit se comporter semblable au champ de données (ses valeurs sont stockées dans l'enregistrement du jeu de données)

PS et il n'y a pas besoin de recharger les données (fermer et rouvrir ensemble de données) lorsque vous l'ajout d'un champ calculé

+0

C'est l'objectif: [link] (http://i.imgur.com/4mLIlil.png). En d'autres termes, ajoutez de nouvelles données (nouveaux champs) au jeu de données précédemment enregistré. InternalCalc ou non ne fait aucune différence, ni n'est enregistré sur le disque lorsque vous appelez SaveToFile. Pourquoi ne veux-je pas utiliser le champ calculé? Il s'agit de la rétrocompatibilité. Il y a beaucoup de "fichiers" sauvegardés dans la nature avec une structure ancienne. J'ai besoin d'ajouter de nouvelles propriétés aux anciens fichiers lorsqu'un utilisateur ouvre et le ré-enregistre. TClientDataSet ne facilite pas cette tâche! –

+0

Et une note de côté: L'ajout d'un nouveau champ InternalCalc à un TClientDataSet ouvert provoque certainement 'EDatabaseError with message 'Impossible d'effectuer cette opération sur un dataset ouvert'. –