Qual é um bom padrão de design para gerar um arquivo Excel (xlsx) no código?

12

Veja minha atualização na parte inferior para mais.


Ocasionalmente, tenho projetos nos quais tenho que gerar alguns dados como um arquivo do Excel (formato xlsx). O processo é geralmente:

  1. O usuário clica em alguns botões no meu aplicativo

  2. Meu código executa uma consulta ao banco de dados e processa os resultados de alguma forma

  3. Meu código gera um arquivo * .xlsx usando as bibliotecas de interoperabilidade do Excel com ou alguma biblioteca de terceiros (por exemplo, Aspose.Cells)

Posso encontrar facilmente exemplos de código de como fazer isso online, mas estou procurando uma maneira mais robusta de fazer isso. Gostaria que meu código seguisse alguns princípios de design para garantir que meu código fosse mantenível e facilmente compreensível.


Aqui está a aparência da minha tentativa inicial de gerar um arquivo 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);

Prós: Não muito. Funciona, então isso é bom.

Contras:

  • As referências às células são codificadas, então eu tenho números mágicos espalhados por todo o meu código.
  • É difícil adicionar ou remover colunas e linhas sem atualizar muitas referências de célula.
  • Eu preciso aprender alguma biblioteca de terceiros. Algumas bibliotecas são usadas como outras bibliotecas, mas ainda pode haver problemas. Eu tive um problema em que as bibliotecas de interoperabilidade com usam referência de célula com base em 1, enquanto Aspose.Cells usa referência de célula com base em 0.

Aqui está uma solução que aborda alguns dos contras listados acima. Eu queria tratar uma tabela de dados como seu próprio objeto que pode ser movido e alterado sem precisar manipular a célula e perturbar outras referências de célula. Aqui está algum 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 desta solução, terei algum objeto BlockEngine que pega um contêiner de Blocks e executa as manipulações de célula necessárias para gerar os dados como um arquivo * .xlsx. Um objeto Bloco pode ter uma formatação anexada a ele.

Prós:

  • Isso remove a maioria dos números mágicos que meu código inicial tinha.
  • Isso oculta muito código de manipulação de células, embora a manipulação de células ainda seja necessária no objeto BlockEngine que eu mencionei.
  • É muito mais fácil adicionar e remover linhas sem afetar outras partes da planilha.

Contras:

  • Ainda é difícil adicionar ou remover colunas. Se eu quisesse trocar a posição das colunas dois e três, teria que trocar diretamente o conteúdo da célula. Nesse caso, seriam oito edições e, portanto, oito oportunidades para cometer um erro.
    • Se eu tiver alguma formatação em vigor para essas duas colunas, também preciso atualizá-la.
  • Esta solução não suporta a colocação de blocos horizontais; Só posso colocar um bloco abaixo do outro. Claro que eu poderia ter tableRight.PutToRightOf(tableLeft), mas isso causaria problemas se tableRight e tableLeft tivessem números diferentes de linhas. Para colocar tabelas, o mecanismo precisaria estar ciente de todas as outras tabelas. Isso parece desnecessariamente complicado para mim.
  • Ainda preciso aprender o código de terceiros, embora, por meio de uma camada de abstração via objetos Block e um BlockEngine, o código seja menos fortemente acoplado à biblioteca de terceiros do que a minha tentativa inicial. Se eu quisesse oferecer suporte a muitas opções de formatação diferentes de uma maneira pouco acoplada, provavelmente teria que escrever muito código; meu BlockEngine seria uma enorme bagunça.

Aqui está uma solução que segue uma rota diferente. Aqui está o processo:

  1. Pego meus dados de relatório e giro um arquivo xml em algum formato que eu escolher.

  2. Em seguida, uso uma transformação xsl para converter o arquivo xml em um arquivo de planilha XML do Excel 2003.

  3. A partir daí, basta converter a planilha xml em um arquivo xlsx usando uma biblioteca de terceiros.

Encontrei esta página que descreve um processo semelhante e inclui exemplos de código.

Prós:

  • Esta solução quase não requer manipulação celular. Você usa xsl / xpath para fazer suas manipulações. Para trocar duas colunas em uma tabela, você move as colunas inteiras no arquivo xsl, diferente das minhas outras soluções, que exigiriam troca de células.
  • Embora você ainda precise de uma biblioteca de terceiros que possa converter uma planilha XML do Excel 2003 em um arquivo xlsx, é sobre isso que você precisará da biblioteca. A quantidade de código que você precisa escrever que chamaria na biblioteca de terceiros é pequena.
  • Penso que esta solução é a mais fácil de entender e requer a menor quantidade de código.
    • O código que cria os dados no meu próprio formato xml será simples.
    • O arquivo xsl será complicado apenas porque a planilha XML do Excel 2003 é complicada. No entanto, é fácil verificar a saída do arquivo xsl: basta abrir a saída no Excel e verificar se há mensagens de erro.
    • É fácil gerar arquivos de amostra da planilha XML do Excel 2003: basta criar uma planilha parecida com o arquivo xlsx desejado e salve-a como uma planilha XML do Excel 2003.

Contras:

  • As planilhas XML do Excel 2003 não oferecem suporte a determinados recursos. Você não pode ajustar automaticamente as larguras das colunas, por exemplo. Você não pode incluir imagens em cabeçalhos ou rodapés. Se você deseja exportar o arquivo xlsx resultante para pdf, não é possível definir indicadores em pdf. (Eu hackeei uma correção para isso usando comentários de célula.). Você precisa fazer isso usando sua biblioteca de terceiros.
  • Requer uma biblioteca que ofereça suporte a planilhas XML do Excel 2003.
  • Usa um formato de arquivo do MS Office com 11 anos de idade.

Nota: Eu sei que os arquivos xlsx são na verdade arquivos zip que contêm arquivos xml, mas a formatação xml parece muito complicada para meus propósitos.


Finalmente, examinei as soluções que envolvem o SSRS, mas parece muito inchado para meus propósitos.


Voltando à minha pergunta inicial, qual é um bom padrão de design para gerar arquivos do Excel no código ?. Posso pensar em algumas soluções, mas nenhuma parece se destacar como ideal. Cada um tem desvantagens.


Atualização: tentei tanto a minha solução BlockEngine quanto a minha planilha XML para gerar arquivos XLSX semelhantes. Aqui estão minhas opiniões sobre eles:

  • A solução BlockEngine:

    • Isso simplesmente exige muito código, considerando as alternativas.
    • Achei muito fácil substituir um bloco por outro se eu tivesse um deslocamento errado.
    • Inicialmente, afirmei que a formatação poderia ser anexada no nível do bloco. Achei que isso não era muito melhor do que fazer a formatação separadamente do conteúdo do bloco. Não consigo pensar em uma boa maneira de combinar o conteúdo e a formatação. Também não consigo encontrar uma boa maneira de mantê-los separados. É apenas uma bagunça.
  • A solução da planilha XML:

    • Eu estou indo com esta solução por enquanto.
    • Vale a pena repetir que esta solução requer muito menos código. Estou efetivamente substituindo o BlockEngine pelo próprio Excel. Eu ainda preciso de um hack para recursos como favoritos e quebras de página.
    • O formato da planilha XML é minucioso, mas é fácil fazer uma pequena alteração e comparar os resultados com um arquivo existente no seu programa Diff favorito. E depois de descobrir alguma idiossincrasia, você pode colocá-la no lugar e esquecê-la a partir daí.
    • Ainda estou preocupado que esta solução dependa de um formato de arquivo antigo do Excel.
    • O arquivo XSLT que criei é fácil de trabalhar. Lidar com a formatação é muito mais simples aqui do que com a solução BlockEngine.
user2023861
fonte

Respostas:

7

Se você realmente deseja algo que funcione bem para você, sugiro que você se acostume com a idéia de "desnecessariamente complexo" ... essa é a natureza de lidar com os formatos de arquivo do Microsoft Office.

Eu (meio que) gostei da sua idéia de "blocos" ... Eu faria objetos de bloco subclassificados, como Tabela, com Colunas e Linhas independentes da noção de células. Em seguida, use o mecanismo de bloqueio para convertê-los em arquivos XSLS.

Eu usei o OpenXML SDK com sucesso no passado, mas não tente ler a documentação e começar do zero. Em vez disso, crie uma cópia exata no Excel do que você deseja, salve-a e inspecione-a usando a ferramenta Refletor de documentos fornecida. Ele fornecerá o código C # necessário para criar o documento, que você poderá aprender e modificar.

mgw854
fonte
Documentos do Office são NÃO "desnecessariamente complexa" - que estão fazendo ou permitindo uma enorme gama de operações, formatação, funcionalidade, etc
Warren
5
Não estou argumentando que os próprios formatos de arquivo são desnecessariamente complexos, tanto quanto eu estou argumentando que trabalhar com eles é. O uso do OpenXML SDK, por exemplo, exige que você saiba a ordem mágica na qual adicionar elementos ... adicionar um layout de slide a uma apresentação, por exemplo, não funciona. Você deve adicioná-lo ao slide primeiro e depois à apresentação. Por quê? Porque a Microsoft codificou as bibliotecas dessa maneira. Também há muitas referências circulares estranhas para gerenciar. Entendo que o formato precisa de complexidade, mas trabalhar com ele não deve ser tão doloroso.
mgw854
3

Aqui está uma solução que eu usei frequentemente no passado:

  • crie um documento regular do Excel (normalmente no formato xlsx) como modelo, contendo todos os cabeçalhos das colunas, incluindo o título e uma formatação padrão para as colunas e talvez formatação para as células de título.

  • incorpore esse modelo aos recursos do seu programa. Em tempo de execução, o primeiro passo é extrair o modelo como um novo arquivo e colocá-lo na pasta de destino

  • use a Interop ou uma biblioteca de terceiros para preencher os dados no xlsx recém-criado. Não faça referência a números de colunas codificados, use alguns metadados (por exemplo, os cabeçalhos das colunas) para identificar as colunas corretas.

Prós:

  • algo como sua abordagem de bloco agora funciona melhor. Por exemplo, troca de coluna: não há necessidade de alterar nada no seu código de bloco, pois as colunas corretas são identificadas por seus cabeçalhos

  • contanto que suas colunas tenham uma formatação exclusiva, a maior parte da formatação pode ser feita diretamente no Excel, manipulando seu modelo. Isso dá a você uma sensação WYSIWYG, juntamente com a liberdade de usar qualquer opção de formatação disponível no Excel, sem a necessidade de escrever código para ele.

Contras:

  • você ainda precisa usar uma biblioteca ou interoperabilidade de terceiros. Eu mencionei que a Interop é lenta?

  • quando os cabeçalhos das colunas mudam no seu modelo, você também precisa adaptar seu código (mas isso pode ser facilmente detectado com uma rotina de validação que indica se há colunas esperadas)

  • quando você precisar de formatação dinâmica de células diferentes na mesma coluna, ainda precisará lidar com isso no código

Como dica geral, qualquer que seja a abordagem que você escolher: ela tem vantagens em separar o layout do conteúdo e fazer uso de soluções declarativas.

Doc Brown
fonte
0

Há duas coisas a considerar:

  • Complexidade de criar um arquivo em um determinado formato
  • Suscetibilidade do código a quebra quando a estrutura do conteúdo do arquivo precisa mudar.

Em relação ao primeiro:

Se as planilhas que você precisa gerar não contêm nenhuma formatação ou fórmula , é bastante simples gerar um arquivo CSV ou delimitado por tabulações em vez de um XLSX real. O Excel abre esses arquivos, geralmente por padrão em muitos computadores. Isso não ajudará você a codificar em torno de colunas e linhas, mas economizará o trabalho extra de manipular o modelo de objeto do Excel.

Se você precisar de formatação ou fórmulas, trabalhar com o modelo de objeto do Excel é um caminho razoável, especialmente se você criar uma planilha que não seja muito "codificada". Em outras palavras, se sua planilha usa fórmulas relativas e nomes de intervalo adequadamente, ela pode ser combinada com uma codificação menos rígida dos números mágicos.

Em relação ao segundo:

Você pode trabalhar célula por célula com referências de linha e coluna codificadas, ou trabalhar com matrizes / coleções de listas e forloops para generalizar a população de células.

Joel Brown
fonte
Não estava claro na minha pergunta original que desejo controlar as opções de formatação e impressão e outras coisas na minha solução. Com relação ao segundo ponto, acho que o que você está se referindo é o que descrevi na minha BlockEnginesolução. Eu poderia pegar IList<IBusinessObject>e cuspir um Blockobjeto. Os prós e contras ainda seriam os mesmos.
user2023861