Qu'est-ce qu'un bon modèle de conception pour générer un fichier Excel (xlsx) dans le code?


12

Voir ma mise à jour en bas pour en savoir plus.


J'ai parfois des projets où je dois sortir des données sous forme de fichier Excel (format xlsx). Le processus est généralement:

  1. L'utilisateur clique sur certains boutons de mon application

  2. Mon code exécute une requête DB et traite les résultats d'une manière ou d'une autre

  3. Mon code génère un fichier * .xlsx à l'aide des bibliothèques d'interopérabilité Excel com ou d'une bibliothèque tierce (par exemple, Aspose.Cells)

Je peux facilement trouver des exemples de code sur la façon de le faire en ligne, mais je cherche un moyen plus robuste de le faire. J'aimerais que mon code suive certains principes de conception pour garantir que mon code soit maintenable et facilement compréhensible.


Voici à quoi ressemblait ma tentative initiale de générer un fichier xlsx:

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);

Avantages: Pas grand-chose. Cela fonctionne, donc c'est bien.

Les inconvénients:

  • Les références de cellule sont codées en dur, j'ai donc des nombres magiques éparpillés dans mon code.
  • Il est difficile d'ajouter ou de supprimer des colonnes et des lignes sans mettre à jour de nombreuses références de cellule.
  • J'ai besoin d'apprendre une bibliothèque tierce. Certaines bibliothèques sont utilisées comme d'autres bibliothèques, mais il peut toujours y avoir des problèmes. J'ai eu un problème où les bibliothèques COM Interop utilisent le référencement de cellule basé sur 1 alors que Aspose.Cells utilise le référencement de cellule basé sur 0.

Voici une solution qui résout certains des inconvénients que j'ai énumérés ci-dessus. Je voulais traiter une table de données comme son propre objet qui peut être déplacé et modifié sans creuser dans la manipulation des cellules et perturber les autres références de cellules. Voici un 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);

Dans le cadre de cette solution, j'aurai un objet BlockEngine qui prend un conteneur de blocs et effectue les manipulations de cellule requises pour sortir les données sous forme de fichier * .xlsx. Un formatage peut être associé à un objet Block.

Avantages:

  • Cela supprime la plupart des nombres magiques que possédait mon code initial.
  • Cela cache beaucoup de code de manipulation de cellules, bien que la manipulation de cellules soit toujours requise dans l'objet BlockEngine que j'ai mentionné.
  • Il est beaucoup plus facile d'ajouter et de supprimer des lignes sans affecter les autres parties de la feuille de calcul.

Les inconvénients:

  • Il est toujours difficile d'ajouter ou de supprimer des colonnes. Si je voulais échanger la position des colonnes deux et trois, je devrais échanger directement le contenu des cellules. Dans ce cas, ce serait huit modifications, et donc huit occasions de faire une erreur.
    • Si j'ai un formatage en place pour ces deux colonnes, je dois également le mettre à jour.
  • Cette solution ne prend pas en charge le placement de bloc horizontal; Je ne peux placer qu'un bloc en dessous d'un autre. Bien sûr, j'aurais pu tableRight.PutToRightOf(tableLeft), mais cela causerait des problèmes si tableRight et tableLeft avaient un nombre différent de lignes. Pour placer des tables, le moteur doit être conscient de toutes les autres tables. Cela me semble inutilement compliqué.
  • J'ai encore besoin d'apprendre le code tiers, bien qu'à travers une couche d'abstraction via des objets Block et un BlockEngine, le code sera moins étroitement couplé à la bibliothèque tierce que ma tentative initiale. Si je voulais prendre en charge de nombreuses options de formatage de manière lâche, je devrais probablement écrire beaucoup de code; mon BlockEngine serait un énorme gâchis.

Voici une solution qui prend un itinéraire différent. Voici le processus:

  1. Je prends mes données de rapport et génère un fichier xml dans un format que je choisis.

  2. J'utilise ensuite une transformation xsl pour convertir le fichier xml en un fichier de feuille de calcul XML Excel 2003.

  3. De là, je convertis simplement la feuille de calcul xml en un fichier xlsx à l'aide d'une bibliothèque tierce.

J'ai trouvé cette page qui décrit un processus similaire et inclut des exemples de code.

Avantages:

  • Cette solution ne nécessite presque aucune manipulation cellulaire. Vous utilisez plutôt xsl / xpath pour effectuer vos manipulations. Afin d'échanger deux colonnes dans un tableau, vous déplacez toutes les colonnes du fichier xsl contrairement à mes autres solutions qui nécessiteraient un échange de cellule.
  • Bien que vous ayez toujours besoin d'une bibliothèque tierce qui peut convertir une feuille de calcul XML Excel 2003 en un fichier xlsx, c'est à peu près tout ce dont vous aurez besoin pour la bibliothèque. La quantité de code dont vous avez besoin pour écrire et qui serait appelée dans la bibliothèque tierce est minuscule.
  • Je pense que cette solution est la plus facile à comprendre et nécessite le moins de code.
    • Le code qui crée les données dans mon propre format xml sera simple.
    • Le fichier xsl sera compliqué uniquement parce que la feuille de calcul XML Excel 2003 est compliquée. Cependant, il est facile de vérifier la sortie du fichier xsl: ouvrez simplement la sortie dans Excel et recherchez les messages d'erreur.
    • Il est facile de générer des exemples de fichiers de feuille de calcul XML Excel 2003: il suffit de créer une feuille de calcul qui ressemble à votre fichier xlsx souhaité, puis de l'enregistrer en tant que feuille de calcul XML Excel 2003.

Les inconvénients:

  • Les feuilles de calcul XML Excel 2003 ne prennent pas en charge certaines fonctionnalités. Vous ne pouvez pas ajuster automatiquement la largeur des colonnes par exemple. Vous ne pouvez pas inclure d'images dans les en-têtes ou les pieds de page. Si vous allez exporter le fichier xlsx résultant au format pdf, vous ne pouvez pas définir de signets pdf. (J'ai piraté un correctif pour cela en utilisant les commentaires des cellules.). Vous devez le faire en utilisant votre bibliothèque tierce.
  • Nécessite une bibliothèque qui prend en charge les feuilles de calcul XML Excel 2003.
  • Utilise un format de fichier MS Office vieux de 11 ans.

Remarque: je me rends compte que les fichiers xlsx sont en fait des fichiers zip contenant des fichiers xml, mais le formatage xml semble trop compliqué pour mes besoins.


Enfin, j'ai cherché des solutions impliquant SSRS, mais cela semble trop gonflé pour mes besoins.


Revenons à ma question initiale, quel est un bon modèle de conception pour générer des fichiers Excel en code?. Je peux penser à quelques solutions, mais aucune ne semble idéale. Chacun a ses inconvénients.


Mise à jour: J'ai donc essayé à la fois ma solution BlockEngine et ma solution de feuille de calcul XML pour générer des fichiers XLSX similaires. Voici mes opinions à leur sujet:

  • La solution BlockEngine:

    • Cela nécessite simplement trop de code compte tenu des alternatives.
    • J'ai trouvé trop facile d'écraser un bloc avec un autre si j'avais un mauvais décalage.
    • J'ai initialement indiqué que le formatage pouvait être attaché au niveau du bloc. J'ai trouvé que ce n'était pas beaucoup mieux que de faire le formatage séparément du contenu du bloc. Je ne vois pas de bonne façon de combiner le contenu et la mise en forme. Je ne peux pas non plus trouver un bon moyen de les séparer. C'est juste un gâchis.
  • La solution de feuille de calcul XML:

    • Je vais avec cette solution pour l'instant.
    • Il convient de répéter que cette solution nécessite beaucoup moins de code. Je remplace efficacement le BlockEngine par Excel lui-même. J'ai toujours besoin d'un hack pour des fonctionnalités telles que les signets et les sauts de page.
    • Le format de feuille de calcul XML est capricieux, mais il est facile d'apporter une petite modification et de comparer les résultats à un fichier existant dans votre programme Diff préféré. Et une fois que vous avez compris une idiosyncrasie, vous pouvez la mettre en place et l'oublier à partir de là.
    • Je suis toujours préoccupé par le fait que cette solution repose sur un ancien format de fichier Excel.
    • Le fichier XSLT que j'ai créé est facile à utiliser. La gestion du formatage est beaucoup plus simple ici qu'avec la solution BlockEngine.

Réponses:


7

Si vous voulez vraiment quelque chose qui fonctionne bien pour vous, alors je vous suggère de vous habituer à l'idée de "inutilement complexe" ... c'est la nature du traitement des formats de fichiers Microsoft Office.

J'aime (en quelque sorte) votre idée de "blocs" ... Je ferais des objets blocs sous-classés, comme Tableau, avec des colonnes et des lignes indépendantes de la notion de cellules. Utilisez ensuite votre moteur de bloc pour les convertir en fichiers XSLS.

J'ai utilisé le SDK OpenXML avec succès dans le passé, mais n'essayez pas de lire la documentation et de recommencer à zéro. À la place, créez une copie exacte dans Excel de ce que vous voulez, enregistrez-la et inspectez-la à l'aide de l'outil Document Reflector fourni. Il vous donnera le code C # dont vous avez besoin pour créer le document, que vous pourrez ensuite apprendre et modifier.


Les documents Office ne sont PAS "inutilement complexes" - ils font ou permettent une vaste gamme d'opérations, de formatage, de fonctionnalités, etc.
warren

5
Je ne prétends pas que les formats de fichiers eux-mêmes sont inutilement complexes autant que je fais valoir que travailler avec eux l'est. L'utilisation du SDK OpenXML, par exemple, nécessite que vous connaissiez l'ordre magique dans lequel ajouter des éléments ... l'ajout d'une disposition de diapositive à une présentation, par exemple, ne fonctionne pas. Vous devez d'abord l'ajouter à la diapositive, puis à la présentation. Pourquoi? Parce que Microsoft a codé les bibliothèques de cette façon. Il y a aussi beaucoup de références circulaires étranges à gérer. Je comprends que le format a besoin de complexité, mais travailler avec lui ne devrait pas être si pénible.
mgw854 du

3

Voici une solution que j'ai souvent utilisée dans le passé:

  • créer un document Excel standard (généralement au format xlsx) comme modèle, contenant tous les en-têtes de colonne, y compris leur titre et une mise en forme par défaut pour les colonnes et peut-être une mise en forme pour les cellules de titre.

  • intégrer ce modèle dans les ressources de votre programme. Au moment de l'exécution, la première étape consiste à extraire le modèle en tant que nouveau fichier et à le placer dans le dossier de destination

  • utilisez Interop ou une bibliothèque tierce pour remplir les données dans le xlsx nouvellement créé. Ne faites pas référence aux numéros de colonne codés en dur, utilisez plutôt des métadonnées (par exemple les en-têtes de colonne) pour identifier les colonnes correctes.

Avantages:

  • quelque chose comme votre approche Block fonctionne désormais mieux. Par exemple, l'échange de colonnes: pas besoin de changer quoi que ce soit dans votre code de bloc, car les bonnes colonnes sont identifiées par leurs en-têtes

  • tant que vos colonnes ont une mise en forme unique, la plupart de la mise en forme peut être effectuée directement dans Excel, en manipulant votre modèle. Cela vous donne une sensation WYSIWYG, ainsi que la liberté d'utiliser n'importe quelle option de formatage disponible dans Excel sans avoir besoin d'écrire du code pour cela

Les inconvénients:

  • vous devez toujours utiliser une bibliothèque tierce ou Interop. Ai-je mentionné qu'Interop est lent?

  • lorsque les en-têtes de colonne changent dans votre modèle, vous devez également adapter votre code (mais cela peut facilement être détecté en ayant une routine de validation qui signale s'il manque des colonnes attendues)

  • lorsque vous avez besoin d'un formatage dynamique de différentes cellules dans la même colonne, vous devez toujours gérer cela dans le code

De manière générale, quelle que soit l'approche que vous choisissez: elle présente l'avantage de séparer la mise en page du contenu et d'utiliser des solutions déclaratives.


0

Il y a deux choses à considérer:

  • Complexité de la création d'un fichier dans un format donné
  • Sensibilité du code à la rupture lorsque la structure du contenu du fichier doit changer.

Concernant le premier:

Si les feuilles de calcul que vous devez générer ne contiennent pas de mise en forme ou de formules , alors il est assez simple de générer un fichier CSV ou délimité par des tabulations au lieu d'un véritable XLSX. Excel ouvre ces fichiers, souvent par défaut sur de nombreux PC. Cela ne vous aidera pas à coder en dur autour des colonnes et des lignes, mais cela vous épargnera le travail supplémentaire de manipulation du modèle d'objet Excel.

Si vous avez besoin de mise en forme ou de formules, travailler avec le modèle d'objet Excel est une solution raisonnable, surtout si vous créez une feuille de calcul qui n'est pas trop "codée en dur". En d'autres termes, si votre feuille de calcul utilise les formules relatives et les noms de plage de manière appropriée, cela peut aller de pair avec un codage moins strict des nombres magiques.

Concernant le second:

Vous pouvez travailler cellule par cellule avec des références de ligne et de colonne codées en dur, ou vous pouvez travailler avec des tableaux / collections List et des forboucles pour généraliser la population de cellules.


Je ne savais pas clairement dans ma question initiale que je voulais contrôler les options de formatage et d'impression et autres dans ma solution. Concernant le deuxième point, je pense que vous faites référence à ce que j'ai décrit dans ma BlockEnginesolution. Je pourrais en prendre un IList<IBusinessObject>et cracher un Blockobjet. Les avantages et les inconvénients seraient toujours les mêmes.
user2023861
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.