DataTable转List最稳写法是AsEnumerable().Select()配合对象初始化器,兼顾可读性、类型安全与性能;需确保列名与属性名大小写一致,DBNull值须显式判断,避免反射型扩展方法及序列化转换。

DataTable转List最稳的写法是用AsEnumerable() + Select()
AsEnumerable() + Select()直接用DataTable.AsEnumerable().Select()配合对象初始化器,是目前C#中兼顾可读性、类型安全和性能的主流做法。它不依赖第三方库,也不触发反射全量属性匹配,适合绝大多数业务场景。
常见错误是手写循环逐行New对象——代码冗长且容易漏字段;或盲目用JsonConvert.DeserializeObject<list>>(JsonConvert.SerializeObject(dt))</list>——序列化开销大,空值/日期格式易出错,还可能丢掉DataRow.RowState等元信息。
- 确保
T的属性名与DataTable.Columns列名**完全一致(大小写敏感)**,否则Select()里手动映射更可靠 - 列值为
DBNull.Value时,DataRow[name]会抛InvalidCastException,必须用IsDBNull()判断或用as T?转换 - 如果
T含只读属性或无参构造函数被禁用,Select()初始化会失败,此时改用new T { ... }显式赋值
用DataTableExtensions.ToList<t>()</t>前先看清楚它干了什么
这个扩展方法(来自System.Data.DataSetExtensions)看似省事,但底层靠Activator.CreateInstance() + 反射赋值,性能比手写Select()低3–5倍,且对属性类型容忍度低:比如int列映射到int?属性会失败,DateTime列映射到string属性直接抛异常。
它只适合原型开发或数据结构极其简单、性能不敏感的脚本场景。
- 必须引用
System.Data.DataSetExtensions程序集(.NET Framework默认有,.NET Core/.NET 5+需单独安装NuGet包System.Data.DataSetExtensions) - 列名和属性名不匹配时静默跳过,不报错也不赋值——很难排查空对象问题
- 不支持自定义类型转换(如把"Y/N"字符串转成
bool),得先用DataTable.Columns.Add()加计算列
处理DBNull和类型不匹配的三个安全习惯
数据库字段常为NULL,而C#实体属性多是非空类型,这是运行时崩得最多的地方。别指望框架自动兜底,得在转换层主动防御。
- 统一用
row[name] == DBNull.Value ? null : (T)row[name]模式,尤其对int?、DateTime?等可空类型 - 对字符串字段,用
row[name]?.ToString() ?? string.Empty,避免NullReferenceException - 遇到类型硬冲突(如SQL Server的
bit列映射到C#的bool没问题,但映射到int就挂),优先改实体属性类型,其次在Select()里做Convert.ToBoolean()等显式转换
大数据量下别碰CopyToDataTable()反向操作
有人想“先转List再转回DataTable方便后续筛选”,结果在万级行数据上调用List<t>.ConvertAll()</t>生成DataRow再CopyToDataTable(),内存暴涨且速度骤降。这不是转换问题,是设计误用。
DataTable本质是内存中的关系表结构,带索引、约束、事件等开销;List<t></t>是纯数据容器。两者定位不同,强行互转超过5000行就该警觉。
- 真需要动态查询,用
AsEnumerable().Where()比转回DataTable快得多 - 导出Excel或绑定WinForms控件才需要
DataTable,其他场景优先保持List<t></t> - 若必须双向,考虑用
BindingList<t></t>替代,它支持通知且无DataTable的元数据包袱
类型映射不是机械填空,关键在DBNull拦截点、大小写一致性、以及是否真需要保留DataTable那一套行为。很多问题其实出在没想清楚:这个List到底给谁用、要不要再写回数据库、会不会被前端直接JSON序列化——这些决定了你该在哪一层做转换、做多深。










