DataSet 是内存中的关系型数据快照,不连接数据库、不自动同步、无事务能力,所有操作仅限本地内存,需显式调用 SqlDataAdapter.Update() 才能写回数据库。

DataSet 是内存里的关系型数据快照,不是数据库连接代理
很多人一看到 DataSet 就默认它会自动同步数据库、支持实时查询或带事务能力——其实它完全不碰网络、不连数据库、也不执行 SQL。它只是把查出来的结果(比如从 SqlDataAdapter.Fill() 得到的数据)用表格形式存进内存,所有增删改都在本地发生,和数据库毫无关系。
常见错误现象:DataTable.Rows.Add() 后直接关程序,发现数据库没变;或者调用 DataSet.AcceptChanges() 以为“提交了”,结果数据库还是老样子。
-
DataSet和DataTable都是纯内存对象,生命周期只在当前作用域内 - 要写回数据库,必须显式调用
SqlDataAdapter.Update(),且依赖InsertCommand/UpdateCommand/DeleteCommand正确配置 - 没有自动主键/外键约束校验(除非手动设
DataTable.Constraints),DataRow.RowState才反映变更状态
Fill() 之后的 DataTable 并不“自动跟踪”原始 SQL 变化
SqlDataAdapter.Fill() 把结果集复制进 DataTable,但这个过程是一次性快照。后续数据库表被其他程序修改,DataSet 完全无感——它不会轮询、不会监听、也不会触发任何事件。
使用场景:适合离线编辑、批量导入导出、报表预处理等不需要强一致性的环节;不适合做高并发协作编辑的实时数据视图。
- 想“刷新”数据?只能重新
Fill(),但要注意清空旧数据(DataTable.Clear()或新建DataTable) -
Fill()默认不会覆盖原有行,而是追加;若需替换,请传入LoadOption.OverwriteChanges - 如果原 SQL 带参数,每次
Fill()都得重设SqlDataAdapter.SelectCommand.Parameters,否则用的是上次值
Update() 失败十有八九是因为 Command 没配对或主键丢失
SqlDataAdapter.Update() 不是魔法函数,它靠你提前配好的 InsertCommand、UpdateCommand、DeleteCommand 来生成对应 SQL。一旦缺一个,或者参数名/顺序/类型对不上,就会抛 InvalidOperationException 或静默失败。
典型错误信息:"Dynamic SQL generation for the UpdateCommand is not supported" —— 这说明没手动设命令,又没开自动生成(且表无主键)。
- 必须确保
DataTable.PrimaryKey已设置(通常是数据库主键列),否则UpdateCommand无法定位要改哪一行 - 自动生成命令(
SqlCommandBuilder)只支持单表、简单 SELECT,且要求 SELECT 包含所有字段(不能用*在视图上) - 手动写的
UpdateCommand中,WHERE 子句必须用原始值比对(WHERE id = @original_id),否则可能误更新其他行
DataSet 序列化时默认不包含 Schema,跨进程容易丢结构
用 DataSet.WriteXml() 或 JSON 序列化时,默认只写数据(WriteXmlMode.IgnoreSchema),不保存列类型、主键、关系、约束等元信息。反序列化回来就是一个空壳 DataSet,DataTable.Columns 是空的,甚至根本读不进数据。
性能影响:带 Schema 的 XML 体积大、解析慢;但不带 Schema,就失去类型安全和结构一致性。
- 必须显式指定
WriteXmlMode.WriteSchema,否则ReadXml()会失败或行为异常 - .NET Core / .NET 5+ 默认禁用
DataSet的二进制序列化(BinaryFormatter已废弃),别再依赖SoapFormatter或自定义二进制流 - Web API 返回
DataSet是反模式——JSON.NET 对它的支持脆弱,字段名大小写、嵌套层级、空值处理都容易出问题
最常被忽略的一点:DataSet 的线程安全性仅限于只读访问。只要有一处调用了 DataTable.Rows.Add() 或 DataRow.SetColumn(),整个 DataSet 就不能再被其他线程并发读——它内部没有锁,也没有不可变设计。多线程场景下,要么加 lock,要么换 DataTable.AsEnumerable() + LINQ 做只读投影,别试图边改边查。










