Was ist ein gutes Entwurfsmuster zum Generieren einer Excel-Datei (xlsx) im Code?

12

Weitere Informationen finden Sie in meinem Update unten.


Ich habe gelegentlich Projekte, bei denen ich einige Daten als Excel-Datei (xlsx-Format) ausgeben muss. Der Prozess ist normalerweise:

  1. Der Benutzer klickt auf einige Schaltflächen in meiner Anwendung

  2. Mein Code führt eine DB-Abfrage aus und verarbeitet die Ergebnisse irgendwie

  3. Mein Code generiert eine * .xlsx-Datei, indem er entweder die Excel-COM-Interop-Bibliotheken oder eine Bibliothek eines Drittanbieters (z. B. Aspose.Cells) verwendet.

Ich kann leicht Codebeispiele finden, um dies online zu tun, aber ich suche nach einer stabileren Möglichkeit, dies zu tun. Ich möchte, dass mein Code einigen Entwurfsprinzipien folgt, um sicherzustellen, dass mein Code wartbar und leicht verständlich ist.


So sah mein erster Versuch, eine XLSX-Datei zu generieren, aus:

var wb = new Workbook();
var ws = wb.Worksheets[0];
ws.Cells[0, 0].Value = "Header";
ws.Cells[1, 0].Value = "Row 1";
ws.Cells[2, 0].Value = "Row 2";
ws.Cells[3, 0].Value = "Row 3";
wb.Save(path);

Vorteile: Nicht viel. Es funktioniert, also ist das gut.

Nachteile:

  • Zellreferenzen sind hartcodiert, daher sind in meinem Code magische Zahlen verstreut.
  • Es ist schwierig, Spalten und Zeilen hinzuzufügen oder zu entfernen, ohne viele Zellreferenzen zu aktualisieren.
  • Ich muss eine Fremdbibliothek lernen. Einige Bibliotheken werden wie andere Bibliotheken verwendet, es kann jedoch weiterhin Probleme geben. Ich hatte ein Problem, bei dem die COM-Interop-Bibliotheken eine 1-basierte Zellreferenzierung verwenden, während Aspose.Cells eine 0-basierte Zellreferenzierung verwendet.

Hier ist eine Lösung, die einige der oben aufgeführten Nachteile behebt. Ich wollte eine Datentabelle als ein eigenes Objekt behandeln, das verschoben und geändert werden kann, ohne in die Zellmanipulation einzugreifen und andere Zellreferenzen zu stören. Hier ist ein Pseudocode:

var headers = new Block(new string[] { "Col 1", "Col 2", "Col 3" });
var body = new Block(new string[,]
    {
        { "Row 1", "Row 1", "Row 1" },
        { "Row 2", "Row 2", "Row 2" },
        { "Row 3", "Row 3", "Row 3" }
    });

body.PutBelow(headers);

Als Teil dieser Lösung wird ein BlockEngine-Objekt benötigt, das einen Container mit Blöcken aufnimmt und die erforderlichen Zellmanipulationen ausführt, um die Daten als * .xlsx-Datei auszugeben. An ein Block-Objekt kann eine Formatierung angehängt werden.

Vorteile:

  • Dies entfernt die meisten magischen Zahlen, die mein ursprünglicher Code hatte.
  • Dadurch wird viel Code zur Zellmanipulation ausgeblendet, obwohl im erwähnten BlockEngine-Objekt noch eine Zellmanipulation erforderlich ist.
  • Es ist viel einfacher, Zeilen hinzuzufügen und zu entfernen, ohne andere Teile der Tabelle zu beeinflussen.

Nachteile:

  • Es ist immer noch schwierig, Spalten hinzuzufügen oder zu entfernen. Wenn ich die Position der Spalten zwei und drei tauschen wollte, musste ich den Zelleninhalt direkt tauschen. In diesem Fall wären das acht Bearbeitungen und damit acht Möglichkeiten, einen Fehler zu machen.
    • Wenn für diese beiden Spalten Formatierungen vorhanden sind, muss diese ebenfalls aktualisiert werden.
  • Diese Lösung unterstützt keine horizontale Blockplatzierung. Ich kann nur einen Block unter dem anderen platzieren. Sicher könnte ich haben tableRight.PutToRightOf(tableLeft), aber das würde Probleme verursachen, wenn tableRight und tableLeft unterschiedliche Anzahlen von Zeilen hatten. Um Tabellen zu platzieren, müsste die Engine jede andere Tabelle kennen. Das erscheint mir unnötig kompliziert.
  • Ich muss noch Code von Drittanbietern lernen, obwohl der Code durch eine Abstraktionsebene über Block-Objekte und eine BlockEngine weniger eng an die Bibliothek von Drittanbietern gekoppelt ist als bei meinem ersten Versuch. Wenn ich viele verschiedene Formatierungsoptionen auf lose Weise unterstützen wollte, müsste ich wahrscheinlich viel Code schreiben. Meine BlockEngine wäre ein großes Durcheinander.

Hier ist eine Lösung, die einen anderen Weg einschlägt. Hier ist der Prozess:

  1. Ich nehme meine Berichtsdaten und generiere eine XML-Datei in einem von mir gewählten Format.

  2. Ich verwende dann eine XSL-Transformation, um die XML-Datei in eine Excel 2003-XML-Tabellenkalkulationsdatei zu konvertieren.

  3. Von dort konvertiere ich einfach das XML-Arbeitsblatt in eine XLSX-Datei unter Verwendung einer Drittanbieter-Bibliothek.

Ich habe diese Seite gefunden , die einen ähnlichen Prozess beschreibt und Codebeispiele enthält.

Vorteile:

  • Diese Lösung erfordert fast keine Zellmanipulation. Sie verwenden stattdessen xsl / xpath, um Ihre Manipulationen durchzuführen. Um zwei Spalten in einer Tabelle auszutauschen, verschieben Sie im Gegensatz zu meinen anderen Lösungen, bei denen das Austauschen von Zellen erforderlich wäre, die gesamten Spalten in der xsl-Datei.
  • Sie benötigen zwar noch eine Drittanbieter-Bibliothek, die eine Excel 2003-XML-Kalkulationstabelle in eine XLSX-Datei konvertieren kann, aber das ist ungefähr alles, wofür Sie die Bibliothek benötigen. Die Menge an Code, die Sie schreiben müssen, um in die Bibliothek eines Drittanbieters zu gelangen, ist gering.
  • Ich denke, diese Lösung ist am einfachsten zu verstehen und erfordert die geringste Menge an Code.
    • Der Code, der die Daten in meinem eigenen XML-Format erstellt, ist einfach.
    • Die xsl-Datei ist nur deshalb kompliziert, weil das Excel 2003-XML-Arbeitsblatt kompliziert ist. Es ist jedoch einfach, die Ausgabe der xsl-Datei zu überprüfen: Öffnen Sie einfach die Ausgabe in Excel und suchen Sie nach Fehlermeldungen.
    • Es ist ganz einfach, Excel 2003-XML-Beispieltabellendateien zu generieren: Erstellen Sie einfach eine Tabelle, die Ihrer gewünschten xlsx-Datei ähnelt, und speichern Sie sie dann als Excel 2003-XML-Tabelle.

Nachteile:

  • Excel 2003-XML-Arbeitsblätter unterstützen bestimmte Funktionen nicht. Beispielsweise können Sie Spaltenbreiten nicht automatisch anpassen. Sie können keine Bilder in Kopf- oder Fußzeilen einfügen. Wenn Sie die resultierende xlsx-Datei als PDF exportieren, können Sie keine PDF-Lesezeichen setzen. (Ich habe eine Lösung für dieses Problem mithilfe von Zellkommentaren zusammengestellt.) Sie müssen dies mit Ihrer Drittanbieter-Bibliothek tun.
  • Benötigt eine Bibliothek, die Excel 2003 XML Spreadsheets unterstützt.
  • Verwendet ein 11 Jahre altes MS Office-Dateiformat.

Hinweis: Mir ist klar, dass XLSX-Dateien eigentlich ZIP-Dateien sind, die XML-Dateien enthalten, aber die XML-Formatierung scheint für meine Zwecke zu kompliziert zu sein.


Schließlich habe ich nach Lösungen für SSRS gesucht, die für meine Zwecke jedoch zu aufgebläht erscheinen.


Zurück zu meiner Ausgangsfrage, was ist ein gutes Entwurfsmuster zum Generieren von Excel-Dateien im Code? Ich kann mir ein paar Lösungen vorstellen, aber keine scheint sich als ideal herauszustellen. Jeder hat Nachteile.


Update: Also habe ich sowohl meine BlockEngine-Lösung als auch meine XML-Tabellenkalkulationslösung zum Generieren ähnlicher XLSX-Dateien ausprobiert. Hier sind meine Meinungen von ihnen:

  • Die BlockEngine-Lösung:

    • Dies erfordert einfach zu viel Code in Anbetracht der Alternativen.
    • Ich fand es zu einfach, einen Block mit einem anderen zu überschreiben, wenn ich einen falschen Versatz hatte.
    • Ich habe ursprünglich angegeben, dass die Formatierung auf Blockebene angehängt werden kann. Ich fand das nicht viel besser als das Formatieren getrennt vom Blockinhalt. Ich kann mir keine gute Möglichkeit vorstellen, den Inhalt und die Formatierung zu kombinieren. Ich kann auch keinen guten Weg finden, sie getrennt zu halten. Es ist nur ein Durcheinander.
  • Die XML-Tabellenkalkulationslösung:

    • Ich gehe mit dieser Lösung vorerst.
    • Es ist zu wiederholen, dass diese Lösung viel weniger Code erfordert. Ich ersetze die BlockEngine effektiv durch Excel. Ich brauche immer noch einen Hack für Features wie Lesezeichen und Seitenumbrüche.
    • Das XML-Spreadsheet-Format ist schwierig, aber es ist einfach, kleine Änderungen vorzunehmen und die Ergebnisse mit einer vorhandenen Datei in Ihrem bevorzugten Diff-Programm zu vergleichen. Und sobald Sie eine Eigenart herausgefunden haben, können Sie sie einsetzen und von dort aus vergessen.
    • Ich bin immer noch besorgt, dass diese Lösung auf einem älteren Excel-Dateiformat beruht.
    • Die von mir erstellte XSLT-Datei ist einfach zu bearbeiten. Der Umgang mit Formatierungen ist hier viel einfacher als mit der BlockEngine-Lösung.
user2023861
quelle

Antworten:

7

Wenn Sie wirklich möchten, dass etwas für Sie gut funktioniert, sollten Sie sich an die Vorstellung von "unnötig komplex" gewöhnen. Das liegt in der Natur des Umgangs mit Microsoft Office-Dateiformaten.

Ich (irgendwie) mag Ihre Vorstellung von "Blöcken" ... Ich würde untergeordnete Blockobjekte wie "Tabelle" mit Spalten und Zeilen unabhängig vom Begriff "Zellen" erstellen. Verwenden Sie dann Ihre Block-Engine, um diese in XSLS-Dateien zu konvertieren.

Ich habe das OpenXML SDK in der Vergangenheit erfolgreich verwendet, aber versuche nicht, die Dokumentation zu lesen und von vorne zu beginnen. Erstellen Sie stattdessen in Excel eine exakte Kopie des gewünschten Dokuments, speichern Sie sie und überprüfen Sie sie mit dem bereitgestellten Document Reflector-Tool. Sie erhalten den C # -Code, den Sie zum Erstellen des Dokuments benötigen, aus dem Sie lernen und ändern können.

mgw854
quelle
Office-Dokumente sind NICHT "unnötig komplex" - sie erledigen oder erlauben eine enorme Bandbreite an Operationen, Formatierungen, Funktionen usw.
warren
5
Ich behaupte nicht, dass Dateiformate selbst unnötig komplex sind, so wie ich argumentiere, dass die Arbeit mit ihnen ist. Wenn Sie beispielsweise das OpenXML SDK verwenden, müssen Sie die magische Reihenfolge kennen, in der Elemente hinzugefügt werden müssen. Das Hinzufügen eines Folienlayouts zu einer Präsentation funktioniert beispielsweise nicht. Sie müssen es zuerst der Folie und dann der Präsentation hinzufügen. Warum? Weil Microsoft die Bibliotheken so codiert hat. Es gibt auch viele seltsame Zirkelverweise, die verwaltet werden müssen. Ich verstehe, dass das Format Komplexität erfordert, aber es sollte nicht so schmerzhaft sein, damit zu arbeiten.
mgw854
3

Hier ist eine Lösung, die ich in der Vergangenheit oft verwendet habe:

  • Erstellen Sie ein reguläres Excel-Dokument (in der Regel im XLSX-Format) als Vorlage, das alle Spaltenüberschriften einschließlich des Titels und einer Standardformatierung für die Spalten sowie möglicherweise eine Formatierung für Titelzellen enthält.

  • Betten Sie diese Vorlage in die Ressourcen Ihres Programms ein. Zur Laufzeit besteht der erste Schritt darin, die Vorlage als neue Datei zu extrahieren und im Zielordner abzulegen

  • Verwenden Sie Interop oder eine Drittanbieter-Bibliothek, um die Daten in das neu erstellte XLSX-Format einzufügen. Verweisen Sie nicht auf fest codierte Spaltennummern, sondern verwenden Sie stattdessen einige Metadaten (z. B. die Spaltenüberschriften), um die richtigen Spalten zu identifizieren.

Vorteile:

  • So etwas wie Ihr Block-Ansatz funktioniert jetzt besser. Beispiel: Spaltenaustausch: Der Blockcode muss nicht geändert werden, da die richtigen Spalten anhand ihrer Überschriften identifiziert werden

  • Solange Ihre Spalten eine eindeutige Formatierung haben, können die meisten Formatierungen direkt in Excel vorgenommen werden, indem Sie Ihre Vorlage bearbeiten. Das gibt Ihnen ein WYSIWYG-Gefühl, zusammen mit der Freiheit, alle in Excel verfügbaren Formatierungsoptionen zu verwenden, ohne dafür Code schreiben zu müssen

Nachteile:

  • Sie müssen weiterhin eine Drittanbieter-Bibliothek oder Interop verwenden. Habe ich erwähnt, dass Interop langsam ist?

  • Wenn sich die Spaltenüberschriften in Ihrer Vorlage ändern, müssen Sie auch Ihren Code anpassen (dies kann jedoch leicht durch eine Validierungsroutine erkannt werden, die anzeigt, ob erwartete Spalten fehlen).

  • Wenn Sie die dynamische Formatierung verschiedener Zellen in derselben Spalte benötigen, müssen Sie sich immer noch mit dem Code befassen

Als allgemeiner Hinweis, für welchen Ansatz Sie sich auch entscheiden: Es hat Vorteile, das Layout vom Inhalt zu trennen und deklarative Lösungen zu verwenden.

Doc Brown
quelle
0

Es gibt zwei Dinge zu beachten:

  • Komplexität beim Erstellen einer Datei in einem bestimmten Format
  • Fehleranfälligkeit des Codes, wenn sich die Struktur des Inhalts der Datei ändern muss.

Zum ersten:

Wenn die zu generierenden Tabellenkalkulationen keine Formatierungen oder Formeln enthalten , können Sie ganz einfach eine CSV- oder tabulatorgetrennte Datei anstelle einer tatsächlichen XLSX-Datei generieren. Excel öffnet diese Dateien, häufig standardmäßig auf vielen PCs. Dies hilft Ihnen nicht beim harten Codieren von Spalten und Zeilen, erspart Ihnen jedoch die zusätzliche Arbeit beim Bearbeiten des Excel-Objektmodells.

Wenn Sie Formatierungen oder Formeln benötigen, ist die Arbeit mit dem Excel-Objektmodell ein vernünftiger Weg, insbesondere wenn Sie eine Tabelle erstellen, die selbst nicht zu "fest" codiert ist. Mit anderen Worten, wenn Ihre Tabelle relative Formeln und Bereichsnamen in geeigneter Weise verwendet, kann dies mit einer weniger strengen Kodierung von magischen Zahlen einhergehen.

Zum zweiten:

Sie können Zelle für Zelle mit fest codierten Zeilen- und Spaltenreferenzen arbeiten, oder Sie können mit Arrays / List-Auflistungen und forSchleifen arbeiten, um die Zellpopulation zu verallgemeinern.

Joel Brown
quelle
In meiner ursprünglichen Frage war mir nicht klar, dass ich die Formatierungs- und Druckoptionen und dergleichen in meiner Lösung steuern möchte. In Bezug auf den zweiten Punkt denke ich, dass Sie sich auf das beziehen, was ich in meiner BlockEngineLösung beschrieben habe . Ich könnte IList<IBusinessObject>einen BlockGegenstand nehmen und ausspucken . Das Für und Wider wäre immer noch dasselbe.
user2023861