生产环境推荐使用 exceldatareader 读取 .xlsx/.xls 文件,它轻量可靠、不依赖 office、支持流式读取;需配合 datatable 处理空值、混合类型、日期格式及列名映射,并做好数据清洗、主键校验与批量插入优化。

用 ExcelDataReader 读取上传的 .xlsx 文件最轻量可靠
别碰 Microsoft.Office.Interop.Excel —— 它依赖本地 Office 安装、不能跑在服务器、极易卡死或内存泄漏。生产环境唯一推荐是 ExcelDataReader(配合 System.Data.DataTable 做中间桥接)。
安装 NuGet 包:Install-Package ExcelDataReader(必须选这个,不是 ExcelDataReader.DataSet)
- 只支持
.xlsx和.xls,不支持.csv(那是另一套逻辑) - 读取时默认不加载样式、公式结果,只取单元格最终值,符合导入场景需求
- 流式读取,内存占用低;但注意:它不支持随机访问某张 sheet,得按顺序遍历
- 示例关键代码:
using (var stream = file.OpenReadStream())<br>{<br> using (var reader = ExcelReaderFactory.CreateReader(stream))<br> {<br> do<br> {<br> var result = reader.AsDataSet(new ExcelDataSetConfiguration {<br> UseColumnDataType = true,<br> ConfigureDataTable = _ => new ExcelDataTableConfiguration {<br> UseHeaderRow = true<br> }<br> });<br> // result.Tables[0] 就是第一张 sheet 的 DataTable<br> } while (reader.NextResult());<br> }<br>}
DataTable 转实体时字段类型错乱怎么办
常见现象:DataTable 里明明是数字列,Convert.ToInt32(row["Age"]) 却抛 InvalidCastException —— 因为 ExcelDataReader 默认把空单元格识别为 DBNull.Value,而混合类型列(比如 A1 是 "123",A2 是空)会统一推断为 string 类型。
- 永远先判断
row["Age"] == DBNull.Value再转换,别直接强转 - 数值列建议用
decimal.TryParse(row["Price"].ToString(), out var price),比Convert.ToDecimal更安全 - 日期列特别脆弱:Excel 存的是序列号(如 44562),
ExcelDataReader会自动转成DateTime,但若原始单元格格式是“文本”,就会变成字符串 → 此时DateTime.TryParse是唯一靠谱方式 - 如果列名含空格或特殊字符(如 "User Name"),
DataTable默认允许,但映射到 C# 属性时需用[Column("User Name")]标记(配合 Dapper 或 EF Core)
批量插入数据库前必须做数据清洗和主键冲突处理
用户上传的 Excel 很少“开箱即用”:空行、重复 ID、必填字段缺失、手机号格式错误……直接 INSERT INTO 必崩。
- 用
LINQ过滤掉全空行:rows.Where(r => r.ItemArray.Any(c => c != null && c != DBNull.Value)) - 对业务主键(如
OrderNo)提前查库:context.Orders.Where(x => list.Select(y => y.OrderNo).Contains(x.OrderNo)).ToDictionary(x => x.OrderNo),避免DbUpdateException - 别用 EF Core 的
AddRange直接插几百条——性能差且无事务控制;改用SqlBulkCopy(SQL Server)或NpgsqlBatch(PostgreSQL) - 示例中易漏:
SqlBulkCopy要求DataTable列名与目标表完全一致,且顺序要匹配;否则必须显式设置ColumnMappings
前端传文件后端收不到?检查 form-data 的 key 名和大小限制
ASP.NET Core 默认只允许 128MB 以内文件,且 multipart 表单的 name 必须和后端参数名严格一致,否则 IFormFile 为 null。
- 控制器方法签名必须是:
public async Task<iactionresult> Import([FromForm] IFormFile file)</iactionresult>,不是IFormFileCollection除非你允许多文件 - 前端
FormData.append("file", input.files[0])中的"file"必须和参数名file一致 - 修改最大上传限制(
Startup.cs或Program.cs):services.Configure<iisoptions>(options =><br>{<br> options.MaxRequestBodySize = 100 * 1024 * 1024; // 100MB<br>});</iisoptions> - Kestrel 还要额外配:
webBuilder.ConfigureKestrel(serverOptions =><br>{<br> serverOptions.Limits.MaxRequestBodySize = 100 * 1024 * 1024;<br>});
Excel 导入真正的难点不在读取,而在面对真实用户上传的脏数据时,能否稳定跳过错误行、记录失败原因、并让前端能准确定位第几行哪一列出了问题——这些逻辑比解析本身重要得多。











