推荐使用 exceldatareader 替代 oledbconnection:它支持 .xls/.xlsx、纯托管无依赖,避免 isam 错误和类型推断问题;配合 dataset 扩展可生成 datatable 供 sqlbulkcopy 高效导入,注意列映射、临时表校验及日期/数字格式处理。

用 ExcelDataReader 读取 Excel 文件比 OleDbConnection 更稳
直接用 OleDbConnection 读 Excel(尤其是 .xlsx)在 .NET 6+ 环境下容易报 "Could not find installable ISAM" 或字段类型自动推断错——尤其当第一行是空或混合类型时,它会按前 8 行采样并固化列类型,后续数据被截断或转成 NULL。
推荐用开源库 ExcelDataReader(支持 .xls 和 .xlsx,纯托管,无 Office 依赖):
dotnet add package ExcelDataReader dotnet add package ExcelDataReader.DataSet // 如需转 DataTable
关键实操点:
- 务必用
new ExcelReaderFactory().CreateReader(stream),别用CreateOpenXmlReader单独判断扩展名——它对加密/损坏文件容错差 - 调用
reader.IsFirstRowAsColumnNames = true前,先确认 Excel 第一行确实是表头;否则设为false,手动映射列名 - 读取时用
while (reader.Read())配合reader.GetValue(i),避免reader.GetFieldType(i)返回object导致后续转换失败
批量插入数据库别用单条 INSERT,优先走 SqlBulkCopy
逐行 INSERT 几千条 Excel 数据,耗时可能从 2 秒拉长到 2 分钟以上,且容易触发连接超时或事务日志暴涨。
SqlBulkCopy 是 SQL Server 场景下的最优解(注意:仅限 SQL Server,MySQL/PostgreSQL 需换方案):
- 源数据必须是
DataTable、IDataReader或实现IColumnMapping的对象;用ExcelDataReader+DataSet扩展可直接生成DataTable - 显式设置
DestinationTableName和列映射:bulk.ColumnMappings.Add("ExcelColName", "DbColumnName"),避免因顺序不一致写错字段 - 关键性能参数:
BatchSize = 10000、EnableStreaming = true(防大内存占用)、SqlBulkCopyOptions.TableLock(减少锁竞争) - 若目标表有自增主键或计算列,确保
DataTable中对应列为DBNull.Value,且数据库端设好IDENTITY_INSERT OFF
日期和数字列解析失败?不是 Excel 问题,是区域性设置在作怪
Excel 里显示 “2024/3/15” 或 “12,345.67”,但读出来是 double 类型的 45366.0 或 12345.67——这是因为 Excel 内部用序列号存日期、用浮点存数值,而 ExcelDataReader 默认不自动转换格式。
处理方式取决于你是否需要原始语义:
- 要保持 Excel 显示样式:读取后用
reader.GetDateTime(i)(仅当单元格类型为DateTime)或DateTime.FromOADate(doubleValue)转日期;数字列用Convert.ToDecimal(reader.GetValue(i))防精度丢失 - 要兼容用户本地习惯:别依赖
Thread.CurrentThread.CurrentCulture,显式用DateTime.ParseExact或decimal.Parse(x, NumberStyles.Number, CultureInfo.GetCultureInfo("zh-CN")) - 最省事但有风险:在 Excel 源文件里把日期列设为「文本」格式再录入,这样读出来就是字符串,可控性高
导入中途出错怎么回滚?别依赖 SqlTransaction 包整个 bulk
SqlBulkCopy 本身不参与用户事务(除非用 SqlBulkCopyOptions.UseInternalTransaction,但它只对单 batch 生效),所以用 SqlTransaction 包住 WriteToServer 并不能回滚已写入的数据。
真实可用的容错策略:
- 提前建临时表(
CREATE TABLE #temp_import (...)),用SqlBulkCopy先灌进去,再用INSERT INTO real_table SELECT ... FROM #temp_import WHERE ...做校验后写入——失败只丢临时表 - 对关键业务字段(如唯一约束、外键)做预检查:用
SELECT COUNT(*) FROM real_table WHERE key IN (SELECT key FROM #temp_import)查重 - 记录错误行号和原始值到日志表(非事务表),而不是抛异常中断——用户需要知道第 1234 行“手机号格式错误”,不是“导入失败”
真正麻烦的是 Excel 列名含空格、特殊符号或和数据库关键字冲突(比如叫 order),这种必须在映射阶段重命名,不能靠方括号逃逸——因为 SqlBulkCopy 不解析 SQL 语法,只认列名字符串。











