C# Welche Technik für XML Reader und Writer verwenden.

Magic1416

Lieutenant
Registriert
Dez. 2003
Beiträge
530
Hi,

ich möchte einen TreeList (ein TreeView von DevExpress) erstellen, welcher ziemlich viele Nodes enthält. Die Datenbasis ist bzw. soll dabei XML sein und bleiben. Stand jetzt ist jeder Node auch ein kleines XML im Filesystem auf einem Share. Dadurch gibt es automatisch auch eine Hierarchie durch die Unterverzeichnisse, jed nach dem wie tief das XML File liegt.
Allerdings hat sich dies als inperformant herausgestellt, je mehr Nodes es sind und werden.
Also soll das ganze jetzt konsolidiert werden. Nicht mehr viele kleine XML Files, sondern ein großes File soll es werden. Nun stellt sich mir die Frage, welche Technik ich für das Lesen bzw. Schreiben des Files verwenden soll.

System.Xml.Document habe ich bisher verwendet. Ist eigentlich schön einfach zum einlesen und zum wegschreiben. Hat aber, wenn man googlet wohl starke Performance-Einbussen wenn es große XML Files sind.

Xml.Linq wäre auch noch eine Idee. Habe ich aber noch nie gemacht und weiß daher nicht, ob es sich am Ende von der Performance her lohnt.

XMLReader / XMLWriter (incl. XMLTextReader und XMLTextWriter) liest das ganze der Reihe nach ein und soll schnell sein. Im Prinzip das Richtige für den Programmstart. Aber wie würde man dann das XML am besten editieren, ohne es komplett neu zu schreiben ?

CodeProjekt Nano XML: Soll wohl das schnellste sein, was das Einlesen betrifft. Könnte ich mir zumindest für den Programmstart vorstellen. Aber auch da stell ich mir die Frage, welche Technik für performantes Schreiben.

XMLSerializer: Eigentlich schön. Aber die Objekte dahinter sind zu dynamisch und komplex. Möchte ich nicht hernehmen


Außerdem stell ich mir die Frage, ob ich die Hierarchie der Ordner im XML abbilden soll, also die Elemente entsprechend tief verschachteln soll, oder ob ich gar keine Hierarchie im XML abbilde und die Hierarchie Ebene lieber als Pfadangabe in einem Attribut abspeichere. Ich könnte dann alle Nodes auf einmal in ein Array packen, diese dann entsprechend sortieren und daraufhin meinen Tree aufbauen.

Also um noch einmal kurz zusammenzufassen:

1. Welche Technik würdet ihr zum Einlesen großer XML Dateien verwenden
2. Welche Technik zum Schreiben bzw. Updaten des Files.
3. Wie würdet ihr das Programm benachrichtigen, wenn ein anderer User Änderung am XML vornimmt.

Wichtig ist, dass es schnell ist.


Danke Gruß Magic
 
Wichtig ist, dass es korrekt ist.
Nehm die Lib, die dir am sympathischsten ist.
Wenn es dann zu langsam wird, überleg dir, warum. Du hast ja schon rausgefunden, dass viele kleine Dateien nicht so pralle sind. (Mglw. ist XML gar nicht das richtige Format für dein Problem (Datenbanken?)).
 
Magic1416 schrieb:
ich möchte einen TreeList (ein TreeView von DevExpress) erstellen, welcher ziemlich viele Nodes enthält. Die Datenbasis ist bzw. soll dabei XML sein und bleiben. Stand jetzt ist jeder Node auch ein kleines XML im Filesystem auf einem Share. Dadurch gibt es automatisch auch eine Hierarchie durch die Unterverzeichnisse, jed nach dem wie tief das XML File liegt.
Allerdings hat sich dies als inperformant herausgestellt, je mehr Nodes es sind und werden.

Was genau ist zu langsam? Den Baum zu erstellen? Einzelne Dateien zu laden?

Wie wird der Baum erstellt? Findet alles synchron statt? Oder geschieht das schon nebenläufig? Werden immer alle XML-Dateien gelesen? Oder nur bei Bedarf?
 
Was genau ist zu langsam? Den Baum zu erstellen? Einzelne Dateien zu laden?

Wie wird der Baum erstellt? Findet alles synchron statt? Oder geschieht das schon nebenläufig? Werden immer alle XML-Dateien gelesen? Oder nur bei Bedarf?

Langsam wird das Lesen der Dateien wenn es viele werden. Es ist grob gesagt so programmiert:

1. Schaue ins Datenverzeichnis. Dort sind noch keine XML Files sondern nur Ordner in denen sich dann die Files hierarchisch angeordnet befinden.
2. Jeder dieser Ordner ist ein Hauptknoten im Tree.
3. Der Worker Thread wird mittels Async Await gestartet.
4. Dann mittels Parallel.Foreach durch diese Ordner iterieren. Also gibt es soviele Threads wie Hauptknoten zum Auslesen.
4. Aus einem XML File wird ein Objekt, wiederum daraus am Ende ein Node. Alles Asynchron.
5. Das Appenden zum TreeView muss seriell erfolgen. Also ist dieses Code-Schnippsel gelockt.
6. Warten bis alle Threads fertig sind.
7. Das Zeichnen des Trees aktivieren. Die Methode dazu über das Synchronization Objekt.Post() an den Vordergrund Thread schicken.

Soweit so gut.

Zwei Probleme, sind aufgetreten: Der Kunde kam auf die Idee, das Programm für etwas zu benutzen, für das es nicht entwickelt worden ist. Also sind in einem Verzeichnis, die ein Thread abarbeiten soll ca. 30000 MiniXML Files drin, während alle anderen irrelevant klein sind. ca. 50 - 300 Files.

Also sind alle Threads fertig, bis auf der eine. Also programmierte ich, ein Nachladen on Demand. Es werden also nur zwei Ebenen auf einmal gelesen und nicht alle auf einmal. Nachgeladen wird dann beim Aufklappen einzelner Subnodes.
Das Problem: Der Kunde beschwert sich jetzt, dass der TreeFilter nicht mehr funktioniert, was klar ist. Was noch nicht da ist, kann nicht gefiltert werden.

Zu dem o.g "Problemen" gesellt sich auch ein Laufzeitproblem hinzu welches ich nicht getrackt bekomme. Manchmal sind nicht alle Nodes da. Es wurden welche einfach überlesen. Muss irgendwas mit dem Threading sein weil Singlethreaded trat das noch nie auf.

Warum keine Datenbank ? Weil es die Anforderung gewesen ist, keine zusätzliche Infrastruktur zu verwenden. File basierend und so kompatibel wie möglich.

Daher die Idee die MiniFiles in einzelne große XML zu verpacken. Es soll, so erst einmal geplant, für jeden Hauptknoten ein XMLFile existieren, welche ich wieder Asynchron verarbeiten möchte.
 
Magic1416 schrieb:
Langsam wird das Lesen der Dateien wenn es viele werden. Es ist grob gesagt so programmiert:

1. Schaue ins Datenverzeichnis. Dort sind noch keine XML Files sondern nur Ordner in denen sich dann die Files hierarchisch angeordnet befinden.
2. Jeder dieser Ordner ist ein Hauptknoten im Tree.
3. Der Worker Thread wird mittels Async Await gestartet.
4. Dann mittels Parallel.Foreach durch diese Ordner iterieren. Also gibt es soviele Threads wie Hauptknoten zum Auslesen.

Es werden erst die Ordner ermittelt und dann erst die Ordner-Inhalte verarbeitet?


Magic1416 schrieb:
4. Aus einem XML File wird ein Objekt, wiederum daraus am Ende ein Node. Alles Asynchron.

Wird die XML-Datei dabei gelesen oder wird nur ein Knoten erzeugt?


Magic1416 schrieb:
5. Das Appenden zum TreeView muss seriell erfolgen. Also ist dieses Code-Schnippsel gelockt.

Sehe ich nicht. Bei einer Breitensuche sind die Mutter-Elemente zuerst vorhanden und so lässt sich ein Knoten immer sofort aktualisieren. Es gibt nur einen UI-Thread. Das ist ein Engpass. Deswegen würde ich hier eine Queue verwenden, um die Dateisuche nicht aufzuhalten (Producer-Consumer).


Magic1416 schrieb:
6. Warten bis alle Threads fertig sind.

Warten? Echt jetzt? Direkt nachdem ein Ordner durchlaufen wurde, kann der jeweilige Knoten entsprechend aktualisiert werden. Erst danach die gefundenen Verzeichnisse durchlaufen. Da das Aktualisieren bei vielen Dateien langsam sein kann, würde ich das nur machen, wenn notwendig, d.h. Knoten für die Dateien nur erstellen, wenn sichtbar. Ansonsten einen Platzhalter verwenden bzw. aktualisieren und On-Demand laden.


Magic1416 schrieb:
Daher die Idee die MiniFiles in einzelne große XML zu verpacken. Es soll, so erst einmal geplant, für jeden Hauptknoten ein XMLFile existieren, welche ich wieder Asynchron verarbeiten möchte.

Ich würde messen, wie schnell die Dateien ermittelt werden können. Wenn das nicht schnell genug ist, würde ich mir Alternativen überlegen. Ad hoc bin ich nicht überzeugt, dass man Metadaten einführen muss. Außerdem gibt es offensichtlich einen Fehler bei der Dateisuche, der behoben werden muss.

Was ist wenn der Baum bereits angezeigt wird? Wird das komplette Share noch mal durchsucht? Oder kommt dann ein FileSystemWatcher zum Zuge?
 
Es werden erst die Ordner ermittelt und dann erst die Ordner-Inhalte verarbeitet?

nein, das hast Du falsch verstanden. Hier ein Beispiel:

Datenverzeichnis:
1.JPG

In diesem Fall gibt es für jedes Verzeichnis einen Thread. Der Code hierfür ist sinngemäß:
Code:
//Enum directory
DirectoryInfo[] dirs = datenverzeichnis.GetDirectories();

//Process each directory
Parallel.ForEach(dirs, dir=>
                {
                    createNodes(dir);
                });

Die Daten in so einem Verzeichnis schauen so aus:
2.JPG


AUs jedem XML File wird am Ende ein Node im Treeview. Gibt es einen gleichnamigen Folder, so wird dieser Node SubNodes haben. Gibt es ein Folder, zu dem kein XML File existiert, so wird ein Node angelegt, der im Tree wie ein Verzeichnis aussieht.
Das sieht so aus:
3.JPG


Wird die XML-Datei dabei gelesen oder wird nur ein Knoten erzeugt?

Sehe ich nicht. Bei einer Breitensuche sind die Mutter-Elemente zuerst vorhanden und so lässt sich ein Knoten immer sofort aktualisieren. Es gibt nur einen UI-Thread. Das ist ein Engpass. Deswegen würde ich hier eine Queue verwenden, um die Dateisuche nicht aufzuhalten (Producer-Consumer)

Das XML wird in einem aufwasch gelesen, das Objekt erzeugt, ebenso der Treenode. Das Objekt wird an das Tag Objekt des Nodes gehängt.
Beim Erzeugen des TreeNodes wird dieser auch gleich appended. Das ist bei DevExpress TreeList einfach so.

Code:
childNode = AppendNewNode(parentnode, objXmlData.DisplayName);

//Add properties
childNode.Tag                      = objXmlData;
childNode.ImageIndex          = index;
childNode.SelectImageIndex  = index;

public TreeListNode AppendNewNode(TreeListNode parent, string nodename)
        {
            lock (_objAddTreeNodeLock)
            {
                TreeListNode childNode =  null;  //bei DevExpress gibt es kein new TreeListNode. Es gibt nur die AppendNode
                                                                //Methode welche Objekt erzeugt und gleichzeitig appended
                //Create new one
                childNode = _treeList.AppendNode(null, parent);   //Die Methode kann ich nicht gleichzeitig aufrufen. 

                //Set Node Name
                childNode.SetValue("Name", nodename);

                return childNode;
            }
        }
        object _objAddTreeNodeLock = new object();



Warten? Echt jetzt? Direkt nachdem ....

Was soll ich denn sonst tun ?
Du musst zu Beginn des Einlesens den treeList mit der Methode
Code:
treeList.BeginUnboundLoad();
vorbereiten. Wenn das Einlesen fertig ist, rufst Du die Methode
Code:
treeList.EndUnboundLoad();
auf. Dazwischen findet keine optische Aktualisierung
statt. Machst Du das nicht, aktualisiert sich der TreeList sofort bei jeder Änderung. Da das dann nicht im Mainthread passiert, musst Du das erst mit dem Sync.Context Object mit der Post Methode machen. Das ist Irrsinn. Das mach ich erst dann, wenn sich nur noch einzelne Nodes aktualisieren, aber nicht beim Laden.


Was ist wenn der Baum bereits angezeigt wird? Wird das komplette Share noch mal durchsucht? Oder kommt dann ein FileSystemWatcher zum Zuge?

Ja. Sobald jemand was ändert, wird die Änderung am Node sofort aktualisiert, sofern der Benutzer nicht den Fokus darauf hat. Ansonsten wird er informiert, dass eine Änderung in seinem fokusiertem Node bzw. Zweig stattgefunden hat.

Ich stecke in einem Dilemma. Ich finde die Idee mit den vielen XML Files und der hierarchischen Abbildung eigentlich ganz gut. Das Filehandling, die gemeinsamen Zugriffe, sind quasi an das Filesystem abgegeben und ich brauch mich nicht drum kümmern. Auch wenn sich ein Fehler in ein XML einschleicht, z.b. durch manuelles editieren, ist nur ein Node im schlimmsten Fall betroffen und nicht alles.
 
Meiner Meinung nach wärst du mit einer Datenbank trotzdem besser beraten, als einfach alles ins Dateisystem zu werfen, weil Dateizugriff einfach ein Flaschenhals ist.
Mit SQLite bräuchtest du auch keine zusätzliche Infrastruktur und auch gleichzeitige Zugriffe sind kein Problem.

Zwecks einfachem Zugriff:
Für SQLite gibts auch nen Powershell Provider der dich die DB als Laufwerk mounten lässt. Falls es unbedingt XML Files sein müssen, dürfte es auch nicht allzu schwer sein XML import/export für die DB zu implementieren.


Falls du trotzdem bei deiner momentanen Lösung bleiben willst, denke ich (wie soares schon erwähnt hat), dass ne Breitensuche die gefühlte Ladezeit extrem verkürzen kann.
Einfach immer das TreeView aktualisieren sobald ne Hierarchieebene komplett eingelesen ist.
 
Magic1416 schrieb:
Code:
//Enum directory
DirectoryInfo[] dirs = datenverzeichnis.GetDirectories();

//Process each directory
Parallel.ForEach(dirs, dir=>
                {
                    createNodes(dir);
                });

Ich würde testen, ob hier viele Threads etwas bringen, wenn später ohnehin alles von einem Lock abhängt. Mein Ansatz wäre ein Thread um das Share zu durchlaufen, hier nur die notwendigsten Daten zu ermitteln (sprich: den Dateipfad). Diese in eine Queue zu stellen. Und dann in einem anderen Thread abzuarbeiten, um den Baum zu erstellen. Damit muss man nicht warten, bis der ganze Baum erstellt ist, sondern sieht quasi sofort erste Knoten und kann schon navigieren, während im Hintergrund weiter geladen wird.


Magic1416 schrieb:
Das XML wird in einem aufwasch gelesen, das Objekt erzeugt, ebenso der Treenode.

Du liest wirklich alle XML-Dateien beim Start? Da wäre es natürlich kein Wunder, wenn das nicht skaliert. Im Baum wird doch nur der Name angezeigt. Die eigentliche XML-Datei würde ich auf jeden Fall on-demand laden.


Magic1416 schrieb:
Was soll ich denn sonst tun ?
Du musst zu Beginn des Einlesens den treeList mit der Methode
Code:
treeList.BeginUnboundLoad();
vorbereiten. Wenn das Einlesen fertig ist, rufst Du die Methode
Code:
treeList.EndUnboundLoad();
auf.

Das lässt sich sicher auch granular durchführen. Man muss dann nur aufpassen die Selektion nicht zu verändern und es darf nicht zu Scrolling kommen. Das sollte das Widget können.


Magic1416 schrieb:
Dazwischen findet keine optische Aktualisierung
statt. Machst Du das nicht, aktualisiert sich der TreeList sofort bei jeder Änderung. Da das dann nicht im Mainthread passiert, musst Du das erst mit dem Sync.Context Object mit der Post Methode machen. Das ist Irrsinn. Das mach ich erst dann, wenn sich nur noch einzelne Nodes aktualisieren, aber nicht beim Laden.

Hast Du das getestet und irgendwelche Probleme festgestellt?
 
Grantig schrieb:
Meiner Meinung nach wärst du mit einer Datenbank trotzdem besser beraten, als einfach alles ins Dateisystem zu werfen, weil Dateizugriff einfach ein Flaschenhals ist.
Mit SQLite bräuchtest du auch keine zusätzliche Infrastruktur und auch gleichzeitige Zugriffe sind kein Problem.

Du hast Recht. Ich werde mir die Doku dazu ansehen und nach und nach Klassen dazu entwickeln

soares schrieb:
Ich würde testen, ob hier viele Threads etwas bringen, wenn später ohnehin alles von einem Lock abhängt. Mein Ansatz wäre ein Thread um das Share zu durchlaufen, hier nur die notwendigsten Daten zu ermitteln (sprich: den Dateipfad). Diese in eine Queue zu stellen. Und dann in einem anderen Thread abzuarbeiten, um den Baum zu erstellen. Damit muss man nicht warten, bis der ganze Baum erstellt ist, sondern sieht quasi sofort erste Knoten und kann schon navigieren, während im Hintergrund weiter geladen wird.

Aber hast Du dann nicht exakt das selbe Ergebnis, wie wenn ich mein Code Singlethreaded laufen lassen würde ? Er würde doch in deinem Bsp. zuerst den ersten Hauptfolder mit allen Ebenen und deren Files nach und nach in die Queue stecken. Dann den nächsten Hauptfolder usw. Der zweite Thread würde dann auch erst den ersten Node aufbauen, dann den zweiten, den dritten usw.
Aber eigentlich will man doch erreichen, dass zuerst alle Hauptnodes, dann deren erste Ebene, dann die zweite, dritte Ebene usw. sich aufbauen, sodass ein User auf jeder beliebigen Hauptebene mit dem Navigieren beginnen kann.


soares schrieb:
Du liest wirklich alle XML-Dateien beim Start? Da wäre es natürlich kein Wunder, wenn das nicht skaliert. Im Baum wird doch nur der Name angezeigt. Die eigentliche XML-Datei würde ich auf jeden Fall on-demand laden.

Ja. Das Grundkonzept des Programmes war am Anfang anders als es letztendlich wurde. Im Laufe der Entwicklung fielen den Leuten immer mehr Sachen ein, die das Programm dann auch noch können sollte. z.b. der Treefilter. Dieser ist konfigurierbar. Er filtert nicht nur auf den Namen des Nodes. Man kann auch nach Inhalten innerhalb der Objekte filtern und würde dann alle Nodes angezeigt bekommen, die den Filterkriterien entsprechen, ganz unabhängig vom Namen. Das geht dann nur, wenn alle Daten auch im Speicher sind. Lade ich OnDemand, was kein Problem wäre, geht der Inhaltsfilter nicht. Eigentlich müsste ich für das OnDemand ja nur ein Stück Code auskommentieren :-) Die Funktionen sind drin.

soares schrieb:
Das lässt sich sicher auch granular durchführen. Man muss dann nur aufpassen die Selektion nicht zu verändern und es darf nicht zu Scrolling kommen. Das sollte das Widget können.

Kann ich dir AdHoc nicht beantworten. Ich muss an der Stelle die Doku lesen. Ggf auch einen Call aufmachen. Dafür zahlt man ja auch.



soares schrieb:
Hast Du das getestet und irgendwelche Probleme festgestellt?
ja. Der Treelist reagiert nicht mehr. Im schlimmsten Fall war das ganze Treelist Steuerelement mit einem roten X durchgestrichen.



Ich muss jetzt erstmal Nachdenken und ein paar Versuche mit dem Breitenladen und dem SQLite durchführen. Komme aber erst am Donnerstag zum programmieren.
 
Magic1416 schrieb:
Aber hast Du dann nicht exakt das selbe Ergebnis, wie wenn ich mein Code Singlethreaded laufen lassen würde ?

Am Ende steht der Baum mit allen Knoten, klar. Es geht hier doch aber darum, wie man ausreichend performant dazu kommt


Magic1416 schrieb:
Er würde doch in deinem Bsp. zuerst den ersten Hauptfolder mit allen Ebenen und deren Files nach und nach in die Queue stecken. Dann den nächsten Hauptfolder usw. Der zweite Thread würde dann auch erst den ersten Node aufbauen, dann den zweiten, den dritten usw.
Aber eigentlich will man doch erreichen, dass zuerst alle Hauptnodes, dann deren erste Ebene, dann die zweite, dritte Ebene usw. sich aufbauen, sodass ein User auf jeder beliebigen Hauptebene mit dem Navigieren beginnen kann.

Wenn man dass möchte, nutzt man eben keine Breitensuche, sondern Tiefensuche. Ändert an der Vorgehensweise mit den Threads nichts. Der Gedanke dahinter ist, Suche und Erstellen der Knoten zu entkoppeln. Je nachdem, wie schnell oder langsam die Suche ist, kann man dann auch mehr Threads drauf werfen. Aber man muss so nicht warten, bis der komplette Baum erstellt ist.


Magic1416 schrieb:
Ja. Das Grundkonzept des Programmes war am Anfang anders als es letztendlich wurde.

Aber dann hast Du doch einen Ansatz die Performanz zu verbessern. Geladen wird nur, was angeklickt wird.


Magic1416 schrieb:
Im Laufe der Entwicklung fielen den Leuten immer mehr Sachen ein, die das Programm dann auch noch können sollte. z.b. der Treefilter. Dieser ist konfigurierbar. Er filtert nicht nur auf den Namen des Nodes. Man kann auch nach Inhalten innerhalb der Objekte filtern und würde dann alle Nodes angezeigt bekommen, die den Filterkriterien entsprechen, ganz unabhängig vom Namen. Das geht dann nur, wenn alle Daten auch im Speicher sind. Lade ich OnDemand, was kein Problem wäre, geht der Inhaltsfilter nicht. Eigentlich müsste ich für das OnDemand ja nur ein Stück Code auskommentieren :-) Die Funktionen sind drin.

Ah, damit ist der Ansatz mit dem Dateisystem hinfällig. Geladen werden müssten den Dateien zwar nur, wenn wirklich ein entsprechender Filter zum Einsatz kommt. Aber wenn das der Fall ist, wird es bei vielen Dateien zu langsam. Da wäre dann eine Datenbank die richtige Lösung. Wobei man die natürlich auch nur für die Filterung verwenden könnte.


Magic1416 schrieb:
ja. Der Treelist reagiert nicht mehr. Im schlimmsten Fall war das ganze Treelist Steuerelement mit einem roten X durchgestrichen.

Ich würde davon ausgehen, dass der Code hier fehlerhaft war. Scheint ja ohnehin Fehler mit der Nebenläufigkeit zu geben.
 
Ich stecke in einem Dilemma. Ich finde die Idee mit den vielen XML Files und der hierarchischen Abbildung eigentlich ganz gut. Das Filehandling, die gemeinsamen Zugriffe, sind quasi an das Filesystem abgegeben und ich brauch mich nicht drum kümmern. Auch wenn sich ein Fehler in ein XML einschleicht, z.b. durch manuelles editieren, ist nur ein Node im schlimmsten Fall betroffen und nicht alles.
Dafür gibt es ne Lösung: Liefere einfach ein XSD mit, und deine Kunden können die XML Datei mit nem XML Editor validieren.
Es gibt sogar XML Editoren mit Codevervollständigung.

Würde mir keine so großen Gedanken über zu verwendende XML API machen. Eine XML Datei mit 50 MB stellt für einen modernen
Computer keine Herausforderung dar.
Das Problem ist, soweit ich das richtig verstanden habe, ja nicht die Datenmenge, sondern die vielen verschiedenen Dateien.
Zum einen hast du den Overhead durch 30000 Win 32 API Calls für das Öffnen der Dateien, anstatt nur einmal.
Dann kommt noch dazu, dass eine einzelne, zusammenhängende Datei schneller gelesen werden kann,
als 30000 einzelne die verstreut über die Platte liegen. Gilt natürlich insbesondere für mechanische Festplatten, da hier
die Zugriffszeit besonders hoch ist.
Eine SQlite Datenbank wäre natürlich auch eine Option,
allerdings lassen sich hierarchische Daten wie in einer TreeView nicht so elegent abbilden wie mit XML.
 
Zurück
Oben