Announcement

Collapse
No announcement yet.

ADO Recordset (schnell) in XML wandeln

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • ADO Recordset (schnell) in XML wandeln

    Hallo,<br>
    ich versuche, ein ADO-Recordset möglichst performant in XML zu wandeln. In einer Diskussion aus dem Jahre 2001 schrieb Andreas Kosch, dass es prinzipiell 3 Wege gäbe, dies zu tun.<br>
    <ul>
    <li>
    <font color="#000080">
    a) RecordSet-Objekt: SaveToFile (mit der pfXML-Anweisung), oder</font>
    </li>
    <li>
    <font color="#000080">
    b) RecordSet+Stream-Objekt:
    aStrm := CoStream.Create;
    aRS.Save(aStrm, adPersistXML);
    sXML := aStrm.ReadText(aStrm.Size);</font>
    </li>
    <li>
    <font color="#000080">
    c) DOMDocument-Objekt:
    aXML := CoDOMDocument30.Create;
    aRS.Save(aXML, adPersistXML);
    sXML := aXML.xml;</font>
    </li>
    </ul>
    <font color="#000080">
    Zitat: 'Der Weg c) ist von der Performance her gesehen der Schnellste.'<br>
    &nbsp;<br>
    </font>
    Weg a) kommt aus technischen Gründen nicht in Frage.
    <font color="#000080">
    <br>
    </font>
    Ein paar Test haben ergeben, dass Weg b) in der Praxis völlig inakzeptabel ist.
    Es scheint sogar als ob sich die benötigte Zeit mit steigender Datenmenge exponentiell
    erhöht.&nbsp; Weg c) führt zu akzeptablen Werten (=18 Sekunden, ca. Faktor 10
    bei 2000 Datensätzen und einer 'XML-Datenmenge' von 3,24 MB gegenüber Weg b))<br>
    &nbsp;<br>
    Es gibt jedoch noch einen Weg über den TStringStream, der noch schneller ist,
    als Weg c). Mit den obigen Testdaten nochmals um ca. Faktor 4. Das ist bei einer
    absoluten Zeit von 4 Sekunden gegenüber 16 Sekunden ein gravierender
    Performancegewinn.&nbsp;
    <pre>
    function RecordsetToXML(const aRS: _Recordset): string;

    var
    Stream : TStringStream;
    begin
    Result := '';
    if aRS = nil then Exit;
    Stream := TStringStream.Create('');
    try
    aRS.Save(TStreamAdapter.Create(Stream) as IUnknown, adPersistXML);
    Stream.Position := 0;
    Result := Stream.DataString;
    finally
    Stream.Free;
    end;

    end;</pre>

    Die Sache hat jedoch einen Haken: Irgendwie scheint der StringStream nicht mit
    dem Unicode aus aRS.Save zurechtukommen. Nach dem Speichern (aRS.Save) in den
    Stream, sind die Umlaute nicht mehr lesbar bzw. wurden durch andere Zeichen
    ersetzt.<br>
    Wie könnte man den Weg über den Stringstream verwenden, ohne dass die Umlaute
    verschwinden ? Es wäre ein sehr schneller Weg, an die XML Daten zu kommen.<br>
    Viele Grüsse an's Forum<br>
    Hermann<br>
    <br>
    Umgebung: ADO 2.7, Delphi 6.2, MS-SQL Server 7 (Codepage 1252)<br>
    &nbsp;

  • #2
    > Irgendwie scheint der StringStream nicht mit dem Unicode aus aRS.Save zurechtukommen. Nach dem Speichern (aRS.Save) in den Stream, sind die Umlaute nicht mehr lesbar bzw. wurden durch andere Zeichen ersetzt.

    Kann ich mir nicht vorstellen. Was passiert wenn man die XML-Datei mit dem IE öffnet. Ich vermute eher das es einfach nur die codierte Darstellung des Sonderzeichens ist

    Comment


    • #3
      Hallo,<br>
      der Stringstream selbst enthält (direkt nach Einlesen) die falschen Umlaute. Speichert man das Recordset via (ADODB_TLB.)_Recordset.save(Dateiname,adPersistXML ) in eine Datei, stehen die Umlaute richtig in der Datei. Auch wenn man das Recordset in einen ADO-Stream über (ADODB_TLB.)_Recordset.save(ADOtream,adPersistXML) schreibt, stehen in dem ADO-Stream die richtigen Umlaute. Nur (!) wenn man über den Streamadapter in einen Stringstream schreibt, gehen die Umlaute verloren. Es scheint also in der Tat beim Zuweisen an den Stringstream zu passieren.<br>
      Viele Grüsse<br>
      Herman

      Comment


      • #4
        <p>Hallo,<br>
        ich bin der Sache mal noch ein wenig nachgegangen und habe folgendes festgestellt:</p>
        <ul>
        <li>Immer dann, wenn das Recordset mit save über TStreamadapter in einen
        TCustomStream (d.h. es betrifft auch alle Nachfolger) geschrieben wird,
        ändert sich irgendetwas an der Darstellung des Strings.</li>
        <li>Da ich in der Unicode- und Ansidarstellung von Strings nicht so bewandert
        bin, kann ich hier nur kurz beschreiben, wie der Inhalt des Streams oder der
        Datei aussieht (wenn man den Stream in eine Datei schreibt):<br>
        Alle Standard-Ascii Zeichen werden mit einem Byte dargestellt. Für Umlaute
        werden 2(!) Bytes benutzt. Z.B. für das 'ä' C3h und A4h, für das 'ö' 4Dh
        C3h.<br>
        </li>
        </ul>
        <p>Hier die große Frage: Wie kann man aus dieser Art Stringcodierung wieder
        einen String machen, in dem die Umlaute richtig dargestellt werden ?</p>
        <p>Vielen Dank für Tipps und Grüsse<br>
        Hermann</p>

        &#10

        Comment


        • #5
          <p>Hallo,</p>
          <p>ich bin der Sache nochmal ein Stück näher gekommen: Mit UTF8ToAnsi(aString) kann man
          den (Data-)String aus dem Stream wieder korrekt mit Umlauten darstellen. Nur - warum ist das so ? Weiss vielleicht jemand Rat ?<br>
          <br>
          Hier nochmal der Code:
          </p>
          <pre>function RecordsetToXML(const aRS: _Recordset): string; var
          Stream : TStringStream;
          begin
          Result := '';
          if aRS = nil then Exit;
          Stream := TStringStream.Create('');
          try
          aRS.Save(TStreamAdapter.Create(Stream) as IUnknown, adPersistXML);
          Stream.Position := 0;
          Result := Stream.DataString; // Das Result enthält für jeden Umlaut zwei Byte

          <b> Result := UTF8ToAnsi(Result); // Damit werden die Umlaute wieder korrekt dargestellt
          </b>
          finally
          Stream.Free;
          end;
          end;
          </pre>
          <br>
          Viele Grüsse
          Herman

          Comment


          • #6
            Was steht im XML-Header für ein Codierung?

            Wenn <pre><?xml version="1.0" encoding="UTF-8" ?></pre> drin steht ist es klar.

            Eine XML-Datei sollte man auch entsprechend wieder mit einem <a href="http://www.philo.de/xml/downloads_de.shtml">XML-Parser</a> einlesen, welche alle XML-Codierungsregeln kennt. Ein einfaches einlesen mit einem TStringStream ist m.E. der falsche Weg

            Comment


            • #7
              <p>Hallo,</p>
              der Header sieht so aus:
              <p>&lt;xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'<br>
              xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'<br>
              xmlns:rs='urn:schemas-microsoft-com:rowset'<br>
              xmlns:z='#RowsetSchema'></p>
              <p>Ich denke auch, dass das Einlesen mit einem XML-Parser der 'ordentlichere'
              Weg wäre. Aber der Weg über den Stringstream ist so verführerisch, weil er im
              Vergleich zum ADO-Stream bzw. zum MSXML Parser so rasend schnell ist (In meinem
              Beispiel um Faktor 44 zum ADO-Stream und Faktor 4 zum XML-Parser).<br>
              Da die Routine in einem Webservice genutzt wird, auf den u.U. einige Benutzer
              gleichzeitig zugreifen, ist Geschwindigkeit in diesem Fall eminent wichtig.<br>
              Da ich mir jedoch nicht ganz sicher bin, ob das Umwandeln mit UTF8ToAnsi immer
              und unter allen Umständen funktioniert, werde ich wohl bei der MSXML Version
              bleiben.</p>
              <p>Hier nochmal die drei unterschiedlichen Wege mit den entsprechenden
              Messergebnissen (als Orientierung: Die Tabelle enthält 2000 Datensätze und ist
              im XML-Format ca. 3,24 MB gross)<br>
              </p>
              <p><b><font color="#800000">Langsam (ADO-Stream)</font></b><br>
              Erzeugen des XML-Streams mit <font color="#008080">aRS.Save</font>:&nbsp;453 ms<br>
              Auslesen aus dem Stream in String mit <font color="#008080">aStrm.ReadText</font>: 18.172 ms</p>
              <pre>function RecordsetToXML(const aRS: _Recordset): String;
              var
              aStrm : Stream;
              begin
              aStrm := CoStream.Create;
              aRS.Save(aStrm, adPersistXML);
              Result := aStrm.ReadText(aStrm.Size);
              aStrm.Close;
              end;

              </pre>
              <p><font color="#800000"><b>Akzeptabel (XMLDomDocument)</b></font><br>
              Erzeugen des XML-Streams mit <font color="#008080">aRS.Save</font>:&nbsp;1500 ms<br>
              Auslesen aus dem Stream in String mit <font color="#008080">Result := aXML.xml</font>: 234 ms</p>
              <pre>
              function RecordsetToXML(const aRS: _Recordset): string;
              var
              aXML : IXMLDomDocument2;
              begin
              aXML := CoDOMDocument30.Create;
              aRS.Save(aXML, adPersistXML);
              Result := aXML.xml;
              end;
              </pre>
              <p><font color="#800000"><b>Sehr schnell (TStringStream):</b></font><br>
              Erzeugen des XML-Streams mit <font color="#008080">aRS.Save</font>:&nbsp;422 ms<br>
              Auslesen aus dem Stream in String mit <font color="#008080">Result :=
              UTF8ToAnsi(Stream.DataString)</font>: ca. 0 ms</p>
              <pre>function RecordsetToXML(const aRS: ADODB_TLB._Recordset): string;
              var
              Stream : TStringStream;
              begin
              Stream := TStringStream.Create('');
              try
              aRS.Save(TStreamAdapter.Create(Stream) as IUnknown, adPersistXML);
              Stream.Position := 0;
              Result := UTF8ToAnsi(Stream.DataString);
              finally
              Stream.Free;
              end;
              end;
              </pre>
              <p>
              Viele Grüsse
              Hermann</p>

              &#10

              Comment


              • #8
                <p>Ich denke, ich habe die Lösung:</p>
                <p>Im MSDN steht unter</p>
                <p><a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdconxmlpersistformat.asp" target="_blank">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdconxmlpersistformat.asp</a></p>
                <p>folgendes geschrieben:<br>
                <br>
                <b>XML Persistence Format</b><br>
                ADO uses UTF-8 encoding for the XML stream it persists.<br>
                .....<br>
                </p>
                <p>Somit wird auch klar, wieso es zu den genannten Erscheinungen kommt. Da sich
                bei dem ADO- bzw. MSXML-Weg Microsoft selbst um den Stream kümmert, kommen beim
                Schreiben und Lesen auch korrekte Ergebnisse heraus. Übernimmt man den
                XML-Inhalt ohne sich darum zu kümmern, dass er in UTF-8 kodiert ist, erhält
                man bei der direkten Darstellung ohne weiter Dekodierung die falschen Zeichen
                für Umlaute.</p&gt

                Comment


                • #9
                  > Aber der Weg über den Stringstream ist so verführerisch

                  Verführerisch vieleicht, aber Du mußt eigentlich alle Codierungsvorschriften nachbauen die für XML nötig sind. Und dann wird es auch wieder langsamer.

                  > ... werde ich wohl bei der MSXML Version bleiben

                  Probier trotzdem mal den OpenXML-Parser aus. Evtl. wird es auch schneller wenn Du nur eine Parsung vornimmst aber nicht den DOM aufbauen läßt (Beispiel bei OpenXML vorhanden)

                  Comment


                  • #10
                    Hallo<br>
                    ich brauche ja gar nicht zu parsen. Ich will das XML lediglich aus dem ADO-Recordset holen, mit ZLIB packen, über einen Webservice zum Client transportieren und unverändert wieder einem ADO-Recordset unterschieben. Von daher benötige ich (für diesen Zweck) eigentlich keinen Parser. Und da ich der Aussage von Microsoft vertraue, werde ich nun doch TStringStream benutzen, eine UTF-8 Dekodierung vornehmen und alles wird gut...<br&gt

                    Comment


                    • #11
                      Und wieso willst du es dann per UTF-8 dekodieren?

                      ADO-Recordset speichern, ZLib packen, an Client schicken. Auf Client entpacken und einem Recordset wieder zuschieben sollte doch reichen. ZLib ist eigentlich nur nötig damit etwas weniger Bandbreite benötigt wird.

                      Wenn Du den String-Stream veränderst (Utf8ToAnsiString) so haßt du kein gültiges XML-Dokument mehr und das Einlesen wird fehl schlagen

                      Comment


                      • #12
                        <p>Hallo</p>
                        <p>&gt; ADO-Recordset speichern, ZLib packen, an Client schicken</p>
                        <p>Genau zwischen ADO-Recordset speichern und ZLib packen kommt der Stream in's
                        Spiel, denn ich muss ja das Recordset erstmal in XML überführen (bevor ich es
                        packe) und genau das ist mit den drei beschriebenen Wegen möglich, wobei die
                        Variante mit TStringStream die mit Abstand (!) schnellste ist.</p>
                        <p>&gt; Wenn Du den String-Stream veränderst (Utf8ToAnsiString) so haßt du
                        kein gültiges XML-Dokument mehr und das Einlesen wird fehl schlagen.</p>
                        <p><b>Server:</b><br>
                        ADO-Recordset -&gt; Save in StringStream (ergibt UTF-8 String) -&gt; UTF8ToAnsiString
                        (ergibt AnsiString, und ein XML Recordset wie es schöner nicht aussehen kann
                        d.h. mit korrekten Umlauten) -&gt; AnsiString mit ZLIB packen (ergibt binären
                        Stream) -&gt; Binären ZLIB Stream in Base64 kodieren <br>
                        </p>
                        <p><b>Transport:</b><br>
                        Base64 String an Client übertragen (z.B. per Webservice oder HTTP) </p>
                        <p><b>Client:</b><br>
                        Auf Clientseite Base64 dekodieren und mit ZLIB auspacken (ergibt wieder
                        Ansi-String) -&gt; AnsiString (das XML-Recordset) an ADO-Recordset zuweisen.</p>
                        <p>-&gt; funktioniert fabelhaft<br>
                        </p>
                        <p>Es muss funktionieren weil ADOStream.ReadText(ADOStream.Size) (die erste
                        Variante) auch einen AnsiString (!) liefert, der rückwärts auch wieder an das
                        ADO-Recordset zugewiesen werden kann (sogar zugewiesen werde muss, weil das
                        Zuweisen des UTF-8 Strings für die Umlaute ja nicht funktioniert - mein
                        anfängliches Problem). Ich vermute auch, dass ADOStream.ReadText hinter den
                        Kulissen viel Zeit für das Wandeln in einen AnsiString benötigt und deswegen
                        so langsam ist.</p>
                        <p>&gt; ZLib ist eigentlich nur nötig damit etwas weniger Bandbreite benötigt
                        wird.</p>
                        <p>In meinem Beispiel (s. oben) werden damit aus 3,24 MB Daten ca. 150 kB Daten,
                        die zu übertragen sind. Das ist ein Faktor von ca. 22<br>
                        <br>
                        Auf jeden Fall funktioniert der Transport auf diesem Weg jetzt korrekt und sehr,
                        sehr schnell (verglichen mit Recordet.Save(ADOStream, adPersistXML) und
                        anschliessendem ADOStream.ReadText.</p>
                        <p>Viele Grüsse<br>
                        Hermann</p&gt

                        Comment


                        • #13
                          Hallo,

                          ich finde trotzdem das das UTF8ToAnsiString-Codieren eigentlich unnötig ist bzw. zu einem fehler führen sollte.

                          Aber evtl. ist dein Code dahingehend fehlerhaft das irgenwo bei der Zuweisung an das Recordset auf dem Client unnötigerweise Delphi-ADOExpress-Funktionen verwendet werden welche keine UTF8-Codierten Daten erwarten. Andreas Kosch hat hier im Forum schon öfters Beispiel gepostet wie man ein disconnected Recordset vom Server zum Client schicken kann (ohne eigene Komprimierung).

                          Was würde wohl mit deinem Daten basieren wenn Unicodezeichen (z.B. kyrilisch) vorhanden wären? XML und ADO kann mit Unicode umgehen. Delphi hat hier vielmehr einschränkungen (fast alle Funktionen/Komponenten sind nur ANSI-Versionen). Und deine Lösung würde diese Zeichen auch zerstören

                          Comment


                          • #14
                            <p><font color="#008080">&gt; ich finde trotzdem das das
                            UTF8ToAnsiString-Codieren eigentlich unnötig ist bzw. zu einem fehler führen
                            sollte.</font><br>
                            Du kannst ja mal probieren, ein in UTF-8 kodiertes XML-Recordset (mit Umlauten),
                            so wie es von Recordet.Save(Stringstreamadapter, adPersistXML) erzeugt wird,
                            einem ADO-Recordset zuzuweisen. Du wirst sehen, dass dort die falschen Umlaute
                            ankommen.&nbsp;</p>
                            <p><font color="#008080">&gt; Aber evtl. ist dein Code dahingehend fehlerhaft das irgenwo bei der Zuweisung an das Recordset auf dem Client unnötigerweise Delphi-ADOExpress-Funktionen verwendet werden welche keine UTF8-Codierten Daten erwarten.&nbsp;</font><br>
                            Es wird ausschliesslich mit den nativen ADO-Objekten gearbeitet</p>
                            <p><font color="#008080">&gt; Andreas Kosch hat hier im Forum schon öfters Beispiel gepostet wie man ein disconnected Recordset vom Server zum Client schicken kann (ohne eigene Komprimierung).</font><br>
                            Warum sollte man auf eine eigene Komprimierung verzichten ? Man könnte ja sogar
                            noch einen Schritt weitergehen und die Daten selbst verschlüsseln. Der allseits
                            bekannte Weg über den ADO-Stream, der in diversen Beispielen hier im Forum als auch in der Literatur
                            von Andreas Kosch gezeigt wird, ist zwar elegant, aber nicht schnell, wie die
                            obigen Tests belegen. Es gibt halt viele Wege, die nach Rom führen.</p&gt

                            Comment


                            • #15
                              > Du kannst ja mal probieren, ein in UTF-8 kodiertes XML-Recordset (mit Umlauten), so wie es von Recordet.Save(Stringstreamadapter, adPersistXML) erzeugt wird, einem ADO-Recordset zuzuweisen. Du wirst sehen, dass dort die falschen Umlaute ankommen.

                              Hab ich probiert. Jedoch einfach "nur" in eine Datei geschrieben. Und dort passt der String (Auch mit Russisch, Chinesisch, ...) wenn man ihn wieder einließt:

                              Schreiben:
                              <pre>
                              ADOConnection.Connected := True;
                              dsConnected.Open;
                              dsConnected.Recordset.Save(edtXML.Text, adPersistXML);
                              </pre>

                              Lesen:
                              <pre>
                              dsDisconnected.LoadFromFile(edtXML.Text);
                              dsDisconnected.Open;
                              </pre>

                              Ich vermute das Problem liegt beim StringStreamAdapter (Ist ja auch 'ne Delphi-Klasse).

                              > Warum sollte man auf eine eigene Komprimierung verzichten
                              Spricht ja auch nichts dagegen. Die Beispiel von Kosch sollen ja erst mal das Prinzip zeigen. Und das man bei XML immer sehr einfach komprimieren kann ist ja bekannt

                              Comment

                              Working...
                              X