DataTable列定义须显式指定类型如dt.Columns.Add("Id", typeof(int)),避免默认string导致类型异常;跨表添加行用ImportRow而非Rows.Add;查数据优先Select()而非DefaultView.RowFilter。

DataTable 创建和列定义怎么写才不踩坑
直接 new DataTable() 没问题,但列类型必须明确指定,否则后续赋值 DBNull.Value 或类型不匹配时会抛异常(比如把 "123" 往 int 列里塞)。别用 dt.Columns.Add("Name") 这种无类型写法——它默认是 string,但掩盖了设计意图,后期改类型或导出到数据库时容易出错。
- 推荐写法:
dt.Columns.Add("Id", typeof(int))或new DataColumn("Name", typeof(string)) - 注意:
typeof(string)比System.Type.GetType("System.String")更简洁、更安全(后者字符串拼错就 null) - 如果列可能为空,
int?不被 DataTable 原生支持,得用typeof(int)+ 允许DBNull.Value,读取时用row["Id"] == DBNull.Value ? null : (int?)row["Id"]
添加行为什么用 ImportRow 而不是 Rows.Add(dr)
当你从另一个 DataTable 里取 DataRow(比如 src.Select("...")[0]),再往目标表加时,直接 dst.Rows.Add(dr) 会报错:“该行已经属于另一个表”。这是 DataTable 的所有权机制导致的——DataRow 绑定在创建它的表上。
- 正确做法是:
dst.ImportRow(dr),它复制数据和状态(包括RowState),不破坏原行归属 -
Rows.Add(object[])也安全,比如dst.Rows.Add(srcRow.ItemArray),但丢失原始RowState和表达式列值 - 批量插入场景下,
ImportRow比Merge快 100 倍(尤其多表合并时)
Select() 和 DefaultView.RowFilter 性能差在哪
查数据别一上来就用 dt.DefaultView.RowFilter = "Age > 25"; dt.DefaultView.ToTable()。这会强制构建完整 DataView 并生成新表,内存和 CPU 开销大;而 dt.Select("Age > 25") 是纯内存扫描,返回 DataRow[],快且轻量。
- 小数据量(Select() 通常快 10–30 倍
-
Select()不支持 LIKE 通配(只能用column LIKE 'A%',且区分大小写),也不支持函数如UPPER();需要这些就得切到DefaultView - 若要复用筛选逻辑,建议封装为方法并缓存
DataView实例,而非反复ToTable()
DataTable 大了以后卡顿,怎么救
界面绑定后还一边循环 Rows.Add() 一边刷新控件?那是典型“边绑边填”反模式。GridControl 等控件监听 Rows.CollectionChanged,每加一行都触发重绘,1000 行可能卡死几十秒。
- 务必先解绑:
grid.DataSource = null,填完再赋值 - 大数据量(>1w 行)优先考虑分页或虚拟滚动,而不是全量加载到 DataTable
- 避免在循环里反复调用
row["ColName"]——改用row[index](列序号)或提前缓存DataColumn对象 - 真要高性能遍历,用
dt.CreateDataReader(),它比foreach (DataRow r in dt.Rows)少一半对象分配开销
最常被忽略的一点:DataTable 本质是内存中的关系表,不是 ORM 替代品。它适合中等规模、结构稳定、需离线操作的场景;一旦涉及复杂关联、实时计算或百万级数据,该换 System.Linq.Expressions + List 或实体类集合了。









