Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   TFDJSONDataSetsReader liefert immer das letzte DataSet (https://www.delphipraxis.net/185708-tfdjsondatasetsreader-liefert-immer-das-letzte-dataset.html)

Kostas 30. Jun 2015 22:21

TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Zusammen,

vorab: ich suche keine Lösung sondern berichte über zwei Bugs die ich gefunden habe, zumindest empfinde ich es als Bug.


------------Problem 1----------------
Ich habe eine TFDQuery
Delphi-Quellcode:
SELECT * FROM &TableNamen WHERE UEBERTRAGEN = 1
dessen Tabellenname per Makro übergeben wird.
Insgesamt sind es fünf Tabellen die in ein TFDJSONDataSets geschrieben werden.
Ich gehe also in einer Schleife durch. Dabei setze ich den Tabellennamen und schreibe die Daten in das TFDJSONDataSets
über die Methode:
Delphi-Quellcode:
TFDJSONDataSetsWriter.ListAdd(ExportFDJSONDataSets, TableName, qry);
Die drei Parameter sind:
-ExportFDJSONDataSets das ist das TFDJSONDataSets
-TableName den eindeutige Tabellennamen
-qry immer das gleiche TFDQuery welches die Daten der aktuellen Datei darstellt.

ACHTUNG: Das Problem ist, ich übergebe immer das gleiche Object "qry" eigentlich sollte das keine Problem sein, ist es jedoch.
Deutlich wird es wenn man sich anschaut was exportiert wird. Der Objectname "qry" wird als "Table Name" exportiert und nicht wie erwartet,
nach meiner Vorgabe über "TableName"
Code:
 <Table Name="qry" SourceName="KONTAKTE" SourceID="1" TabID="0" EnforceConstraints="False" MinimumCapacity="50">
Das hat zur Folge, beim schreiben der DataSets ins TFDJSONDataSets, überschreibt das letzte DataSet alle DataSets da sie den gleichen Namen haben "qry"
Somit sind alle fünf DataSets mit dem gleichen Inhalt!
Delphi-Quellcode:
LDataSet := TFDJSONDataSetsReader.GetListValueByName(FJSONDataSets, TableName); //Auslesen über den Namen
Delphi-Quellcode:
LDataSet := TFDJSONDataSetsReader.GetListValue(FJSONDataSets, I); //Auslesen über den Index
Wenn ich jedoch für jede Query ein eigene TFDQuery verwende die auch jeweils einen anderen Namen hat, funktioniert das Auslesen einwandfrei.


------------Problem 2----------------
Wenn das TFDJSONDataSets erzeugt und noch kein DataSet per "TFDJSONDataSetsWriter.ListAdd" geschrieben wurde,
würde ich erwarten dass
Delphi-Quellcode:
  TFDJSONDataSetsReader.GetListCount(FJSONDataSets)
0 zurückgibt.
Das ist nicht so, es kommt eine Exception dass kein DataSet vorhanden ist.

Wenn man den Source anschaut ist es klar, AList.FDataSets ist nil! Eigentlich sollte hier die zwei or Bedingung weg und nil zurückgegeben werden.
Die aufrufende Methode prüft bereits auf nil dieser Methode und gibt richtigerweise auch als Result := 0; zurück.
Delphi-Quellcode:
class function TFDJSONDataSetsReaderBase.GetPairList(
  const AList: TFDJSONDataSetsBase): TPairList;
begin
  if (AList = nil) or (AList.FDataSets = nil) then
    raise EFDDataListError.Create(sCantReadNilList);
  Result := AList.FDataSets;

end;

------------Problem 3----------------
Der erzeugte JSON Text ist zu geschwätzig. Meine fünf Tabellen habe ich als CSV exportiert, erste Zeile die Feldbezeichner und danach die Records.
Alle fünf Tabellen zusammen ergeben etwa 1300 Bytes. Exportiere ich aus den fünf Tabellen das JSON in ein File hat dieses File 15000 Bytes.
Dabei ist auffällig, zu jedem Feld bei jedem Record werden die Feldbezeichner mit exportiert. Auch weitere Daten werden exportiert. Z.B.
für jedes Feld werden stolze 17 Eigenschaften exportiert. Somit ist die eigentliche Stärke von JSON ausgehebelt wie ich finde.

Code:
"RowList":[{"RowID":0,"Original":{"KONTAKTID":700002,"PERSONID":700002,"KONTAKTTYP":1,"KONTAKT":"11111111111","UEBERTRAGEN":true}}]}]
Als Beispiel habe ich einen Datensatz exportiert. Die Nutzdaten sind "700002, 700002, 1, '11111111111', true"
Ihr könnt das JSON anzeigen lassen z.B.: mit dem Viewer: http://codebeautify.org/jsonviewer


Gruß Kostas

[Edit] getestet mit Delphi XE7 und XE8 SP1

Sir Rufo 30. Jun 2015 23:25

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Die eigentliche Stärke von JSON sieht man nicht, wenn man einfach nur eine platte Tabelle überträgt. Versuch mal mit CSV mehrere komplexe Dokumente zu übertragen, dann sieht man was JSON kann.

Mit einem Trecker den Vorgarten pflügen ist umständlicher als mit dem Spaten, aber bei 3ha Acker sieht das anders aus.

Uwe Raabe 30. Jun 2015 23:31

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Zitat:

Zitat von Kostas (Beitrag 1307258)
Das Problem ist, ich übergebe immer das gleiche Object "qry" eigentlich sollte das keine Problem sein, ist es jedoch.

Das ist genau das Problem! TFDJSONDataSetsWriter.ListAdd speichert die Instanz in der Liste. Die Umwandlung in JSON geschieht erst später. Aus diesem Grund musst du nicht nur jeweils eine separate FDQuery mitgeben, sondern diese müssen auch außerhalb deiner aktuellen Routine weiter existieren.

Zitat:

Zitat von Kostas (Beitrag 1307258)
Der Objectname "qry" wird als "Table Name" exportiert und nicht wie erwartet,
nach meiner Vorgabe über "TableName"
Code:
 <Table Name="qry" SourceName="KONTAKTE" SourceID="1" TabID="0" EnforceConstraints="False" MinimumCapacity="50">

Du liest das falsch: "Table" ist der XML-Tag und "Name" das Attribut. Es handelt sich hier um das interne Storage-Format der Klasse TFDDatSTable. Der von dir übergebene Name taucht an dieser Stelle gar nicht auf.

Zitat:

Zitat von Kostas (Beitrag 1307258)
Wenn ich jedoch für jede Query ein eigene TFDQuery verwende die auch jeweils einen anderen Namen hat, funktioniert das Auslesen einwandfrei.

Genau so war es auch gedacht.


Zitat:

Zitat von Kostas (Beitrag 1307258)
Wenn das TFDJSONDataSets erzeugt und noch kein DataSet per "TFDJSONDataSetsWriter.ListAdd" geschrieben wurde,
würde ich erwarten dass
Delphi-Quellcode:
  TFDJSONDataSetsReader.GetListCount(FJSONDataSets)
0 zurückgibt.
Das ist nicht so, es kommt eine Exception dass kein DataSet vorhanden ist.

Das ist richtig! Ich empfehle einen Eintrag in quality.embarcadero.com

Zitat:

Zitat von Kostas (Beitrag 1307258)
Der erzeugte JSON Text ist zu geschwätzig. Meine fünf Tabellen habe ich als CSV exportiert, erste Zeile die Feldbezeichner und danach die Records.
Alle fünf Tabellen zusammen ergeben etwa 1300 Bytes. Exportiere ich aus den fünf Tabellen das JSON in ein File hat dieses File 15000 Bytes.
Dabei ist auffällig, zu jedem Feld bei jedem Record werden die Feldbezeichner mit exportiert. Auch weitere Daten werden exportiert. Z.B.
für jedes Feld werden stolze 17 Eigenschaften exportiert. Somit ist die eigentliche Stärke von JSON ausgehebelt wie ich finde.

Es gibt noch einen weiteren Nachteil: Dadurch, daß das interne Storage-Format übertragen wird, funktioniert das nur, wenn Sender und Empfänger mit derselben FireDAC-Version arbeiten. Das fällt insbesondere dann ins Gewicht, wenn man das JSON als externes, neutrales Speicherformat verwenden will. Das ist es hier nämlich nicht.

Allerdings ist das wohl kaum als Bug anzusehen. Allenfalls als ungeschickte Implementierung.

jaenicke 1. Jul 2015 03:24

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1307265)
Das ist genau das Problem! TFDJSONDataSetsWriter.ListAdd speichert die Instanz in der Liste. Die Umwandlung in JSON geschieht erst später. Aus diesem Grund musst du nicht nur jeweils eine separate FDQuery mitgeben, sondern diese müssen auch außerhalb deiner aktuellen Routine weiter existieren.

Wir haben das so gelöst, dass es eine TObjectList<TFDQuery> in der Servermethodenklasse gibt, die jeweils beim nächsten Aufruf der Methode zuerst geleert wird.

Uwe Raabe 1. Jul 2015 07:42

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Zitat:

Zitat von jaenicke (Beitrag 1307269)
Wir haben das so gelöst, dass es eine TObjectList<TFDQuery> in der Servermethodenklasse gibt, die jeweils beim nächsten Aufruf der Methode zuerst geleert wird.

So mache ich das in der Regel auch. Alternativ, wenn die Servermethodenklasse ein Datenmodul ist und natürlich wenn es praktikabel ist, kann man die Komponenten auch auf das Datenmodul legen. Das existiert immer über den gesamten Remote-Aufruf.

jaenicke 1. Jul 2015 09:06

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1307282)
Alternativ, wenn die Servermethodenklasse ein Datenmodul ist und natürlich wenn es praktikabel ist, kann man die Komponenten auch auf das Datenmodul legen.

In unserem Fall werden die zu ladenden Tabellen als SQL-Texte an die Servermethode übergeben. Deshalb ging das dort nicht.

Kostas 1. Jul 2015 09:44

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Zitat:

Zitat von Sir Rufo (Beitrag 1307264)
Die eigentliche Stärke von JSON sieht man nicht, wenn man einfach nur eine platte Tabelle überträgt. Versuch mal mit CSV mehrere komplexe Dokumente zu übertragen, dann sieht man was JSON kann.

Mit einem Trecker den Vorgarten pflügen ist umständlicher als mit dem Spaten, aber bei 3ha Acker sieht das anders aus.

Hallo Sir Rufo,
JSON hat die Eigenschaft die Feldnamen eine Section zu übertragen und die Records jeweils ohne Feldnamen. Darauf war
mein Kommentar gerichtet. Ich vergleiche XML mit JSON. Bei XML müssen die Feldnamen angegeben werden aber nicht in JSON.

Gruß Kostas

Kostas 1. Jul 2015 10:17

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Hallo Zusammen,

Wenn ich eine TFDQuery über
Delphi-Quellcode:
SaveToFile(File, sfXML)
ein XML schreibe,
beinhaltet das XML auch alle weitere Information dich auch in JSON enthalten sind.
Würde das bedeuten dass das XML nicht kompatibel wäre bei unterschiedlichen FireDac Versionen
oder ist das nur bei JSON?

Gruß Kostas

jaenicke 1. Jul 2015 11:03

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Ich habe es nicht ausprobiert, aber ich gehe davon aus, dass auch das nicht kompatibel sein muss.

Uwe Raabe 1. Jul 2015 11:42

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Der Unterschied zum CSV-Ansatz ist eher, daß FireDAC hier Objekte überträgt und nicht nur die reinen Inhalte eines DataSets. Damit werden insbesondere auch interne Zustände und falls vorhanden auch unterschiedliche Versionen der DataRows (Original, Current und Proposed) übertragen. Am Zielort entsteht so mehr oder weniger eine exakte Kopie der FDQuery-Instanz. Diese Art der Serialisierung ist halt bereits vorhanden ist und muss für JSON-Reflect nicht speziell für DataSets neu implementiert werden.

Andererseits hindert dich aber auch niemand daran, aus dem DataSet eine CSV-Repräsentation zu machen, diese zu übertragen und am Ziel in ein anderes DataSet einzulesen. Die BatchMove-Funktionalität in FireDAC kann dort sicher Hilfe leisten.

Kostas 1. Jul 2015 16:06

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Hallo Uwe,

ich verwende aus einem andere Thread deine zwei Methoden um ein FDDataSet als JSON zu exportieren.

Sorgen macht mir deine Aussage dass in späteren Version das JSON? String nicht kompatibel sein könnte
allerdings war hier die Rede vom binary Format. In meinem Fall werden die JSON String in eine DB als Text
abgelegt. Der Anwender kann irgendwann auch nach einem Jahr das Paket anfordern. Dabei wird der JSON Text
in eine FDMemTable geschrieben und verarbeitet.

Weil es wichtig ist, wie wahrscheinlich könnte es sein dass der gespeicherte JSON Text nicht in eine FDMemTable
geschrieben werden kann?

Delphi-Quellcode:
function DataSetToJSON(Source: TFDDataSet): string;
var
   stream: TStringStream;
begin
   stream := TStringStream.Create;
   try
     Source.Open;
     Source.FetchAll;
     Source.SaveToStream(stream, TFDStorageFormat.sfJSON);
     result := stream.DataString;
   finally
     stream.Free;
   end;
end;

procedure JSONToDataSet(const Source: string; Target: TFDDataSet);
var
   stream: TStringStream;
begin
   stream := TStringStream.Create(Source);
   try
     Target.LoadFromStream(stream, TFDStorageFormat.sfJSON);
   finally
     stream.Free;
   end;
end;
Gruß Kostas

Uwe Raabe 1. Jul 2015 17:26

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Zitat:

Zitat von Kostas (Beitrag 1307377)
Sorgen macht mir deine Aussage dass in späteren Version das JSON? String nicht kompatibel sein könnte
allerdings war hier die Rede vom binary Format. In meinem Fall werden die JSON String in eine DB als Text
abgelegt. Der Anwender kann irgendwann auch nach einem Jahr das Paket anfordern. Dabei wird der JSON Text
in eine FDMemTable geschrieben und verarbeitet.

Weil es wichtig ist, wie wahrscheinlich könnte es sein dass der gespeicherte JSON Text nicht in eine FDMemTable
geschrieben werden kann?

Die Implementierung bei SaveToStream und LoadFromStream ist innerhalb der jeweiligen Objekte untergebracht. Dabei werden beim Save im Wesentlichen die Objekteigenschaften geschrieben, die vom Default abweichen (ähnlich wie bei einer DFM-Datei). Beim Load wird dieselbe Reihenfolge der Eigenschaften verwendet, wobei da halt geprüft wird, ob das erwartete Property tatsächlich im Storage an dieser Stelle drin steht; falls nicht, wird der Defaultwert verwendet. Solange die Reihenfolge und Typen der Eigenschaften im Storage nicht geändert wird, sollte ein älteres Storageformat somit lesbar sein.

Ergänzend wird auch eine StreamVersion im Storage verwaltet, die als Konstante im FireDAC hinterlegt ist. Wenn das konsequent berücksichtigt wird (von EMBA), ist zumindest eine Abwärtskompatibilität (neuere FireDAC können ältere Storages lesen) gegeben. Umgekehrt ist das mit Sicherheit nicht so. In der Vergangenheit (AnyDAC) war das zumindest schon mal ein Problem.

Für dein Speicherproblem sind Probleme dieser Art wohl unwahrscheinlich, wenn auch nicht ausgeschlossen. Es ist ja durchaus möglich, daß der Support sehr alter Formate irgendwann entfernt wird (muss ja alles gepflegt werden). Natürlich sind auch Fehler bei der Interpretation möglich. Es ist sehr unwahrscheinlich, daß der Import älterer Formate durch ausreichend Tests abgedeckt ist. Willst du auf Nummer-Sicher gehen und es kommt dir wirklich nur auf die enthaltenen Daten an, dann wäre ein reiner Text-Export/Import á la CSV wohl die bessere Wahl (KISS, selbst wenn die Implementierung dadurch komlexer wird).

Was sicher problematisch wird, ist eine Kommunikation zwischen Client und Server mit unterschiedlichen Stream-Formaten. Für den Fall, daß XE8+ ein neues Stream-Format einführt, dann müssen eventuell sowohl der Server als auch alle Clients mit derselben Version compiliert werden. Sonst kann zumindest einer vielleicht das Stream-Format des anderen nicht lesen. Das kann bei vielen Mobile-Clients, die irgendwo unkontrolliert in der Weltgeschichte rumschwirren schon eine anspruchsvolle Aufgabe werden.

Kostas 1. Jul 2015 19:03

AW: TFDJSONDataSetsReader liefert immer das letzte DataSet
 
Tausend Dank Uwe für die sehr ausführliche Antwort.

In meinem Fall, kann ich dafür sorgen dass die Clients immer aktuell sind, ansonsten können sie einfach nicht senden, das ist völlig unkritisch.
Die gespeicherten Datenpakete werde ich riskieren und als JSON Text in die DB ablegen. Etwa 80% der Datenpakete beinhalten an die 15 Tabellen mit
insgesamt ca. 100 Datensätzen. Das ist schnell übertragen. Übrigens, ich habe heute eine Tabelle als JSON String mit 500.000 Datensätzen per DataSnap vom Client zum Server einfach über eine Methode als Parameter vom Typ String übertragen und am Server den String in ein StringStream gepackt und in die MemTable
importiert. Danach gleich als JSON wieder rausgeschrieben. Das File war 160MB groß es hat einwandfrei funktioniert. Solche Datenpakete muss ich nie übertragen.
Da in der DB der content JSON oder auch XML sein kann, habe ich den Vorteil dass ich es direkt in die MemTable aus der DB einlesen kann.
Falls sich wirklich die Struktur ändern sollte, kann ich alle Pakete auslesen und mit einem Tool welches ich dann schreiben würde in das neue Format konvertieren und wieder zurückschreiben. Vermutlich wird das nicht so oft vorkommen.

Du hast mir sehr geholfen, besten Dank Uwe.
Dir noch eine schöne Zeit.

Gruß Kostas


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:55 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz