C# ClosedXML导出Excel数据错误排查与解决方案
2024-12-29 00:46:49
C# 使用 ClosedXML 导出 Excel 文件:数据错误排查
将数据导出到 Excel 是常见需求,C# 和 ClosedXML.Excel 是很好的工具组合。 但有时,导出结果并非预期,出现数据不一致的情况。本文将深入探讨一个典型场景,并给出解决方案。
问题
在进行 C# 开发时,使用 ClosedXML.Excel 导出 MySQL 数据到 XLSX 文件。数据库中通过存储过程获取的数据,在导出的 Excel 文件中却显示为数字,而非原本的文本或日期数据。实际数据库数据包含'_Date_Hour'
, '_ID'
, 和 '_Area'
等字段,但 Excel 文件却显示类似“44335”,“4833”,“5281”,无法识别的内容。
问题分析
问题的核心在于 ClosedXML 如何处理 DataSet
中的数据。MySqlDataAdapter
的 Fill
方法将数据填充到 DataSet
的 DataTable
中。而问题在于,存储过程中使用 UNION ALL
连接多个 SELECT
语句时,SELECT
返回字段使用了 相同的别名 (Alias) 。比如, col1
, col2
, 以及后面的 _ID
, _Area
在不同SELECT
语句里有不同的意思,而所有列都设置成固定的别名了,导致返回的结果可能并非程序所预期的。
更深入地说 ,数据表内的每一列都通过序号被处理,col1
, col2
在所有结果集中都有索引0,1, 但是每次循环得到结果的数据类型,可能各不相同,造成 ClosedXML 将 DataTable
的第一列, 都默认理解成日期或者数值类型了。因此导出的结果,也就出现了乱码。ClosedXML 基于数据的类型进行转换,无法正确处理混合类型的列,进而产生格式上的错误。
解决方案
方法一:避免别名重复和混用,让每一列都有独特的意义
- 修改存储过程 :调整 SQL 语句,给每一个查询的列添加一个有意义且独一无二的别名。
- 保证数据类型一致性 :确保存储过程返回的数据类型与 C# 代码中期望的数据类型一致。这样才能在Excel中正确显示。
存储过程修改示例:
SELECT
src.col1_date AS col1,
src.col2_date AS col2
FROM
(
SELECT
'_Date_Hour' as col1_date,
_Date_Hour AS col2_date
FROM
check_list
WHERE
_ID = @ID
UNION ALL
SELECT
'_ID' AS col1_id,
_ID AS col2_id
FROM
check_list
WHERE
_ID = @ID
UNION ALL
SELECT
'_Area' AS col1_area,
_Area as col2_area
FROM
check_list
WHERE
_ID = @ID
) AS src;
通过修改别名,避免混淆和数据类型判断的错误。这样做之后, ClosedXML 就可以按照实际的数据类型导出正确结果。
方法二:处理DataTable中列的类型
- 强制转换数据类型 :遍历 DataTable 中的每一列,并根据实际情况将其类型显式设置为字符串类型或其它适合类型。通过明确列的类型,可以让 ClosedXML 正确处理。
public static void ExportDataSetToExcel(DataSet ds)
{
string AppLocation = @"D:\inetpub\wwwroot\PUBLIC\ExcelFiles\";
string guid = Guid.NewGuid().ToString().ToUpper().Replace("-", "_");
HttpContext.Current.Response.Cookies["guid"].Value = guid.ToString();
HttpContext.Current.Response.Cookies["guid"].Expires = DateTime.Now.AddMinutes(3);
string filepath = AppLocation + "Export_" +
DateTime.Now.ToString("ddMMyyyyHHmm") + "_" +
HttpContext.Current.Response.Cookies["guid"].Value + ".xlsx";
using (XLWorkbook wb = new XLWorkbook())
{
for (int i = 0; i < ds.Tables.Count; i++)
{
DataTable dt = ds.Tables[i];
// 遍历所有列,把数据转换成string
foreach (DataColumn column in dt.Columns) {
if (column.DataType != typeof(String)) {
column.DataType= typeof(String);
}
}
wb.Worksheets.Add(dt, dt.TableName);
}
wb.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
wb.Style.Font.Bold = true;
wb.SaveAs(filepath);
wb.Dispose();
}
Thread.Sleep(3000);
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Buffer = true;
HttpContext.Current.Response.ContentType = "application/force-download";
HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment; " +
"filename=Export_" +
DateTime.Now.ToString("ddMMyyyyHHmm") + "_" +
HttpContext.Current.Response.Cookies["guid"].Value + ".xlsx");
HttpContext.Current.Response.TransmitFile(@"D:\inetpub\wwwroot\PUBLIC\ExcelFiles\Export_" +
DateTime.Now.ToString("ddMMyyyyHHmm") + "_" +
HttpContext.Current.Response.Cookies["guid"].Value + ".xlsx");
HttpCookie cookie = new HttpCookie("ExcelDownloadFlag")
{
Value = "Flag",
Expires = DateTime.Now.AddDays(1)
};
HttpContext.Current.Response.AppendCookie(cookie);
HttpContext.Current.Response.End();
}
通过遍历 DataTable
并将所有列转换为 String
类型,保证数据能正常显示,避免由于 ClosedXML 类型推断错误而导致的数据显示问题。
额外的安全建议
- 避免硬编码文件路径 : 将文件保存路径存储在配置文件中,提高灵活性和安全性。
- 增加错误处理 : 在代码中加入 try-catch 语句,捕获可能出现的异常情况,例如文件写入失败等。 这样做可以提升代码健壮性。
- 限制权限 :确保导出文件的存储位置仅限于授权的用户或进程访问。这可以降低潜在的安全风险。
正确处理数据库数据和 Excel 数据类型是导出正确结果的关键。使用这两种方法都可以有效地解决导出数据出现错误的问题。根据实际场景选择适合的方法,才能高效地使用 C# 和 ClosedXML 生成准确的 Excel 文件。