在业务系统中,管理员经常需要把Excel或CSV文件里的数据批量灌进数据库。C#作为主流后端语言,可以组合文件解析库与数据库批量写入机制,高效完成这件事。下面从文件读取、数据校验到入库分别说明。

一、读取Excel文件
处理Excel最省心的方式是使用MiniExcel这类轻量库,它基于流式读取,不会像旧版ExcelDataReader那样把整个文件加载进内存。我们先通过NuGet安装MiniExcel,然后用一行代码把sheet映射为实体列表。
假设数据库有一张User表,对应实体如下。注意Excel列名最好和实体属性一致,或者用地图配置别名,避免反射失败。
public class UserImport
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
读取代码非常直观。MiniExcel.Query方法返回IEnumerable,配合ToList即可拿到集合。如果文件很大,也可以不ToList,直接在后续流水线中逐条校验后缓冲入库。
using MiniExcelLibs;
using System.Collections.Generic;
using System.IO;
public List<UserImport> ReadExcel(string filePath)
{
// 默认读取第一个sheet,列名自动匹配属性
var rows = MiniExcel.Query<UserImport>(filePath).ToList();
return rows;
}
这种方式的优点是内存占用低,且支持xlsx和xls。缺点是若用户文件列顺序错乱但列名正确仍可用,若列名不对则需写映射。实际项目里建议先抽样检查表头,再决定是否继续。
二、读取CSV文件
CSV比Excel更简单,但没有统一标准,常见坑是分隔符是逗号还是分号、文本是否用引号包裹、编码是UTF-8还是GB2312。CsvHelper能自动处理多数情况,并支持自定义配置。
下面示例用CsvHelper读取CSV,并指定可能的中文编码。如果文件来自Windows导出,往往需用GB2312,否则中文变乱码。
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.IO;
using System.Text;
public List<UserImport> ReadCsv(string filePath)
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Encoding = Encoding.GetEncoding("GB2312"),
HasHeaderRecord = true
};
using var reader = new StreamReader(filePath, config.Encoding);
using var csv = new CsvReader(reader, config);
var records = csv.GetRecords<UserImport>().ToList();
return records;
}
读取后同样得到UserImport列表。CsvHelper允许通过ClassMap定义列名映射,比如文件里叫“姓名”而实体叫Name,可写映射消除差异。此外,它能抛出BadDataException,方便我们捕获格式错误行。
对于超大型CSV,也可以改为foreach逐行读取,配合后面批量写入的缓冲逻辑,避免一次性占用太多内存。
三、数据基础校验
无论来源是Excel还是CSV,落库前都要做校验。常见校验包括:必填字段为空、年龄负数、邮箱格式错误、重复主键。我们可以在内存列表上用LINQ快速过滤。
下面示例剔除空名和年龄异常的数据,并简单用正则校验邮箱。生产环境可引入FluentValidation做更复杂的规则。
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
public List<UserImport> Validate(List<UserImport> list)
{
var emailRegex = new Regex(@"^[^@s]+@[^@s]+.[^@s]+$");
var valid = list.Where(u =>
!string.IsNullOrWhiteSpace(u.Name) &&
u.Age >= 0 && u.Age < 150 &&
(string.IsNullOrWhiteSpace(u.Email) || emailRegex.IsMatch(u.Email))
).ToList();
return valid;
}
校验阶段建议把被拒绝的行和原因记到另一个列表,最后返回给前端或写日志,让使用者知道哪些数据没导入。不要静默丢弃,否则排查问题很痛苦。
四、批量写入数据库
拿到干净数据后,最关键的是写入方式。若用循环EF SaveChanges,千行数据可能要几秒到几十秒;而SqlBulkCopy走专属协议,万行也常在毫秒级完成。
SqlBulkCopy要求DataTable或IDataReader。我们可以把List转为DataTable,注意列类型要和表一致。下面代码演示批量写SQL Server。
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;
public void BulkInsert(List<UserImport> list, string connStr)
{
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Age", typeof(int));
table.Columns.Add("Email", typeof(string));
foreach (var u in list)
{
table.Rows.Add(u.Id, u.Name, u.Age, u.Email);
}
using var conn = new SqlConnection(connStr);
conn.Open();
using var bulk = new SqlBulkCopy(conn)
{
DestinationTableName = "Users",
BatchSize = 1000
};
bulk.WriteToServer(table);
}
BatchSize设为1000表示每批提交一千行,可平衡内存与事务。若表有自增主键,应在UserImport里把Id留空,并在数据库设自增,避免冲突。另外,可包一层事务,但SqlBulkCopy本身已较稳,大事务反而易锁表。
若使用MySQL,可改用MySqlBulkLoader或逐批INSERT多值;PostgreSQL可用COPY命令。思路一致:减少网络往返和单条编译开销。
五、错误处理与内存建议
导入功能常作为后台任务,应捕获文件损坏、数据库连接断线等异常。建议外层try-catch记录失败文件,并给用户提示。对于十万级以上数据,不要一次性ReadExcel再ToList,而是边读边按批入库。
下表对比两种写入方式,方便选型:
| 方式 | 千行耗时 | 内存占用 | 适用场景 |
|---|---|---|---|
| 循环Insert | 2到5秒 | 低 | 极少数据或调试 |
| SqlBulkCopy | 小于100毫秒 | 中 | 常规批量导入 |
最后提醒,上传文件要限制大小和后缀,防止恶意超大文件拖垮服务。结合上面代码,你就能在C#里搭起一个稳健的Excel与CSV批量导入模块。