¿Cuál es un buen patrón de diseño para generar un archivo Excel (xlsx) en código?

12

Vea mi Actualización en la parte inferior para más información.


Ocasionalmente tengo proyectos en los que tengo que generar algunos datos como un archivo de Excel (formato xlsx). El proceso suele ser:

  1. El usuario hace clic en algunos botones en mi aplicación

  2. Mi código ejecuta una consulta DB y procesa los resultados de alguna manera

  3. Mi código genera un archivo * .xlsx utilizando las bibliotecas de interoperabilidad com de Excel o alguna biblioteca de terceros (por ejemplo, Aspose.Cells)

Puedo encontrar fácilmente ejemplos de código sobre cómo hacer esto en línea, pero estoy buscando una forma más sólida de hacerlo. Me gustaría que mi código siguiera algunos principios de diseño para asegurar que mi código sea mantenible y fácilmente comprensible.


Así es como se veía mi intento inicial de generar un archivo 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);

Pros: no mucho. Funciona, así que está bien.

Contras:

  • Las referencias de las celdas están codificadas, por lo que tengo números mágicos en todo mi código.
  • Es difícil agregar o eliminar columnas y filas sin actualizar muchas referencias de celda.
  • Necesito aprender alguna biblioteca de terceros. Algunas bibliotecas se usan como otras bibliotecas, pero aún puede haber problemas. Tuve un problema en el que las bibliotecas de interoperabilidad com usan referencias a celdas basadas en 1, mientras que Aspose.Cells usa referencias a celdas basadas en 0.

Aquí hay una solución que aborda algunos de los inconvenientes que enumeré anteriormente. Quería tratar una tabla de datos como su propio objeto que se puede mover y cambiar sin excavar en la manipulación de la celda y alterar otras referencias de la celda. Aquí hay un pseudocódigo:

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

Como parte de esta solución, tendré algún objeto BlockEngine que toma un contenedor de Bloques y realiza las manipulaciones de celdas necesarias para generar los datos como un archivo * .xlsx. Un objeto Block puede tener un formato adjunto.

Pros:

  • Esto elimina la mayoría de los números mágicos que tenía mi código inicial.
  • Esto oculta mucho código de manipulación de celdas, aunque la manipulación de celdas aún se requiere en el objeto BlockEngine que mencioné.
  • Es mucho más fácil agregar y eliminar filas sin afectar otras partes de la hoja de cálculo.

Contras:

  • Todavía es difícil agregar o eliminar columnas. Si quisiera cambiar la posición de las columnas dos y tres, tendría que cambiar directamente el contenido de la celda. En este caso, serían ocho ediciones y, por lo tanto, ocho oportunidades para cometer un error.
    • Si tengo algún formato para esas dos columnas, también tengo que actualizarlo.
  • Esta solución no admite la colocación de bloques horizontales; Solo puedo colocar un bloque debajo de otro. Claro que podría tableRight.PutToRightOf(tableLeft), pero eso causaría problemas si tableRight y tableLeft tuvieran diferentes números de filas. Para colocar tablas, el motor tendría que ser consciente de todas las demás tablas. Esto me parece innecesariamente complicado.
  • Todavía necesito aprender código de terceros, aunque a través de una capa de abstracción a través de objetos Block y un BlockEngine, el código estará menos estrechamente acoplado a la biblioteca de terceros que mi intento inicial. Si quisiera admitir muchas opciones de formato diferentes de una manera poco acoplada, probablemente tendría que escribir mucho código; mi BlockEngine sería un gran desastre.

Aquí hay una solución que toma una ruta diferente. Aquí está el proceso:

  1. Tomo los datos de mi informe y genero un archivo xml en algún formato que elijo.

  2. Luego uso una transformación xsl para convertir el archivo xml en un archivo de hoja de cálculo XML de Excel 2003.

  3. Desde allí, simplemente convierto la hoja de cálculo xml en un archivo xlsx usando una biblioteca de terceros.

Encontré esta página que describe un proceso similar e incluye ejemplos de código.

Pros:

  • Esta solución casi no requiere manipulación celular. En su lugar, utiliza xsl / xpath para hacer sus manipulaciones. Para intercambiar dos columnas en una tabla, mueve las columnas completas en el archivo xsl a diferencia de mis otras soluciones que requerirían el intercambio de celdas.
  • Si bien aún necesita una biblioteca de terceros que pueda convertir una hoja de cálculo XML de Excel 2003 en un archivo xlsx, eso es todo lo que necesitará para la biblioteca. La cantidad de código que necesita escribir que llamaría a la biblioteca de terceros es pequeña.
  • Creo que esta solución es la más fácil de entender y requiere la menor cantidad de código.
    • El código que crea los datos en mi propio formato xml será simple.
    • El archivo xsl será complicado solo porque la hoja de cálculo XML de Excel 2003 es complicada. Sin embargo, es fácil verificar la salida del archivo xsl: solo abra la salida en Excel y verifique si hay mensajes de error.
    • Es fácil generar archivos de muestra de hoja de cálculo XML de Excel 2003: simplemente cree una hoja de cálculo que se parezca al archivo xlsx deseado y luego guárdelo como una hoja de cálculo XML de Excel 2003.

Contras:

  • Las hojas de cálculo XML de Excel 2003 no admiten ciertas características. No puede ajustar automáticamente los anchos de columna, por ejemplo. No puede incluir imágenes en encabezados o pies de página. Si va a exportar el archivo xlsx resultante a pdf, no puede establecer marcadores de pdf. (Pirateé una solución para esto usando comentarios de celda). Tienes que hacer esto usando tu biblioteca de terceros.
  • Requiere una biblioteca que admita hojas de cálculo XML de Excel 2003.
  • Utiliza un formato de archivo de MS Office de 11 años.

Nota: Me doy cuenta de que los archivos xlsx son en realidad archivos zip que contienen archivos xml, pero el formato xml parece demasiado complicado para mis propósitos.


Finalmente, he buscado soluciones que involucren SSRS, pero parece demasiado hinchado para mis propósitos.


Volviendo a mi pregunta inicial, ¿cuál es un buen patrón de diseño para generar archivos de Excel en código? Se me ocurren algunas soluciones, pero ninguna parece sobresalir como ideal. Cada uno tiene inconvenientes.


Actualización: así que probé tanto mi solución BlockEngine como mi solución de hoja de cálculo XML para generar archivos XLSX similares. Aquí están mis opiniones sobre ellos:

  • La solución BlockEngine:

    • Esto simplemente requiere demasiado código considerando las alternativas.
    • Me resultó demasiado fácil sobrescribir un bloque con otro si tenía un desplazamiento incorrecto.
    • Originalmente dije que el formato podría adjuntarse a nivel de bloque. Descubrí que esto no es mucho mejor que formatear por separado del contenido del bloque. No se me ocurre una buena forma de combinar el contenido y el formato. Tampoco puedo encontrar una buena manera de mantenerlos separados. Es solo un desastre.
  • La solución de hoja de cálculo XML:

    • Voy con esta solución por ahora.
    • Vale la pena repetir que esta solución requiere mucho menos código. Estoy reemplazando efectivamente el BlockEngine con Excel en sí. Todavía necesito un truco para características como marcadores y saltos de página.
    • El formato de hoja de cálculo XML es complicado, pero es fácil hacer un pequeño cambio y comparar los resultados con un archivo existente en su programa Diff favorito. Y una vez que descubras alguna idiosincrasia, puedes ponerla en su lugar y olvidarte de ella desde allí.
    • Todavía me preocupa que esta solución se base en un formato de archivo Excel anterior.
    • El archivo XSLT que creé es fácil de trabajar. Tratar con el formateo es mucho más simple aquí que con la solución BlockEngine.
usuario2023861
fuente

Respuestas:

7

Si realmente desea algo que funcione bien para usted, le sugiero que se acostumbre a la idea de "innecesariamente complejo" ... esa es la naturaleza de tratar con los formatos de archivo de Microsoft Office.

Me gusta su idea de "bloques" ... Haría que los objetos de bloques subclasificados, como Tabla, con columnas y filas sean independientes de la noción de celdas. Luego use su motor de bloques para convertirlos a archivos XSLS.

He usado el SDK OpenXML con éxito en el pasado, pero no intente leer la documentación y comenzar desde cero. En su lugar, cree una copia exacta en Excel de lo que desea, guárdelo e inspecciónelo utilizando la herramienta Document Reflector proporcionada. Le dará el código C # que necesita para crear el documento, del cual puede aprender y modificar.

mgw854
fuente
Los documentos de Office NO son "innecesariamente complejos" - están haciendo o permitiendo una enorme variedad de operaciones, formateo, funcionalidad, etc.
warren
55
No estoy argumentando que los formatos de archivo en sí son innecesariamente complejos tanto como estoy argumentando que trabajar con ellos lo es. El uso del OpenXML SDK, por ejemplo, requiere que sepas el orden mágico en el que agregar elementos ... agregar un diseño de diapositiva a una presentación, por ejemplo, no funciona. Primero debe agregarlo a la diapositiva y luego a la presentación. ¿Por qué? Porque Microsoft codificó las bibliotecas de esa manera. También hay muchas referencias circulares extrañas para administrar. Entiendo que el formato necesita complejidad, pero trabajar con él no debería ser tan doloroso.
mgw854
3

Aquí hay una solución que he usado a menudo en el pasado:

  • cree un documento de Excel normal (generalmente en formato xlsx) como plantilla, que contenga todos los encabezados de columna, incluido su título y un formato predeterminado para las columnas y quizás el formato para las celdas de título.

  • incrustar esa plantilla en los recursos de su programa. En tiempo de ejecución, el primer paso es extraer la plantilla como un archivo nuevo y colocarla en la carpeta de destino

  • use Interop o una biblioteca de terceros para completar los datos en el xlsx recién creado. No haga referencia a números de columna codificados, en su lugar use algunos metadatos (por ejemplo, los encabezados de columna) para identificar las columnas correctas.

Pros:

  • algo así como su enfoque de Bloque ahora funciona mejor. Por ejemplo, intercambio de columnas: no es necesario cambiar nada en su código de bloque, ya que las columnas correctas se identifican por sus encabezados

  • siempre que sus columnas tengan un formato único, la mayoría del formato se puede hacer directamente en Excel, manipulando su plantilla. Eso le da una sensación WYSIWYG, junto con la libertad de usar cualquier opción de formato disponible en Excel sin la necesidad de escribir código para ello.

Contras:

  • aún necesita hacer uso de una biblioteca de terceros o Interop. ¿Mencioné que Interop es lento?

  • cuando los encabezados de columna cambian en su plantilla, debe adaptar también su código (pero eso puede detectarse fácilmente al tener una rutina de validación que indica si faltan columnas esperadas)

  • cuando necesita formateo dinámico de diferentes celdas en la misma columna, aún tiene que lidiar con eso en el código

Como pista general, cualquier enfoque que elija: tiene ventajas para separar el diseño del contenido y hacer uso de soluciones declarativas.

Doc Brown
fuente
0

Hay dos cosas a considerar:

  • Complejidad de crear un archivo en un formato dado
  • Susceptibilidad del código a la rotura cuando la estructura del contenido del archivo necesita cambiar.

Sobre el primero:

Si las hojas de cálculo que necesita generar no contienen ningún formato o fórmula , entonces es bastante sencillo generar un archivo CSV o delimitado por tabuladores en lugar de un XLSX real. Excel abre estos archivos, a menudo de manera predeterminada en muchas PC. Esto no lo ayudará con la codificación rígida alrededor de columnas y filas, pero le ahorrará el trabajo adicional de manipular el modelo de objetos de Excel.

Si necesita formato o fórmulas, entonces trabajar con el modelo de objetos de Excel es un camino razonable, especialmente si crea una hoja de cálculo que no está demasiado "codificada". En otras palabras, si su hoja de cálculo usa fórmulas relativas y nombres de rango de manera apropiada, entonces puede ir junto con una codificación menos difícil de los números mágicos.

En cuanto a la segunda:

Puede trabajar celda por celda con referencias de fila y columna codificadas, o puede trabajar con matrices / listas de colecciones y forbucles para generalizar la población de celdas.

Joel Brown
fuente
No estaba claro en mi pregunta original que quiero controlar las opciones de formato e impresión y tal en mi solución. Con respecto al segundo punto, creo que a lo que te refieres es a lo que describí en mi BlockEnginesolución. Podría tomar un IList<IBusinessObject>y escupir un Blockobjeto. Los pros y los contras seguirían siendo los mismos.
user2023861