C# DataSet使用

介绍

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();

性能优化

  1. 批量操作时禁用约束和事件
    大量数据处理时,临时关闭约束和事件提升效率:

    ds.EnforceConstraints = false; // 禁用约束检查
    // 执行批量添加/修改操作...
    ds.EnforceConstraints = true; // 恢复约束检查
    
  2. 使用 DataView 筛选数据
    对 DataSet 中的表创建 DataView,支持高效筛选和排序:

    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 中处理结构化数据的重要工具。