介绍
DataSet 是 System.Data 命名空间中的核心类,可理解为“内存中的数据库”,用于以包含多个 DataTable、表之间的关系(DataRelation)以及约束,支持离线数据处理。以下是 DataSet 的详细使用指南,涵盖创建、表管理、关系维护、数据操作等全场景:
创建与初始化
DataSet 本质是一个容器,可容纳多个 DataTable 及它们之间的关系,初始化方式如下:
using System;
using System.Data;
// 1. 无参构造
DataSet ds1 = new DataSet();
// 2. 指定数据集名称
DataSet ds2 = new DataSet("学校数据集");
// 3. 指定名称和命名空间(用于 XML 序列化)
DataSet ds3 = new DataSet("学校数据集", "http://example.com/school");
管理 DataTable(添加、移除、复制)
DataSet 通过 Tables 集合管理内部的 DataTable,支持添加、移除、查询等操作。
1. 添加 DataTable
DataSet ds = new DataSet("学生数据集");
// 方法1:先创建 DataTable,再添加到 DataSet
DataTable studentTable = new DataTable("学生表");
studentTable.Columns.Add("学号", typeof(int));
studentTable.Columns.Add("姓名", typeof(string));
ds.Tables.Add(studentTable);
// 方法2:直接在 DataSet 中创建表(通过 Tables.Add 重载)
DataTable classTable = ds.Tables.Add("班级表"); // 直接添加并命名
classTable.Columns.Add("班级ID", typeof(int));
classTable.Columns.Add("班级名称", typeof(string));
// 方法3:复制现有表到 DataSet(包含结构和数据)
DataTable studentCopy = studentTable.Copy();
studentCopy.TableName = "学生表副本"; // 表名必须唯一
ds.Tables.Add(studentCopy);
2. 访问与移除 DataTable
// 访问表(通过名称或索引)
DataTable dt1 = ds.Tables["学生表"]; // 通过表名(推荐,可读性高)
DataTable dt2 = ds.Tables[0]; // 通过索引(0 为第一个添加的表)
// 移除表
ds.Tables.Remove("学生表副本"); // 通过表名移除
// 或 ds.Tables.RemoveAt(2); // 通过索引移除
3. 清空数据
ds.Clear(); // 清空所有表的数据,但保留表结构和关系
// 若需彻底删除所有表:ds.Tables.Clear();
表关系(DataRelation):关联多个 DataTable
DataSet 的核心优势之一是支持表之间的关系(类似数据库的外键),通过 DataRelation 实现,便于多表联合查询。
1. 创建表关系的步骤
// 1. 准备主表和子表(假设已添加到 DataSet)
DataTable classTable = ds.Tables["班级表"]; // 主表:班级
DataTable studentTable = ds.Tables["学生表"]; // 子表:学生
// 2. 为主表添加主键(关系依赖主键)
classTable.PrimaryKey = new DataColumn[] { classTable.Columns["班级ID"] };
// 3. 子表添加外键列(与主表主键关联)
studentTable.Columns.Add("班级ID", typeof(int)); // 学生表的外键
// 4. 填充测试数据
classTable.Rows.Add(1, "一班");
classTable.Rows.Add(2, "二班");
studentTable.Rows.Add(101, "张三", 1); // 张三属于一班
studentTable.Rows.Add(102, "李四", 2); // 李四属于二班
studentTable.Rows.Add(103, "王五", 1); // 王五属于一班
// 5. 创建关系(主表列 -> 子表列)
DataRelation classStudentRelation = new DataRelation(
"班级_学生关系", // 关系名称(唯一)
classTable.Columns["班级ID"], // 主表关联列(必须是主键或唯一列)
studentTable.Columns["班级ID"], // 子表关联列(外键)
false // 是否创建约束(true 则子表外键必须存在于主表,类似数据库外键约束)
);
// 6. 将关系添加到 DataSet
ds.Relations.Add(classStudentRelation);
2. 通过关系查询关联数据
// 示例1:通过主表行获取子表所有关联行(如“一班”的所有学生)
DataRow classRow = classTable.Rows.Find(1); // 查找“一班”
// 获取子表关联行(通过关系名)
DataRow[] studentsInClass = classRow.GetChildRows("班级_学生关系");
foreach (DataRow studentRow in studentsInClass)
{
Console.WriteLine($"学生:{studentRow["姓名"]},学号:{studentRow["学号"]}");
}
// 输出:
// 学生:张三,学号:101
// 学生:王五,学号:103
// 示例2:通过子表行获取主表关联行(如“张三”所属的班级)
DataRow studentRow = studentTable.Rows.Find(101); // 查找“张三”
// 获取主表关联行
DataRow classOfStudent = studentRow.GetParentRow("班级_学生关系");
Console.WriteLine($"张三所属班级:{classOfStudent["班级名称"]}"); // 输出:一班
数据操作(添加、修改、删除)
DataSet 中的数据操作本质是对内部 DataTable 的行操作,与 DataTable 的行操作一致,但可跨表联动。
1. 跨表添加数据
// 向班级表添加新班级
classTable.Rows.Add(3, "三班");
// 向学生表添加三班的学生(外键关联)
studentTable.Rows.Add(104, "赵六", 3);
2. 修改与删除数据(含关系约束)
若创建关系时启用了约束(createConstraints: true),删除主表数据时需先处理子表依赖数据,否则会抛出异常:
// 若关系启用了约束(createConstraints: true),直接删除主表行会报错
// 需先删除子表关联数据,再删除主表行:
DataRow classRow = classTable.Rows.Find(3); // 三班
// 1. 先删除三班的所有学生(子表数据)
foreach (DataRow studentRow in classRow.GetChildRows("班级_学生关系"))
{
studentRow.Delete();
}
// 2. 再删除三班(主表数据)
classRow.Delete();
// 确认所有删除(对 DataSet 中所有表生效)
ds.AcceptChanges();
约束管理(确保数据完整性)
DataSet 支持通过 Constraints 集合添加表级约束(如唯一约束、外键约束),与数据库约束类似。
// 1. 为学生表添加唯一约束(学号不可重复)
UniqueConstraint uc = new UniqueConstraint("学号唯一", studentTable.Columns["学号"]);
studentTable.Constraints.Add(uc);
// 2. 为班级表添加检查约束(班级名称不为空)
CheckConstraint cc = new CheckConstraint("班级名称非空", "班级名称 IS NOT NULL");
classTable.Constraints.Add(cc);
// 若添加违反约束的数据,会抛出 ConstraintException
导入与导出数据(XML/数据库)
DataSet 支持与 XML 互转,也可通过数据适配器(DataAdapter)与数据库交互。
1. 与 XML 互转
// 导出 DataSet 到 XML(包含结构和数据)
ds.WriteXml("school_data.xml");
// 仅导出结构(XML Schema)
ds.WriteXmlSchema("school_schema.xml");
// 从 XML 导入数据到 DataSet
DataSet dsFromXml = new DataSet();
dsFromXml.ReadXml("school_data.xml"); // 自动创建表结构并填充数据
2. 与数据库交互(通过 DataAdapter)
// 示例:从 SQL Server 加载数据到 DataSet
using System.Data.SqlClient;
string connectionString = "Server=.;Database=TestDB;Integrated Security=True;";
string sql = "SELECT * FROM 班级表; SELECT * FROM 学生表;"; // 一次查询多表
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlDataAdapter adapter = new SqlDataAdapter(sql, conn);
DataSet dsDB = new DataSet();
adapter.Fill(dsDB); // 自动将多表结果填充到 DataSet(表名默认 Table、Table1...)
// 重命名表(可选)
dsDB.Tables[0].TableName = "班级表";
dsDB.Tables[1].TableName = "学生表";
}
// 示例:将 DataSet 修改后的数据更新到数据库
adapter.Update(dsDB); // 自动提交新增/修改/删除的数据
高级功能:事务与版本控制
DataSet 内部维护数据的版本信息(原始值、当前值),支持事务性提交。
1. 数据版本(查看修改痕迹)
// 修改一行数据
DataRow row = studentTable.Rows[0];
row["姓名"] = "张三丰"; // 修改后
// 查看原始值和当前值
Console.WriteLine("原始姓名:" + row["姓名", DataRowVersion.Original]); // 张三
Console.WriteLine("当前姓名:" + row["姓名", DataRowVersion.Current]); // 张三丰
// 取消修改(恢复原始值)
row.RejectChanges();
2. 事务提交(批量确认修改)
// 批量修改数据
foreach (DataRow row in studentTable.Rows)
{
row["姓名"] = row["姓名"] + "_修改";
}
// 确认所有修改(对整个 DataSet 生效)
ds.AcceptChanges(); // 此时 Original 值更新为 Current 值
// 或取消所有未确认的修改
// ds.RejectChanges();
性能优化
批量操作时禁用约束和事件
大量数据处理时,临时关闭约束和事件提升效率:ds.EnforceConstraints = false; // 禁用约束检查 // 执行批量添加/修改操作... ds.EnforceConstraints = true; // 恢复约束检查使用 DataView 筛选数据
对DataSet中的表创建DataView,支持高效筛选和排序:DataView dv = new DataView(studentTable); dv.RowFilter = "班级ID = 1"; // 筛选一班学生 dv.Sort = "学号 ASC"; // 按学号升序按需加载表
避免一次性加载过多表,仅加载当前需要的表和数据。
常见异常与解决
| 异常类型 | 可能原因 | 解决方法 |
|---|---|---|
DuplicateNameException |
表名重复或关系名重复 | 确保 TableName 和关系名唯一 |
ConstraintException |
违反约束(如外键不存在、唯一键重复) | 检查数据是否符合约束,或临时禁用约束 |
NoNullAllowedException |
向不允许为空的列插入 null | 确保列值不为 null,或修改 AllowDBNull 属性 |
InvalidConstraintException |
关系中主表列不是主键或唯一列 | 确保主表关联列是主键或设置了 Unique = true |
总结
DataSet 是一个功能完整的内存数据容器,适合以下场景:
- 离线数据处理(断开数据库连接后操作)
- 多表关联数据管理(如订单-订单明细)
- 数据缓存(减少数据库访问)
- 复杂数据结构的 XML 序列化/反序列化
通过 DataTable、DataRelation 和约束的配合,DataSet 可模拟数据库的核心功能,是 .NET 中处理结构化数据的重要工具。