使用 C# 访问 Excel 2003 工作簿的不同方法





5.00/5 (9投票s)
比较 Excel 对象库和原生 C# 库 NPOI 以从 .xls 文件中提取信息。
Excel 2003 和 Visual Studio 2010
在我们的一个项目中,需要从现有的 Excel 2003 工作簿中提取信息(*.xls* 文件扩展名)。客户方无法选择使用较新的基于 XML 的格式。所选 IDE Visual Studio 2010 的 Visual Studio Tools for Office 不再支持 Excel 2003(请参阅按 Office 应用程序和项目类型提供的功能,http://msdn.microsoft.com/en-us/library/aa942839.aspx)。
因此,在关注 Microsoft 的规定时,Microsoft Excel 11.0 对象库的 COM Interop 是唯一的方法。但是此解决方案取决于安装的 Excel 版本。因此,为了能够使用和测试 Excel 2003 绑定,首先需要安装 Excel 2003。
本文随附的下载(Compare_COM_vs_NPOI_v11_20120221.zip - 1 MB)也提供了一个示例 Excel 文件。它包含一些带有内容和计算的工作表。
使用 Excel 11.0 对象库访问 Excel 2003 文件
以下代码片段展示了对 Excel 2003 文件全部内容的简单遍历。首先,使用 Excel `Application` 实例打开 *.xls* 文件。然后,逐行逐列遍历所有工作表。每个单元格的值和注释都打印到控制台屏幕上。
文章“使用 C# 打开和导航 Excel”(https://codeproject.org.cn/Articles/5123/Opening-and-Navigating-Excel-with-C)提供了关于 COM 互操作的全面分步教程。
try
{
xlApp = new Excel.Application();
if (null == xlApp)
{
Console.WriteLine("Excel could not be started. Check that your " +
"office installation and project references are correct.");
return;
}
bool openReadOnly = true;
xlWorkbook = xlApp.Workbooks.Open(extractFile,
0, openReadOnly, 5, "", "", false,
Excel.XlPlatform.xlWindows, "",
true, false, 0, true, false, false);
if (null == xlWorkbook)
{
Console.WriteLine(string.Format(
"Excel Workbook '{0}' could not be opened.", extractFile));
}
Console.WriteLine();
foreach (Excel.Worksheet xlSheet in xlWorkbook.Worksheets)
{
if (null != xlSheet)
{
Console.WriteLine();
Console.WriteLine("*** Worksheet " + xlSheet.Name + " ***");
Excel.Range usedRange = xlSheet.UsedRange;
if ((null != usedRange) && (null != usedRange.Cells))
{
xlCells = usedRange.Cells;
for (int rowNumber = 1; rowNumber <= usedRange.Rows.Count; ++rowNumber)
{
if (1 == (rowNumber % 10)) { Console.WriteLine(); }
Console.Write(string.Format("Row {0,2}: ", rowNumber));
for (int colNumber = 1; colNumber <= usedRange.Columns.Count; ++colNumber)
{
dynamic cell = xlCells[rowNumber, colNumber];
if (null != cell)
{
string value = GetValue(cell);
string comment = GetComment(cell);
Console.Write(string.Format("{0}{1};",
string.IsNullOrWhiteSpace(value) ? "" : value,
string.IsNullOrWhiteSpace(comment) ? "" : " [" + comment + "]"));
}
else
{
Console.Write(";");
}
}
Console.WriteLine();
}
}
Marshal.FinalReleaseComObject(xlSheet);
Console.WriteLine();
}
}
xlWorkbook.Close(Type.Missing, Type.Missing, Type.Missing);
xlApp.Quit();
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
if (null != ex.InnerException)
{
Console.WriteLine("Inner Exception: " + ex.InnerException.Message);
}
}
finally
{
if (null != xlUsedRange) { Marshal.FinalReleaseComObject(xlUsedRange); xlUsedRange = null; }
if (null != xlCells) { Marshal.FinalReleaseComObject(xlCells); xlCells = null; }
if (null != xlWorkbook) { Marshal.FinalReleaseComObject(xlWorkbook); xlWorkbook = null; }
if (null != xlApp) { Marshal.FinalReleaseComObject(xlApp); xlApp = null; }
}
要从单元格中提取内容,使用 `GetValue` 方法。
private static string GetValue(dynamic cell)
{
string ret = string.Empty;
if (null == cell) { return ret; }
Excel.Range singleCell = cell as Excel.Range;
if (null == singleCell) { return ret; }
if (null != singleCell.Text)
{
ret = singleCell.Text as string;
}
if (null == ret) { return string.Empty; }
return ret.Replace("\n", " "); // remove line break
}
使用 `GetComment` 方法提取单元格的注释。
private static string GetComment(dynamic cell)
{
string ret = string.Empty;
if ((null == cell) || (null == cell.Comment)) { return ret; }
ret = cell.Comment.Text as string;
if (null == ret) { return string.Empty; }
return ret.Replace("\n", " "); // remove line break
}
使用 NPOI 库访问 Excel 2003 文件 - 分步说明
NPOI 库提供了另一种访问方法。该库在 Microsoft CodePlex (http://npoi.codeplex.com/) 上提供,并在 GitHub (https://github.com/tonyqus/npoi) 上开发。该实现仅用 C# 完成,因此提供了对 Excel 文件的纯托管访问。
NPOI 库本身是从一个名为 Apache POI 的 Java 项目移植过来的(Apache POI - Microsoft 文档的 Java API,http://poi.apache.org/)。这两个库都成熟且正在积极开发中。
以下代码片段使用 NPOI 库打印 Excel 2003 文件的全部内容。最初,使用标准流读取器读取 XLS 文件。可以根据需要将通常的标准标志应用于流读取器。从 `POIFSFileSystem` 类读取流。此类读取和写入 Microsoft 的 OLE 2 复合文档格式。这是 Excel 文件格式的基础格式。最后,`HSSFWorkbook` 反映了 Excel 用户可见的 Excel 文件结构。
using (StreamReader input = new StreamReader(extractFile))
{
IWorkbook workbook = new HSSFWorkbook(new POIFSFileSystem(input.BaseStream));
if (null == workbook)
{
Console.WriteLine(string.Format("Excel Workbook '{0}' could not be opened.", extractFile));
return;
}
...
}
接下来,评估工作簿的公式并创建数据格式化程序实例。
IFormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator(workbook);
DataFormatter dataFormatter = new HSSFDataFormatter(new CultureInfo("en-US"));
工作簿的各个工作表可以通过数字、名称或使用迭代器访问。这是一个基于迭代器的示例。
foreach (ISheet sheet in workbook)
{
Console.WriteLine("\n\n*** Worksheet " + sheet.SheetName + " ***");
...
}
再次使用迭代器访问给定工作表的行。行号从 `IRow` 类型变量中获取并打印到控制台输出。
foreach (IRow row in sheet)
{
Console.Write(string.Format("Row {0,2}: ", row.RowNum + 1));
}
现在,从行的每个单元格中提取内容,进行格式化,并写入控制台。这同样适用于与单元格关联的注释。
foreach (ICell cell in row)
{
string value = GetValue(cell, dataFormatter, formulaEvaluator);
string commentText = GetComment(cell);
Console.Write(string.Format("{0}{1};",
string.IsNullOrWhiteSpace(value) ? "" : value,
string.IsNullOrWhiteSpace(commentText) ? "" : " [" + commentText + "]"));
}
使用 NPOI 库访问 Excel 2003 文件 - 完整示例
这是完整的代码片段,可以与上面的 COM Interop 片段进行比较。
try
{
using (StreamReader input = new StreamReader(extractFile))
{
IWorkbook workbook = new HSSFWorkbook(new POIFSFileSystem(input.BaseStream));
if (null == workbook)
{
Console.WriteLine(string.Format("Excel Workbook '{0}' could not be opened.", extractFile));
return;
}
// calculates/updates the formulas on the given workbook
IFormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator(workbook);
DataFormatter dataFormatter = new HSSFDataFormatter(new CultureInfo("en-US"));
Console.WriteLine();
foreach (ISheet sheet in workbook)
{
Console.WriteLine("\n\n*** Worksheet " + sheet.SheetName + " ***");
foreach (IRow row in sheet)
{
if (0 == row.RowNum % 10) { Console.WriteLine(); }
Console.Write(string.Format("Row {0,2}: ", row.RowNum + 1));
foreach (ICell cell in row)
{
string value = GetValue(cell, dataFormatter, formulaEvaluator);
string commentText = GetComment(cell);
Console.Write(string.Format("{0}{1};",
string.IsNullOrWhiteSpace(value) ? "" : value,
string.IsNullOrWhiteSpace(commentText) ? "" : " [" + commentText + "]"));
}
Console.WriteLine();
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
if (null != ex.InnerException)
{
Console.WriteLine("Inner Exception: " + ex.InnerException.Message);
}
}
再次使用 `GetValue` 方法从单元格中提取内容。但这次是 NPOI 变体
private static string GetValue(ICell cell, DataFormatter dataFormatter,
IFormulaEvaluator formulaEvaluator)
{
string ret = string.Empty;
if (null == cell) { return ret; }
ret = dataFormatter.FormatCellValue(cell, formulaEvaluator);
return ret.Replace("\n", " "); // remove line break
}
使用 `GetComment` 方法提取单元格的注释。
private static string GetComment(ICell cell)
{
string ret = string.Empty;
if ((null == cell) || (null == cell.CellComment)) { return ret; }
IRichTextString str = cell.CellComment.String;
if (str != null && str.Length > 0)
{
ret = str.ToString();
}
return ret.Replace("\n", " "); // remove line break
}
这是显示部分提取信息的屏幕截图。
比较 NPOI 和 Excel 对象库
比较这两个代码片段,很明显 NPOI 提供了一个**现代接口**,例如,可以使用 `foreach` 迭代实例,并且资源会自动安全地释放。此外,NPOI 提供了一个强类型接口,这是大多数 C# 开发人员今天所期望的。库 API 以清晰的 C# 方式设计。这与 COM Interop 不同,COM Interop 中大多数参数类型都是 `object`。
令我们惊讶的是,NPOI 的实现**比 COM 替代方案快得多**。随附的存档中的程序包含用于测量的秒表。测试机是 Core i7 860@2.8GHz,3 GB RAM,Win7 32 位。多次执行这两个程序,COM Interop 大约需要 2500 - 4200 毫秒才能完成,而 NPOI 程序只需要 350 - 410 毫秒。即使我们项目中使用的更大样本仍然几乎立即返回。尤其是在避免冗长的控制台输出时更是如此。但请尝试使用您自己的样本。
NPOI 库方法的**文档**可提供与标准 .NET 类相同的水平,包括 Intellisense 帮助和导航到文档化方法。COM Interop DLL 仅提供方法的名称和参数列表,其中结构化类型都是无意义的基本 `object` 类型。NPOI 库提供完整的源代码。源代码在 Apache 许可证 2.0 下获得许可,可用于**任何项目,甚至是商业和专有项目**。
由于 `StreamReader` 的存在,以只读文件形式打开 Excel 文件进行提取在 NPOI 中变得**自然**。COM Interop 需要使用带有长参数列表的 `Open` 方法。当从客户提供的 *.xls* 文件中提取信息时,它会按预期打印内容,但使用 COM 互操作时,每次调用后都会弹出一个“保存更改”对话框。以只读文件形式打开 *.xls* 文件并不能避免这个不需要的对话框。原因似乎是在访问某些单元格期间公式的更新。这与 NPOI 不同,NPOI 不会更改 *.xls* 文件。
Excel 始终在 PC 上以单个实例运行。因此,它不得用于**服务器环境**。NPOI 没有此限制。
本文的下载**存档**提供了一个包含三个项目的 Visual Studio 2010 解决方案。每个项目都提取 Excel 文件的全部信息。第一个名为 *ReadExcelUsingCom* 的项目使用 COM Interop DLL 来执行任务。这是本文开头示例所提取的项目。第二个名为 *ReadExcelUsingNPOI* 的项目是上面详细解释的使用 NPOI 的示例。该程序显示 Excel 文件中的所有内容。但是 COM Interop 程序也显示空行。因此,开发了第三个程序,即使使用 NPOI,它也能生成与 COM 示例完全相同的输出。最后一个程序包含在 `ReadExcelUsingNPOICloneCom` 项目中。
除了这三个程序之外,解决方案文件还包含最新版本的 NPOI DLL。因此,这些示例有望开箱即用。当然,缺少 Excel 2003,必须单独安装才能使 COM Interop 示例正常工作。重复一遍,需要安装 Excel 2003,不支持其他版本。
NPOI 库和 Excel 11.0 对象库的 COM 互操作的比较可以**总结**如下:
- NPOI 提供了一个强类型接口。
- 独立的 NPOI 库不依赖于已安装的 Excel 版本。
- 即使只用三个小工作表进行测试,NPOI 也明显更快。
- NPOI 读取 *.xls* 文件时不会对其进行更改。
- Microsoft 仅支持 Excel 11.0 对象库。
- 开源 NPOI 库提供完整的源代码。
历史
- 初始文章
- 更新了下载和文章到 NPOI 1.2.5 预发布版,发布日期为 2012-02-21,svn r309
- NPOI 库的源代码已移至 https://github.com/tonyqus/npoi。已修复上面的引用