DataTable.Merge() 拼出空行是因为未设主键导致匹配失败,仅复制结构而非数据;垂直拼接应优先用 ImportRow(),配合 BeginLoadData() 提升性能,注意列类型必须严格一致。
DataTable.Merge() 为什么拼出来全是空行?
因为 merge() 默认按主键匹配合并,不是简单堆叠。如果两个 datatable 都没设 primarykey,它就当“所有行都不匹配”,只把第二个表的结构复制过去,数据行不追加——看着像“空行”,其实是没触发行合并逻辑。
- 先确认双方
DataTable的列名、类型完全一致,否则Merge()会跳过不兼容的列(不报错,但数据丢失) - 若只是想垂直拼接(类似 SQL UNION ALL),别用
Merge(),改用Rows.Add()循环或ImportRow() - 如果真要基于主键合并(如更新已有行+追加新行),必须提前调用
dt1.PrimaryKey = new DataColumn[] { dt1.Columns["ID"] };
不用循环,怎么高效拼接两个结构相同的 DataTable?
用 ImportRow() 是最轻量、最安全的方案:它复用原行的字段值和状态,不校验主键,也不触发事件,比 Rows.Add(object[]) 更快且保留原始 DataRowState。
-
foreach (DataRow r in dt2.Rows) { dt1.ImportRow(r); }—— 这是最常用写法,无副作用 - 避免用
dt1.Rows.Add(r.ItemArray):会丢失原始行的RowState(比如Deleted状态变成Added) - 如果
dt2行数超万,建议先dt1.BeginLoadData(),拼完再EndLoadData(),能关闭索引维护和事件通知,提速 30%+
列名相同但类型不同,Merge 或 ImportRow 会怎样?
直接抛异常:System.ArgumentException: “该列已属于另一个表。” 或更隐蔽的 InvalidCastException。这不是运行时数据转换失败,而是 DataTable 在初始化时就拒绝类型冲突的列加入同一表结构。
- 检查方式:对比
dt1.Columns[i].DataType和dt2.Columns[i].DataType,注意int和int?、string和DBNull占位的列都算不兼容 - 临时修复:用
dt2.Clone()创建空表,遍历dt2.Columns,对类型不一致的列手动Columns.Add("name", typeof(int)),再用Rows.Add()拷贝数据(需做类型转换) - 长期建议:上游数据源统一类型,或在读取阶段就用
DataTable.Load()+ 自定义IDataReader做类型归一
拼接后排序失效、筛选变慢,是不是 Merge 导致的?
不是 Merge() 的锅,是 DataTable 本身不自动维护索引。拼接后所有行都在内存里,但 Select()、DefaultView.Sort 这些操作每次都会重新扫描全表——数据量一过几千行,性能断崖下跌。
- 拼接完成后立刻建视图:
dt1.DefaultView.Sort = "CreateTime DESC";,后续dt1.DefaultView查询才走排序索引 - 需要频繁筛选?别依赖
Select(),改用 LINQ to DataTable:dt1.AsEnumerable().Where(r => r.Field<int>("Status") == 1)</int>,.NET 4.0+ 有优化 - 如果拼接是高频操作,考虑换
System.Data.DataSet+ 关系约束,或直接上MemoryCache缓存预聚合结果
拼接本身很简单,麻烦的是结构一致性、状态继承和后续查询路径——很多人卡在第三步,回头才发现第一张表的列类型早就不对了。


















