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:
O usuário clica em alguns botões no meu aplicativo
Meu código executa uma consulta ao banco de dados e processa os resultados de alguma forma
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:
Pego meus dados de relatório e giro um arquivo xml em algum formato que eu escolher.
Em seguida, uso uma transformação xsl para converter o arquivo xml em um arquivo de planilha XML do Excel 2003.
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.