sqlbulkcopy 必须用 datatable 或实现 idatareader,不支持直接传 ienumerable;常见失败因类型不匹配或空字符串未处理;需严格对齐列类型、清理空值、调优 batchsize/enablestreaming 并关闭约束索引。

SqlBulkCopy 是 C# 里唯一靠谱的千万级批量插入方案,其他方式(比如循环 INSERT、Entity Framework SaveChanges、甚至 DataTable.Load 配合单条命令)在百万以上数据时基本会卡死或超时。
SqlBulkCopy 必须用 DataTable 还是能用 IEnumerable?
必须先转成 DataTable 或实现 IDataReader —— SqlBulkCopy.WriteToServer 不接受任意集合。直接传 List<myentity></myentity> 会编译报错或运行时报 Invalid operation. The connection is closed.(因底层尝试调用 GetSchemaTable 失败)。
实操建议:
- 别手写
DataTable列定义:用DataTable.Clone()+DataRow.ItemArray = obj.GetType().GetProperties().Select(p => p.GetValue(obj)).ToArray()容易出类型不匹配(比如DateTime?写进DateTime列) - 推荐用开源轻量工具如
FastMember:它内部实现IDataReader,支持直接传IEnumerable<t></t>,且字段映射、空值处理、类型转换都更稳 - 如果坚持用原生
DataTable,务必提前设好列类型:dt.Columns["CreatedTime"].DataType = typeof(DateTime),否则默认是object,BulkCopy 会拒绝写入
为什么 SqlBulkCopy 写入中途报 “The given value of type String from the data source cannot be converted to type datetime for the specified target column”?
这是最常见也最隐蔽的失败原因:源数据中某列含空字符串 "" 或 null,而目标表该列为 NOT NULL datetime,但 DataTable 里对应列类型是 string 或未设允许空值(AllowDBNull = false),导致 BulkCopy 在类型推导阶段就崩了。
解决要点:
- 目标列类型必须和数据库严格一致:datetime →
typeof(DateTime),int →typeof(int),哪怕字段允许 null,也要用typeof(DateTime?)并设置column.AllowDBNull = true - 空字符串要提前清理:入库前对 string 字段做
string.IsNullOrWhiteSpace(s) ? (object)DBNull.Value : s - 不要依赖
DataTable.Load()自动推断类型 —— 它常把全数字字符串当int,后续遇到带小数点的就翻车
如何让 SqlBulkCopy 真正跑满 IO,而不是卡在 .NET 或 SQL Server 端?
默认配置下,SqlBulkCopy 往往只发挥出磁盘 30% 的写入能力。关键不在代码逻辑,而在连接与选项控制。
必须检查这几项:
- SQL Server 连接字符串加
Packet Size=8192;Connection Timeout=300;—— 小包会放大网络开销;超时太短批量过程直接中断 - 设置
SqlBulkCopy.BatchSize = 10000(不是越大越好,5w+ 可能触发 tempdb 压力,1w 是多数场景平衡点) - 开启
SqlBulkCopy.EnableStreaming = true(.NET 4.5+),避免整张DataTable全部加载进内存 - 务必关闭约束和索引:执行
ALTER TABLE xxx NOCHECK CONSTRAINT ALL和DROP INDEX(完事后重建),否则每批插入都触发索引维护,速度跌 5–10 倍 - 数据库恢复模式临时切为
BULK_LOGGED(仅限非生产调试或可接受日志截断的场景)
真正难的不是写几行 SqlBulkCopy,而是把源数据结构、空值策略、SQL Server 状态、网络缓冲这四层对齐。漏掉任意一层,都可能从“秒级”退化成“小时级”。











