ExcelDataReader读Excel到List<T>最省事:用Stream+Json或手动映射,避坑需注意CanSeek、DBNull、列名映射、脏数据过滤及浮点/日期精度问题。

用 ExcelDataReader 读取 Excel 文件到 List<T> 最省事
直接用 ExcelDataReader + System.Text.Json 或手动映射,比 EPPlus 轻、比 OleDb 稳,尤其适合只读场景。它不依赖 Office 安装,也不触发 COM,纯 .NET Standard 兼容。
常见错误现象:NotSupportedException: Stream does not support seeking —— 传了未开启 CanSeek 的流(比如从 HttpRequest.Body 直接读);InvalidDataException: Unable to read beyond the end of the stream —— 文件被其他进程占用或损坏。
- 务必用
using var stream = File.OpenRead("xxx.xlsx");或确保传入的Stream支持Seek - 打开时加
new ExcelReaderConfiguration { ReadHeader = true },否则第一行会被当数据 - 如果 Excel 列名含空格或特殊字符,映射到 C# 属性时得用
[JsonPropertyName("Order Date")]或手动赋值,别硬靠属性名自动匹配
DataTable 中转再转 List<T> 是最稳的兼容写法
不是所有 Excel 都规整:合并单元格、空行、类型混杂(一列里有数字和文本)、日期格式不统一……这时候强求“一键映射”反而掉坑里。先读进 DataTable,再逐行校验转换,可控性高得多。
使用场景:导入模板由业务方提供,字段顺序/名称/格式经常变;需要跳过前几行说明;某列要按规则清洗(如把 “Y/N” 转成 bool)。
- 用
ExcelDataReader.AsDataSet()得到DataSet,取Tables[0]即可 - 遍历
Rows时,用row["列名"] as string+int.TryParse()比直接(int)row["列名"]安全十倍 - 注意 Excel 里空单元格读出来是
DBNull.Value,不是null,判空得写== DBNull.Value
用 Mapper.Map<List<T>>(dataTable) 类型太松容易崩
很多示例用 AutoMapper 或 DataTableExtensions.ToList<T>() 一类扩展方法,看着一行代码搞定,实际运行时静默丢数据或抛 InvalidCastException —— 因为这些工具默认按列名+类型严格匹配,而 Excel 的“数字”列可能第 5 行突然塞了个 “N/A”,整个映射就中断。
性能影响:自动映射会反射每个属性,对 10 万行以上数据,比手写循环慢 3–5 倍;兼容性上,.NET 6+ 的 SourceGen 映射器虽快,但不支持运行时列名动态变更。
- 真要用自动映射,先调用
dataTable.Rows.Cast<DataRow>().Where(r => r.ItemArray.All(c => c != DBNull.Value))过滤脏行 - 给目标类属性加
[Column("实际列名")]特性,别依赖大小写或下划线自动转换 - 日期列务必显式指定
DateTime.ParseExact(..., "yyyy-MM-dd", ...),Excel 的日期序列号在不同区域设置下解析结果可能差一天
导出 Excel 再导入?小心浮点数精度和日期偏移
如果数据源本身是 Excel 导出的(比如用户改完又传回来),要注意 Excel 存储数字用双精度浮点,12345678901234567 这种长整数会变 12345678901234568;日期则可能比原始值多 1 天(Excel 1900 闰年 bug)。
这不是你代码的问题,是 Excel 格式限制。所以别拿导入后的 double 去 == 比较原始值,也别假设 DateTime 和 Excel 单元格显示完全一致。
- 整数列优先读为
string再long.TryParse(),绕过浮点截断 - 日期列用
row.GetDateTime(索引)比Convert.ToDateTime(row[索引])更准,前者走 Excel 内部序列号逻辑 - 如果业务允许,导入后立刻存为
JSON或数据库,别再导回 Excel——链路越长,精度损失越不可控
真正麻烦的从来不是怎么读进来,而是 Excel 里那些没写进需求文档的“人肉格式”:合并标题行、注释列、条件色块、隐藏列……这些没法靠库自动识别,得提前跟业务方约好模板规范,或者留个 ValidateBeforeImport() 手动检查逻辑。









