DataAdapter必须配置SelectCommand才能调用Fill(),否则抛InvalidOperationException;Fill()自动管理连接状态,Update()需预设对应命令且不自动生成SQL。

DataAdapter 不是拿来“new”完就直接用的工具类,它必须配合 SelectCommand(以及可选的 InsertCommand、UpdateCommand、DeleteCommand)才能工作。没设 SelectCommand 就调 Fill(),会直接抛 InvalidOperationException:“SelectCommand property has not been initialized”。
Fill() 必须配 SelectCommand,且 Connection 不必提前打开
Fill() 是最常用也最容易出错的方法。它不关心连接是否已打开——内部会自动判断并按需打开/关闭连接。
- 如果
Connection.State == ConnectionState.Closed,Fill()会自己打开,执行完再关掉 - 如果连接已打开,它不会动状态,用完也不关(避免干扰上层逻辑)
-
SelectCommand的Connection属性必须指向同一个Connection实例,否则运行时报错 - 传入的
DataSet可以为空,Fill()会自动建DataTable;但如果已有同名DataTable,默认行为是追加行(除非设置了主键 + 正确的MissingSchemaAction)
示例片段:
var adapter = new SqlDataAdapter();
adapter.SelectCommand = new SqlCommand("SELECT id, name FROM users", conn);
DataSet ds = new DataSet();
adapter.Fill(ds, "users"); // 自动处理连接,填入名为 "users" 的 DataTable
Update() 前必须确保 Insert/Update/DeleteCommand 已就位
Update() 不是“智能生成 SQL”的魔法方法。它只负责按 DataRow.RowState 分发执行对应命令,不会帮你写语句。
- 对
DataRowState.Added行,调InsertCommand;Modified调UpdateCommand;Deleted调DeleteCommand - 如果某类命令缺失(比如没设
DeleteCommand却有Deleted行),立刻抛异常,不是静默跳过 -
SqlCommandBuilder可自动生成简单 CRUD 命令,但仅限单表、有主键、无计算列或表达式列的场景;一旦用了JOIN或视图,必须手写命令 - 别依赖
Update(DataSet)默认找第一个表——显式传DataTable名称更安全
错误写法:
adapter.Update(ds); // 如果 ds 里有多个表,可能更新错表
推荐写法:
adapter.Update(ds.Tables["users"]); // 明确目标
TableMappings 和 ColumnMappings 用于解决命名冲突
当数据库字段名含空格、大小写混杂、或和 C# 关键字冲突(如 order、user)时,Fill() 后直接绑 UI 或取值会很别扭。映射机制就是为此存在的。
-
TableMappings把查询结果的“源表名”(实际是结果集序号,不是物理表名)映射到DataSet中的DataTable名 -
ColumnMappings把 SELECT 出来的列名(或别名)映射到DataTable的列名,例如SELECT user_name AS name→ 映射为"name"列 - 映射只影响
Fill()和Update()时的列绑定,不影响 SQL 执行本身 - 如果 SQL 里用了
AS别名,ColumnMappings.SourceColumn必须写别名,不是原始列名
常见坑:
adapter.TableMappings.Add("Table", "Users"); // "Table" 是默认结果集名
adapter.ColumnMappings.Add("user_name", "Name"); // 源列名是 SELECT 中写的名
Dispose() 和 using 块不是可选项,尤其在 Web 或长生命周期服务中
DataAdapter 本身托管资源不多,但它持有的 Command、Connection、内部 DataReader 等都是非托管资源密集型对象。不释放会导致连接池耗尽、内存缓慢增长。
-
Dispose()会递归调用其SelectCommand等命令的Dispose(),但不会碰Connection(那是你该管的) - 最佳实践是把
DataAdapter包在using块里,哪怕只用一次Fill() - 不要在类字段里长期持有
DataAdapter实例——它不是线程安全的,重用前必须重置命令和参数 - 若手动调
Dispose(),记得之后别再调Fill()或Update(),否则抛ObjectDisposedException
正确模式:
using (var adapter = new SqlDataAdapter(cmd))
{
adapter.Fill(ds);
} // 这里自动 Dispose,cmd 也被释放
Fill() 和 Update() 看似简单,但每个参数背后都连着连接状态、主键识别、映射优先级、命令生命周期这些隐性链条。最容易被忽略的是:主键缺失时的合并逻辑——没有主键,Fill() 永远只追加不更新;而 Update() 在找不到匹配行时会直接跳过修改,而不是报错。


















