在 C# 的ADO.NET中,DataSet是一个核心的内存中数据缓存对象,用于离线存储和处理数据,常被称为 "内存中的数据库"。它可以包含多个数据表、表之间的关系、约束等,独立于数据源(如 SQL Server、Oracle 等),非常适合需要在客户端维护复杂数据关系或离线操作的场景。
一、DataSet 核心概念
DataSet本质是一个离线数据容器,主要特点:
-
包含一个或多个
DataTable(数据表),表结构与数据库表类似 -
支持
DataRelation(表关系),可定义表之间的关联(如主外键关系) -
自带数据架构信息(
Schema),记录字段类型、约束等元数据 -
独立于数据源,断开连接后仍可操作数据,适合离线场景
-
提供数据修改跟踪(通过
RowState),便于批量更新回数据源
二、DataSet 相关核心类
DataSet的功能依赖于一系列相关类协同工作,形成完整的内存数据模型:
| 类名 | 作用描述 |
|---|---|
DataSet |
顶层容器,包含多个DataTable、DataRelation和约束,管理整体数据状态 |
DataTable |
数据表,对应数据库中的表,包含DataColumn(列)和DataRow(行) |
DataColumn |
表中的列,定义数据类型、默认值、约束(主键、唯一键等) |
DataRow |
表中的行,存储具体数据,提供数据修改、状态跟踪(新增 / 修改 / 删除)功能 |
DataRelation |
定义两个DataTable之间的关系(如父子表),支持关联数据导航 |
DataView |
DataTable的视图,用于排序、筛选、搜索数据,可绑定到 UI 控件 |
DataAdapter |
数据适配器,负责DataSet与数据源之间的同步(填充数据、更新修改) |
三、核心类详解与使用示例
1. DataSet 与 DataTable
DataSet是容器,DataTable是其中的表,一个DataSet可包含多个DataTable。
示例:创建 DataSet 和 DataTable
// 1. 创建DataSet
DataSet dataSet = new DataSet("SalesDB"); // 指定数据集名称
// 2. 创建DataTable(客户表)
DataTable customerTable = new DataTable("Customers");
// 3. 定义DataColumn(列)
DataColumn idCol = new DataColumn("Id", typeof(int));
idCol.AllowDBNull = false; // 不允许为空
idCol.Unique = true; // 唯一约束
customerTable.Columns.Add(idCol);
// 设置主键(可多个列组成复合主键)
customerTable.PrimaryKey = new[] { idCol };
// 添加其他列
customerTable.Columns.Add("Name", typeof(string));
customerTable.Columns.Add("RegisterDate", typeof(DateTime));
// 4. 向DataTable添加DataRow(行数据)
DataRow row1 = customerTable.NewRow();
row1["Id"] = 1;
row1["Name"] = "张三";
row1["RegisterDate"] = new DateTime(2023, 1, 15);
customerTable.Rows.Add(row1);
DataRow row2 = customerTable.NewRow();
row2["Id"] = 2;
row2["Name"] = "李四";
row2["RegisterDate"] = new DateTime(2023, 3, 20);
customerTable.Rows.Add(row2);
// 5. 将DataTable添加到DataSet
dataSet.Tables.Add(customerTable);
// 验证:输出表信息
Console.WriteLine($"数据集名称:{dataSet.DataSetName}");
Console.WriteLine($"包含表数量:{dataSet.Tables.Count}");
Console.WriteLine($"客户表行数:{dataSet.Tables["Customers"].Rows.Count}");
2. DataRow:操作行数据
DataRow存储具体数据,提供数据访问、修改和状态跟踪功能。
示例:操作 DataRow
// 获取前面创建的客户表
DataTable customers = dataSet.Tables["Customers"];
// 1. 访问行数据
DataRow firstRow = customers.Rows[0];
Console.WriteLine($"第一个客户:{firstRow["Name"]}(ID:{firstRow["Id"]})");
// 2. 修改数据
firstRow["Name"] = "张三三"; // 修改名称
Console.WriteLine($"修改后名称:{firstRow["Name"]}");
// 3. 新增行(简化写法)
customers.Rows.Add(3, "王五", new DateTime(2023, 5, 10));
// 4. 删除行(标记删除,需调用AcceptChanges才会真正删除)
DataRow rowToDelete = customers.Rows.Find(2); // 通过主键查找
if (rowToDelete != null)
rowToDelete.Delete();
// 5. 查看行状态(新增/修改/删除/未变)
foreach (DataRow row in customers.Rows)
{
Console.WriteLine($"ID: {row["Id"]}, 状态: {row.RowState}");
}
// 输出:
// ID: 1, 状态: Modified(已修改)
// ID: 2, 状态: Deleted(已删除)
// ID: 3, 状态: Added(新增)
// 6. 提交/撤销修改
customers.AcceptChanges(); // 提交所有修改(删除标记行将被移除)
// customers.RejectChanges(); // 撤销所有修改(恢复到上次Accept状态)
3. DataRelation:表关系
DataRelation用于定义两个DataTable之间的关联(如订单表与客户表的 "一对多" 关系),支持通过父行查找子行。
示例:创建表关系
// 1. 创建订单表
DataTable orderTable = new DataTable("Orders");
orderTable.Columns.Add("OrderId", typeof(int));
orderTable.Columns.Add("CustomerId", typeof(int)); // 外键(关联Customers表的Id)
orderTable.Columns.Add("Amount", typeof(decimal));
orderTable.PrimaryKey = new[] { orderTable.Columns["OrderId"] };
// 添加订单数据
orderTable.Rows.Add(1001, 1, 999.99m); // 客户1的订单
orderTable.Rows.Add(1002, 1, 1599.50m); // 客户1的另一个订单
orderTable.Rows.Add(1003, 3, 599.00m); // 客户3的订单
dataSet.Tables.Add(orderTable);
// 2. 创建关系(客户表 -> 订单表:一对多)
DataRelation customerOrderRel = new DataRelation(
"Customer_Order", // 关系名称
dataSet.Tables["Customers"].Columns["Id"], // 父表主键
dataSet.Tables["Orders"].Columns["CustomerId"] // 子表外键
);
dataSet.Relations.Add(customerOrderRel);
// 3. 通过关系导航数据(查找客户1的所有订单)
DataRow customer = dataSet.Tables["Customers"].Rows.Find(1);
if (customer != null)
{
// 获取子行(订单)
DataRow[] orders = customer.GetChildRows(customerOrderRel);
Console.WriteLine($"客户 {customer["Name"]} 的订单数量:{orders.Length}");
foreach (DataRow order in orders)
{
Console.WriteLine($"订单ID:{order["OrderId"]},金额:{order["Amount"]}");
}
}
// 4. 通过子行查找父行(查找订单1001的客户)
DataRow order = dataSet.Tables["Orders"].Rows.Find(1001);
if (order != null)
{
DataRow parentCustomer = order.GetParentRow(customerOrderRel);
Console.WriteLine($"订单1001的客户:{parentCustomer["Name"]}");
}
4. DataView:数据视图
DataView是DataTable的可定制视图,支持排序、筛选、搜索,常用于 UI 绑定(如DataGridView)。
示例:使用 DataView 筛选和排序
// 获取订单表
DataTable orders = dataSet.Tables["Orders"];
// 1. 创建DataView(默认包含所有行)
DataView orderView = new DataView(orders);
// 2. 筛选:只显示金额>1000的订单
orderView.RowFilter = "Amount > 1000";
// 3. 排序:按金额降序
orderView.Sort = "Amount DESC";
// 4. 遍历视图数据
Console.WriteLine("筛选并排序后的订单:");
foreach (DataRowView rowView in orderView)
{
Console.WriteLine($"订单ID:{rowView["OrderId"]},金额:{rowView["Amount"]}");
}
// 5. 绑定到UI控件(如WinForm的DataGridView)
// dataGridView1.DataSource = orderView;
5. DataAdapter:同步数据源
DataAdapter是DataSet与数据源之间的桥梁,负责将数据源数据填充到DataSet,并将DataSet的修改更新回数据源。
示例:用 DataAdapter 填充和更新数据
string connectionString = "Server=.;Database=SalesDB;Integrated Security=True;";
// 1. 创建DataAdapter(关联查询命令)
using (SqlDataAdapter adapter = new SqlDataAdapter())
{
// 查询命令(用于填充数据)
adapter.SelectCommand = new SqlCommand(
"SELECT Id, Name, RegisterDate FROM Customers",
new SqlConnection(connectionString)
);
// 更新命令(用于将修改同步回数据库,可自动生成或手动指定)
SqlCommandBuilder cmdBuilder = new SqlCommandBuilder(adapter); // 自动生成增删改命令
// 2. 填充DataSet
DataSet dbDataSet = new DataSet();
adapter.Fill(dbDataSet, "Customers"); // 填充到DataSet的"Customers"表
// 3. 修改数据(在内存中)
DataTable dbCustomers = dbDataSet.Tables["Customers"];
dbCustomers.Rows[0]["Name"] = "张三更新"; // 修改
dbCustomers.Rows.Add(4, "赵六", DateTime.Now); // 新增
dbCustomers.Rows[1].Delete(); // 删除
// 4. 将修改更新回数据库(根据RowState批量执行增删改)
int updatedRows = adapter.Update(dbDataSet, "Customers");
Console.WriteLine($"成功更新 {updatedRows} 条记录到数据库");
}
四、DataSet 与相关类的关系总结
-
层级关系 :
DataSet包含DataTable,DataTable包含DataColumn和DataRow,DataRelation关联多个DataTable。 -
数据流转 :
DataAdapter从数据源读取数据填充DataSet,修改后再通过DataAdapter更新回数据源。 -
UI 交互 :
DataView作为DataTable的视图,简化 UI 绑定和数据展示。
五、使用场景与注意事项
-
适用场景:离线数据处理、复杂数据关系维护(多表关联)、需要缓存数据的客户端应用。
-
局限性 :内存占用较大,不适合处理超大量数据(此时推荐
DataReader);离线操作可能导致数据一致性问题(需处理并发冲突)。 -
性能建议 :只加载需要的数据;避免频繁创建
DataSet;批量更新时使用DataAdapter.Update而非逐条操作。
通过DataSet及其相关类,开发者可以在内存中构建完整的关系型数据模型,灵活实现离线数据处理和复杂业务逻辑。