C# DataSet使用 作者:马育民 • 2025-10-25 16:50 • 阅读:10007 # 介绍 `DataSet` 是 `System.Data` 命名空间中的核心类,可理解为“内存中的数据库”,用于以包含多个 `DataTable`、表之间的关系(`DataRelation`)以及约束,支持离线数据处理。以下是 `DataSet` 的详细使用指南,涵盖创建、表管理、关系维护、数据操作等全场景: # 创建与初始化 `DataSet` 本质是一个容器,可容纳多个 `DataTable` 及它们之间的关系,初始化方式如下: ```csharp 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 ```csharp 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 ```csharp // 访问表(通过名称或索引) DataTable dt1 = ds.Tables["学生表"]; // 通过表名(推荐,可读性高) DataTable dt2 = ds.Tables[0]; // 通过索引(0 为第一个添加的表) // 移除表 ds.Tables.Remove("学生表副本"); // 通过表名移除 // 或 ds.Tables.RemoveAt(2); // 通过索引移除 ``` ### 3. 清空数据 ```csharp ds.Clear(); // 清空所有表的数据,但保留表结构和关系 // 若需彻底删除所有表:ds.Tables.Clear(); ``` # 表关系(DataRelation):关联多个 DataTable `DataSet` 的核心优势之一是支持表之间的关系(类似数据库的外键),通过 `DataRelation` 实现,便于多表联合查询。 ### 1. 创建表关系的步骤 ```csharp // 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. 通过关系查询关联数据 ```csharp // 示例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. 跨表添加数据 ```csharp // 向班级表添加新班级 classTable.Rows.Add(3, "三班"); // 向学生表添加三班的学生(外键关联) studentTable.Rows.Add(104, "赵六", 3); ``` ### 2. 修改与删除数据(含关系约束) 若创建关系时启用了约束(`createConstraints: true`),删除主表数据时需先处理子表依赖数据,否则会抛出异常: ```csharp // 若关系启用了约束(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` 集合添加表级约束(如唯一约束、外键约束),与数据库约束类似。 ```csharp // 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 互转 ```csharp // 导出 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) ```csharp // 示例:从 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. 数据版本(查看修改痕迹) ```csharp // 修改一行数据 DataRow row = studentTable.Rows[0]; row["姓名"] = "张三丰"; // 修改后 // 查看原始值和当前值 Console.WriteLine("原始姓名:" + row["姓名", DataRowVersion.Original]); // 张三 Console.WriteLine("当前姓名:" + row["姓名", DataRowVersion.Current]); // 张三丰 // 取消修改(恢复原始值) row.RejectChanges(); ``` ### 2. 事务提交(批量确认修改) ```csharp // 批量修改数据 foreach (DataRow row in studentTable.Rows) { row["姓名"] = row["姓名"] + "_修改"; } // 确认所有修改(对整个 DataSet 生效) ds.AcceptChanges(); // 此时 Original 值更新为 Current 值 // 或取消所有未确认的修改 // ds.RejectChanges(); ``` # 性能优化 1. **批量操作时禁用约束和事件** 大量数据处理时,临时关闭约束和事件提升效率: ```csharp ds.EnforceConstraints = false; // 禁用约束检查 // 执行批量添加/修改操作... ds.EnforceConstraints = true; // 恢复约束检查 ``` 2. **使用 DataView 筛选数据** 对 `DataSet` 中的表创建 `DataView`,支持高效筛选和排序: ```csharp DataView dv = new DataView(studentTable); dv.RowFilter = "班级ID = 1"; // 筛选一班学生 dv.Sort = "学号 ASC"; // 按学号升序 ``` 3. **按需加载表** 避免一次性加载过多表,仅加载当前需要的表和数据。 # 常见异常与解决 | 异常类型 | 可能原因 | 解决方法 | |------------------------|-------------------------------------------|-------------------------------------------| | `DuplicateNameException` | 表名重复或关系名重复 | 确保 `TableName` 和关系名唯一 | | `ConstraintException` | 违反约束(如外键不存在、唯一键重复) | 检查数据是否符合约束,或临时禁用约束 | | `NoNullAllowedException` | 向不允许为空的列插入 null | 确保列值不为 null,或修改 `AllowDBNull` 属性 | | `InvalidConstraintException` | 关系中主表列不是主键或唯一列 | 确保主表关联列是主键或设置了 `Unique = true` | # 总结 `DataSet` 是一个功能完整的内存数据容器,适合以下场景: - 离线数据处理(断开数据库连接后操作) - 多表关联数据管理(如订单-订单明细) - 数据缓存(减少数据库访问) - 复杂数据结构的 XML 序列化/反序列化 通过 `DataTable`、`DataRelation` 和约束的配合,`DataSet` 可模拟数据库的核心功能,是 .NET 中处理结构化数据的重要工具。 原文出处:http://malaoshi.top/show_1GW26dkqOGQz.html