一、基本信息
1、datarelation 的出身
datarelation 是 .net 框架 中 system.data 命名空间下的核心类,用于在内存数据集(dataset)中管理表之间的关系,模拟数据库的外键关联。
来源版本:自 .net framework 1.0(2002 年发布)起就已存在,是 ado.net 技术栈的基础组件之一,在后续的 .net core、.net 5+ 中也完全兼容。
核心作用:在 dataset 中建立表与表之间的关联(如 “订单→订单明细”“部门→员工”),支持关联查询、级联操作和多层级数据结构管理。
本文就是要重点介绍他的数据表之间的级联操作。
2、核心优势
内存级关联:无需访问数据库,直接在 dataset 中完成关联查询,性能高效。
数据完整性:级联操作确保父表变更时子表数据的一致性,避免 “孤儿数据”。
多层级支持:天然适配树形、嵌套结构,如组织架构、分类目录等场景。
综上,datarelation 是 .net 中处理内存数据关联的 “利器”,尤其适合离线数据处理、客户端本地缓存等场景,从基础两表关联到复杂多层级结构都能高效支撑。
二、典型操作
1、操作步骤
初始化数据集与表结构:创建 dataset 并定义参与关联的 datatable(父表和子表)。
定义列与主键,添加数据:为每个表设置列(含主键、外键),并插入初始数据。
创建 datarelation:通过 datarelation 构造函数指定关系名、父表主键列、子表外键列。
执行关联操作:利用 getchildrows、getparentrow 进行关联查询,或通过级联操作维护数据完整性。
2、典型代码实现
步骤1:初始化数据集与表结构
using system;
using system.data;
class program
{
static void main()
{
// 1. 初始化 dataset
dataset ds = new dataset("orderdb");
// 2. 定义父表(订单表)
datatable dtorders = new datatable("orders");
// 定义子表(订单明细表)
datatable dtorderdetails = new datatable("orderdetails");步骤2:定义列、主键并添加数据
// 配置订单表列与主键
dtorders.columns.add("orderid", typeof(int));
dtorders.columns.add("orderdate", typeof(datetime));
dtorders.primarykey = new[] { dtorders.columns["orderid"] }; // 设置主键
// 配置订单明细表列与外键
dtorderdetails.columns.add("detailid", typeof(int));
dtorderdetails.columns.add("orderid", typeof(int)); // 外键,关联 orders.orderid
dtorderdetails.columns.add("productname", typeof(string));
dtorderdetails.columns.add("quantity", typeof(int));
// 向表中添加数据
dtorders.rows.add(1, datetime.now);
dtorders.rows.add(2, datetime.now.adddays(-1));
dtorderdetails.rows.add(101, 1, "手机", 2);
dtorderdetails.rows.add(102, 1, "耳机", 1);
dtorderdetails.rows.add(103, 2, "平板", 1);
// 将表添加到 dataset
ds.tables.add(dtorders);
ds.tables.add(dtorderdetails);步骤3:创建 datarelation 建立关联
// 创建订单与订单明细的关系
datarelation orderdetailrel = new datarelation(
"order_detail_relation", // 关系名
dtorders.columns["orderid"], // 父表主键列
dtorderdetails.columns["orderid"], // 子表外键列
true, true // 启用级联删除和级联更新
);
ds.relations.add(orderdetailrel);
步骤4:执行关联操作(查询、级联删除)
// ① 关联查询:查询订单id=1的所有明细
datarow orderrow = dtorders.rows.find(1); // 通过主键查找订单
datarow[] detailrows = orderrow.getchildrows(orderdetailrel);
console.writeline("订单1的明细:");
foreach (datarow detail in detailrows)
{
console.writeline($"- 商品:{detail["productname"]},数量:{detail["quantity"]}");
}
// ② 级联删除:删除订单后,其明细自动删除
orderrow.delete(); // 删除订单id=1
int remainingdetails = dtorderdetails.select("orderid = 1").length;
console.writeline($"订单1删除后,剩余明细数量:{remainingdetails}"); // 结果为0
// ③ 级联更新:修改订单id后,明细的orderid自动同步
datarow orderrow2 = dtorders.rows.find(2);
orderrow2["orderid"] = 200; // 将订单id从2改为200
datarow[] updateddetails = dtorderdetails.select("orderid = 200");
console.writeline($"订单id更新后,关联明细数量:{updateddetails.length}"); // 结果为1
}
}代码说明
- 步骤1-2:完成了数据集、表结构的定义和初始数据的填充,为关联操作奠定基础。
- 步骤3:通过
datarelation明确了“订单→订单明细”的关联关系,并启用了级联删除和更新,确保数据一致性。 - 步骤4:演示了关联查询(父查子)、级联删除、级联更新三种典型操作,体现了
datarelation在简化多层级数据处理中的优势。
直接运行上述代码即可看到关联操作的效果,可根据实际需求调整表结构、数据和操作逻辑。
三、有关find函数的说明
在代码的第二步中,我们注意到dtorders.rows.find(1) 的作用是“在主键中寻找键值为1的行”。为什么find只找主键而不找其他的键或列呢?
之所以只会查找主键列中值为1的行,而不是其他列,核心原因是 find 方法是.net中专门为“主键查询”设计的,其逻辑严格依赖于 datatable 中预设的 primarykey(主键)配置。
关键原理:find方法与主键的强绑定
primarykey的预配置- 在步骤2中,我们通过以下代码为
dtorders(订单表)设置了主键: dtorders.primarykey = new[] { dtorders.columns["orderid"] };- 这行代码明确告诉
datatable:orderid列是当前表的主键列。主键的特性是:唯一标识一行数据,且值不可重复。 find方法的查询逻辑datarowcollection.find(object key)方法的内部逻辑是:- 简单说:
find方法是“主键专属查询工具”,它完全无视其他非主键列,哪怕其他列(如假设的otherid列)也有值为1的行,find也不会去匹配。
只搜索 datatable 中被标记为 primarykey 的列(此处即 orderid 列)。
匹配传入的 key 值(此处为 1)与主键列中的值,返回第一个匹配的行。
举例验证:其他列有值为1时,find仍只认主键
假设我们给订单表增加一个非主键列 otherid,并插入一行 otherid=1 的数据:
// 给订单表添加一个非主键列
dtorders.columns.add("otherid", typeof(int));
// 插入数据:orderid=3(主键),otherid=1(非主键)
dtorders.rows.add(3, datetime.now, 1);
// 用find(1)查询
datarow foundrow = dtorders.rows.find(1);
// 结果:foundrow 会指向 orderid=1 的行(而非 otherid=1 的行)
console.writeline($"找到的行:orderid={foundrow["orderid"]}, otherid={foundrow["otherid"]}");输出结果会是 orderid=1 的行(假设其 otherid 可能为 null 或其他值),因为 find 只看主键列 orderid。
对比:如果想查询非主键列,该用什么?
如果需要查询非主键列(如 otherid=1),不能用 find,而应使用 select 方法(基于条件筛选):
// 查询非主键列 otherid=1 的行
datarow[] rows = dtorders.select("otherid = 1");
select 方法会扫描所有列,根据条件筛选,不依赖主键配置,这也是它与 find 的核心区别。
总结
dtorders.rows.find(1) 之所以精准定位到“主键值为1的行”,是因为:
- 表已通过
primarykey明确orderid为主键列; find方法的设计逻辑就是仅搜索主键列,与其他列无关。
这种机制保证了find方法的高效性(基于主键索引)和精准性(唯一标识一行),是datatable中主键查询的最优方式。
四、关于getchildrows的使用
datarow.getchildrows 是 datarow 类中用于查询“关联子表行”的核心方法,专门配合 datarelation 使用,能够快速获取当前父表行在子表中对应的所有关联数据行。以下是详细介绍:
1、getchildrows定义和功能
public datarow[] getchildrows(datarelation relation);
- 作用:根据指定的
datarelation(表关系),查询与当前datarow(父表行)相关联的所有子表行。 - 参数:
datarelation对象(需提前定义父表与子表的关联规则)。 - 返回值:
datarow[]数组,包含所有匹配的子表行;若没有关联行,返回空数组(非null)。
2、核心工作原理
getchildrows 的逻辑完全依赖 datarelation 中定义的关联规则,步骤如下:
- 读取关联规则:从传入的
datarelation中获取父表主键列(如orders.orderid)和子表外键列(如orderdetails.orderid)。 - 提取当前父行的主键值:获取当前
datarow(父行)中主键列的值(例如订单1的orderid=1)。 - 自动匹配子表行:在子表中筛选出“外键列值 = 父行主键值”的所有行(例如
orderdetails中orderid=1的所有明细)。 - 返回结果:将筛选出的子表行封装为数组返回。
3、关键特性
- 依赖
datarelation - 必须先定义有效的
datarelation,否则会抛出argumentexception(例如关联的表或列不存在)。 - 无需手动写筛选条件
- 对比无
datarelation时的手动筛选(如dtorderdetails.select("orderid=1")),getchildrows完全通过datarelation自动处理关联逻辑,避免硬编码条件,减少错误。 - 支持多层级关联
- 对于“父→子→孙”的多层级结构(如“公司→部门→员工”),可嵌套调用
getchildrows:
// 公司→部门(第一层子级)
datarow[] depts = companyrow.getchildrows(compdeptrel);
// 部门→员工(第二层子级)
foreach (var dept in depts)
{
datarow[] emps = dept.getchildrows(deptemprel);
}
- 性能高效
- 内部基于
datarelation的关联信息优化查询,比手动遍历或select方法(全表扫描)更高效,尤其数据量大时差异明显。
4、实例解析(结合订单场景)
在之前的“订单→订单明细”例子中:
// 父行:订单id=1的订单行 datarow orderrow = dtorders.rows.find(1); // 获取该订单的所有明细行 datarow[] detailrows = orderrow.getchildrows(orderdetailrel);
orderdetailrel定义了关联规则:orders.orderid(父主键)→orderdetails.orderid(子外键)。getchildrows自动提取orderrow的orderid=1,然后在orderdetails中筛选出所有orderid=1的行,最终返回这两行明细(手机、耳机)。
五、datarelation关于增删改查的总结
结合1~4步的实例代码,datarelation 在增删改查(crud)操作中相比“无关联手动处理”的方式,核心优势体现在 “简化关联逻辑”“保障数据完整性”“提升开发效率” 三个方面,具体如下:
1、查询(read):无需手动关联,多层级查询更简洁
- 有
datarelation时: - 通过
getchildrows直接基于预设关系查询子表数据,无需手动提取父表主键、写筛选条件。 - 例:查询订单1的所有明细,仅需两行代码:
datarow orderrow = dtorders.rows.find(1); // 找父行 datarow[] detailrows = orderrow.getchildrows(orderdetailrel); // 直接获取子行
- 逻辑清晰,且多层级场景(如“公司→部门→员工”)可通过嵌套
getchildrows轻松实现,无需重复写筛选逻辑。 - 无
datarelation时: - 需手动提取父表主键值,再用
select方法写条件筛选子表,代码冗余且易出错:
datarow orderrow = dtorders.rows.find(1);
int orderid = (int)orderrow["orderid"]; // 手动提取主键
datarow[] detailrows = dtorderdetails.select($"orderid = {orderid}"); // 手动写筛选条件
2、新增(create):关联关系自动维护,减少人为错误
- 有
datarelation时: - 新增子表数据时,只需设置外键值(如订单明细的
orderid),datarelation会自动关联到对应的父表行,无需额外逻辑。 - 例:新增订单明细时,只需确保
orderid正确,无需手动验证是否存在对应订单:
dtorderdetails.rows.add(104, 1, "充电器", 3); // 直接设置orderid=1(关联订单1)
- 无
datarelation时: - 虽然新增步骤类似,但需开发者手动确保外键值与父表主键匹配(如检查订单1是否存在),否则会产生“无效关联数据”(如明细的
orderid=999但无对应订单),增加数据不一致风险。
3、修改(update):级联更新自动同步,避免关联断裂
- 有
datarelation时: - 启用级联更新后,修改父表主键值,子表外键会自动同步,无需手动遍历子表修改。
- 例:将订单2的
orderid从2改为200,其关联明细的orderid自动变为200:
orderrow2["orderid"] = 200; // 父表主键修改 // 子表明细的orderid自动同步为200(无需手动操作)
- 无
datarelation时: - 需手动查询所有关联子表行,逐个修改外键值,步骤繁琐且易遗漏:
orderrow2["orderid"] = 200; datarow[] details = dtorderdetails.select($"orderid = 2"); // 手动查关联子行 foreach (var d in details) d["orderid"] = 200; // 手动逐个修改
4、删除(delete):级联删除自动清理,防止孤儿数据
- 有
datarelation时: - 启用级联删除后,删除父表行时,所有关联子表行会被自动删除,避免“孤儿数据”(子表存在但无对应父表行)。
- 例:删除订单1,其关联的所有明细自动删除:
orderrow.delete(); // 删除父行(订单1) // 子表明细自动删除(无需手动操作)
- 无
datarelation时: - 需先手动查询并删除所有关联子表行,再删除父表行,步骤多且易因遗漏导致孤儿数据:
datarow[] details = dtorderdetails.select($"orderid = 1"); // 手动查子行 foreach (var d in details) d.delete(); // 手动删子行 orderrow.delete(); // 再删父行
六、总结:datarelation的核心价值
- 逻辑解耦:关联规则(如“订单id关联”)集中定义在
datarelation中,而非分散在增删改查的代码里,便于维护。 - 自动化处理:级联更新/删除、关联查询等操作由框架自动完成,减少手动编码量和出错概率。
- 数据一致性:通过约束和级联操作,天然避免“无效关联”“孤儿数据”等问题,保障内存中数据集的完整性。
对于多层级数据(如“组织架构”“分类目录”),datarelation 的优势会被进一步放大,是 .net 中处理内存关联数据的最优方案。
到此这篇关于c#数据级联操作的法宝datarelation(操作步骤)的文章就介绍到这了,更多相关c#数据级联datarelation内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论