在代码中生成Excel(xlsx)文件的良好设计模式是什么?


12

有关更多信息,请参见底部的我的更新。


有时,我有一些项目必须将某些数据输出为Excel文件(xlsx格式)。该过程通常是:

  1. 用户单击我的应用程序中的一些按钮

  2. 我的代码运行数据库查询并以某种方式处理结果

  3. 我的代码使用Excel com互操作库或某些第三方库(例如Aspose.Cells)生成* .xlsx文件

我可以轻松地找到有关如何在线执行此操作的代码示例,但是我正在寻找一种更强大的方法来执行此操作。我希望我的代码遵循一些设计原则,以确保我的代码可维护且易于理解。


这是我最初尝试生成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);

优点:不多。它可以工作,所以很好。

缺点:

  • 单元格引用是经过硬编码的,因此在我的代码中到处都是乱七八糟的数字。
  • 如果不更新许多单元格引用,则很难添加或删除列和行。
  • 我需要学习一些第三方库。一些库的使用方式与其他库一样,但是仍然存在问题。我有一个问题,com互操作库使用基于1的单元格引用,而Aspose.Cells使用基于0的单元格引用。

这是一种解决上面列出的缺点的解决方案。我想将数据表当作自己的对象,可以在不深入了解单元操作和不干扰其他单元格引用的情况下随意移动和更改数据。这是一些伪代码:

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

作为该解决方案的一部分,我将具有一些BlockEngine对象,该对象将容纳一个Blocks容器并执行将数据输出为* .xlsx文件所需的单元格操作。块对象可以附加格式。

优点:

  • 这将删除我的初始代码中的大多数魔术数字。
  • 尽管我提到的BlockEngine对象中仍然需要进行单元操作,但这隐藏了许多单元操作代码。
  • 在不影响电子表格其他部分的情况下,添加和删除行要容易得多。

缺点:

  • 添加或删除列仍然很困难。如果要交换第二列和第三列的位置,则必须直接交换单元格内容。在这种情况下,这将是八个编辑,因此有八个机会犯错。
    • 如果这两列有任何格式,我也必须更新。
  • 该解决方案不支持水平块放置;我只能将一个方块放在另一个方块之下。当然可以tableRight.PutToRightOf(tableLeft),但是如果tableRight和tableLeft的行数不同,那会引起问题。要放置表,引擎必须知道其他所有表。对我来说,这似乎不必要地复杂。
  • 我仍然需要学习第三方代码,尽管通过Block对象和BlockEngine的抽象层,代码将比我最初尝试的紧密耦合到第三方库。如果我想以松散耦合的方式支持许多不同的格式设置选项,则可能不得不编写很多代码。我的BlockEngine真是一团糟。

这是采用不同路线的解决方案。过程如下:

  1. 我获取报告数据并以我选择的某种格式生成一个xml文件。

  2. 然后,我使用xsl转换将xml文件转换为Excel 2003 XML Spreadsheet文件。

  3. 从那里,我简单地使用第三方库将xml电子表格转换为xlsx文件。

我发现此页面描述了类似的过程,并包含代码示例。

优点:

  • 该解决方案几乎不需要细胞操作。相反,您可以使用xsl / xpath进行操作。为了交换一个表中的两列,您将xsl文件中的整个列都移动了,这与我的其他需要单元交换的解决方案不同。
  • 尽管您仍然需要可以将Excel 2003 XML电子表格转换为xlsx文件的第三方库,但这几乎就是您所需要的库。您需要编写的可调用第三方库的代码量很小。
  • 我认为此解决方案最容易理解,所需代码最少。
    • 以我自己的xml格式创建数据的代码将很简单。
    • 仅因为Excel 2003 XML Spreadsheet很复杂,xsl文件才会变得复杂。但是,检查xsl文件的输出很容易:只需在Excel中打开输出并检查错误消息即可。
    • 生成示例Excel 2003 XML电子表格文件很容易:只需创建一个看起来像所需的xlsx文件的电子表格,然后将其另存为Excel 2003 XML电子表格即可。

缺点:

  • Excel 2003 XML电子表格不支持某些功能。例如,您不能自动调整列宽。您不能在页眉或页脚中包含图片。如果要将生成的xlsx文件导出为pdf,则不能设置pdf书签。(我使用单元格注释共同解决了此问题。)。您必须使用第三方库来执行此操作。
  • 需要一个支持Excel 2003 XML电子表格的库。
  • 使用11岁的MS Office文件格式。

注意:我意识到xlsx文件实际上是包含xml文件的zip文件,但是xml格式对于我而言似乎太复杂了。


最后,我研究了涉及SSRS的解决方案,但对于我的目的而言似乎太过膨胀。


回到我最初的问题,什么是在代码中生成Excel文件的良好设计模式?我可以想到一些解决方案,但是似乎没有一个解决方案是理想的。每个都有缺点。


更新:所以我尝试使用BlockEngine解决方案和XML Spreadsheet解决方案来生成类似的XLSX文件。这是我对他们的看法:

  • BlockEngine解决方案:

    • 考虑到替代方案,这仅需要太多代码。
    • 如果发现偏移错误,我发现用另一个块覆盖一个块太容易了。
    • 我最初说过,格式化可以附加在块级别。我发现这并没有比将块内容单独进行格式化更好。我想不出一种将内容和格式结合起来的好方法。我也找不到一种将它们分开的好方法。只是一团糟。
  • XML电子表格解决方案:

    • 我现在要使用这种解决方案。
    • 需要重复说明的是,该解决方案需要更少的代码。我实际上是用Excel本身替换BlockEngine。我仍然需要针对书签和分页符等功能进行破解。
    • XML Spreadsheet格式有点挑剔,但是很容易进行一些小的更改并将结果与​​您喜欢的Diff程序中的现有文件进行比较。一旦发现了一些特质,就可以将其放在适当的位置,然后从那里忘记它。
    • 我仍然担心此解决方案依赖于较旧的Excel文件格式。
    • 我创建的XSLT文件易于使用。在这里,处理格式比使用BlockEngine解决方案要简单得多。

Answers:


7

如果您确实想要适合您的东西,那么我建议您习惯“不必要地复杂”的想法……这就是处理Microsoft Office文件格式的本质。

我(有点)喜欢您对“块”的想法...我将使子类的块对象(如表格)具有独立于单元格概念的列和行。然后使用您的块引擎将它们转换为XSLS文件。

过去,我已经成功使用过OpenXML SDK,但不要尝试阅读文档并从头开始。而是在Excel中创建所需的精确副本,保存并使用提供的Document Reflector工具进行检查。它将为您提供创建文档所需的C#代码,您可以从中学习和修改。


Office文档并非 “不必要地复杂”-它们正在执行或允许进行广泛的操作,格式化,功能等
Warren

5
我并不是说,文件格式本身尽可能多的不必要的复杂,因为我争辩说,工作与他们的。例如,使用OpenXML SDK要求您知道添加元素的神奇顺序...例如,向演示文稿添加幻灯片布局是行不通的。您必须先将其添加到幻灯片中,然后再添加到演示文稿中。为什么?因为Microsoft用这种方式对库进行编码。也有很多奇怪的循环引用需要管理。我知道格式需要复杂性,但是使用它应该不会那么痛苦。
mgw854 2014年

3

这是我过去经常使用的解决方案:

  • 创建一个常规Excel文档(通常为xlsx格式)作为模板,其中包含所有列标题,包括它们的标题,列的默认格式以及标题单元格的格式。

  • 将该模板嵌入到程序的资源中。在运行时,第一步是将模板提取为新文件并将其放入目标文件夹中

  • 使用Interop或第三方库将数据填充到新创建的xlsx中。不要引用硬编码的列号,而要使用一些元数据(例如列标题)来标识正确的列。

优点:

  • 现在,像“块”方法之类的方法效果更好。例如,列交换:无需更改块代码中的任何内容,因为正确的列由其标题标识

  • 只要您的列具有唯一的格式,大多数格式都可以通过操纵模板直接在Excel中完成。这给您一种所见即所得的感觉,并且可以自由使用Excel中可用的任何格式设置选项,而无需为其编写代码

缺点:

  • 您仍然需要使用第三方lib或Interop。我是否提到Interop运行缓慢?

  • 当模板中的列标题更改时,您还需要调整代码(但是可以通过具有验证例程来轻松检测到该例程,该例程将发出信号,告知是否缺少预期的列)

  • 当您需要在同一列中对不同单元格进行动态格式化时,仍然需要在代码中进行处理

作为一般提示,无论选择哪种方法,它都具有将布局与内容分开并使用声明性解决方案的优势。


0

有两件事要考虑:

  • 以给定格式创建文件的复杂性
  • 当文件内容的结构需要更改时,代码易于断裂。

关于第一个:

如果您需要生成的电子表格不包含任何格式或公式,则可以很直接地生成CSV或制表符分隔的文件,而不是实际的XLSX。Excel通常在许多PC上默认打开这些文件。这不会帮助您围绕列和行进行硬编码,但是它将节省您处理Excel对象模型的额外工作。

如果需要格式或公式,那么使用Excel对象模型是一种合理的方法,特别是如果您构建的电子表格本身不是太“硬编码”。换句话说,如果您的电子表格适当地使用了相对公式和范围名称,那么它可以与更少的魔术数字编码一起使用。

关于第二个:

您可以使用带有硬编码的行和列引用的单元格进行操作,也可以使用数组/列表集合和for循环来概括单元格的数量。


在最初的问题中,我并不清楚我想在解决方案中控制格式和打印选项等。关于第二点,我认为您指的是我在BlockEngine解决方案中描述的内容。我可以拿出一个东西IList<IBusinessObject>吐出来Block。利弊仍然是一样的。
user2023861 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.