C#數(shù)據(jù)級聯(lián)操作的法寶DataRelation詳解(操作步驟)
一、基本信息
1、DataRelation 的出身
DataRelation 是 .NET 框架 中 System.Data 命名空間下的核心類,用于在內(nèi)存數(shù)據(jù)集(DataSet)中管理表之間的關(guān)系,模擬數(shù)據(jù)庫的外鍵關(guān)聯(lián)。
來源版本:自 .NET Framework 1.0(2002 年發(fā)布)起就已存在,是 ADO.NET 技術(shù)棧的基礎(chǔ)組件之一,在后續(xù)的 .NET Core、.NET 5+ 中也完全兼容。
核心作用:在 DataSet 中建立表與表之間的關(guān)聯(lián)(如 “訂單→訂單明細(xì)”“部門→員工”),支持關(guān)聯(lián)查詢、級聯(lián)操作和多層級數(shù)據(jù)結(jié)構(gòu)管理。
本文就是要重點介紹他的數(shù)據(jù)表之間的級聯(lián)操作。
2、核心優(yōu)勢
內(nèi)存級關(guān)聯(lián):無需訪問數(shù)據(jù)庫,直接在 DataSet 中完成關(guān)聯(lián)查詢,性能高效。
數(shù)據(jù)完整性:級聯(lián)操作確保父表變更時子表數(shù)據(jù)的一致性,避免 “孤兒數(shù)據(jù)”。
多層級支持:天然適配樹形、嵌套結(jié)構(gòu),如組織架構(gòu)、分類目錄等場景。
綜上,DataRelation 是 .NET 中處理內(nèi)存數(shù)據(jù)關(guān)聯(lián)的 “利器”,尤其適合離線數(shù)據(jù)處理、客戶端本地緩存等場景,從基礎(chǔ)兩表關(guān)聯(lián)到復(fù)雜多層級結(jié)構(gòu)都能高效支撐。
二、典型操作
1、操作步驟
初始化數(shù)據(jù)集與表結(jié)構(gòu):創(chuàng)建 DataSet 并定義參與關(guān)聯(lián)的 DataTable(父表和子表)。
定義列與主鍵,添加數(shù)據(jù):為每個表設(shè)置列(含主鍵、外鍵),并插入初始數(shù)據(jù)。
創(chuàng)建 DataRelation:通過 DataRelation 構(gòu)造函數(shù)指定關(guān)系名、父表主鍵列、子表外鍵列。
執(zhí)行關(guān)聯(lián)操作:利用 GetChildRows、GetParentRow 進(jìn)行關(guān)聯(lián)查詢,或通過級聯(lián)操作維護(hù)數(shù)據(jù)完整性。
2、典型代碼實現(xiàn)
步驟1:初始化數(shù)據(jù)集與表結(jié)構(gòu)
using System;
using System.Data;
class Program
{
static void Main()
{
// 1. 初始化 DataSet
DataSet ds = new DataSet("OrderDB");
// 2. 定義父表(訂單表)
DataTable dtOrders = new DataTable("Orders");
// 定義子表(訂單明細(xì)表)
DataTable dtOrderDetails = new DataTable("OrderDetails");步驟2:定義列、主鍵并添加數(shù)據(jù)
// 配置訂單表列與主鍵
dtOrders.Columns.Add("OrderID", typeof(int));
dtOrders.Columns.Add("OrderDate", typeof(DateTime));
dtOrders.PrimaryKey = new[] { dtOrders.Columns["OrderID"] }; // 設(shè)置主鍵
// 配置訂單明細(xì)表列與外鍵
dtOrderDetails.Columns.Add("DetailID", typeof(int));
dtOrderDetails.Columns.Add("OrderID", typeof(int)); // 外鍵,關(guān)聯(lián) Orders.OrderID
dtOrderDetails.Columns.Add("ProductName", typeof(string));
dtOrderDetails.Columns.Add("Quantity", typeof(int));
// 向表中添加數(shù)據(jù)
dtOrders.Rows.Add(1, DateTime.Now);
dtOrders.Rows.Add(2, DateTime.Now.AddDays(-1));
dtOrderDetails.Rows.Add(101, 1, "手機(jī)", 2);
dtOrderDetails.Rows.Add(102, 1, "耳機(jī)", 1);
dtOrderDetails.Rows.Add(103, 2, "平板", 1);
// 將表添加到 DataSet
ds.Tables.Add(dtOrders);
ds.Tables.Add(dtOrderDetails);步驟3:創(chuàng)建 DataRelation 建立關(guān)聯(lián)
// 創(chuàng)建訂單與訂單明細(xì)的關(guān)系
DataRelation orderDetailRel = new DataRelation(
"Order_Detail_Relation", // 關(guān)系名
dtOrders.Columns["OrderID"], // 父表主鍵列
dtOrderDetails.Columns["OrderID"], // 子表外鍵列
true, true // 啟用級聯(lián)刪除和級聯(lián)更新
);
ds.Relations.Add(orderDetailRel);
步驟4:執(zhí)行關(guān)聯(lián)操作(查詢、級聯(lián)刪除)
// ① 關(guān)聯(lián)查詢:查詢訂單ID=1的所有明細(xì)
DataRow orderRow = dtOrders.Rows.Find(1); // 通過主鍵查找訂單
DataRow[] detailRows = orderRow.GetChildRows(orderDetailRel);
Console.WriteLine("訂單1的明細(xì):");
foreach (DataRow detail in detailRows)
{
Console.WriteLine($"- 商品:{detail["ProductName"]},數(shù)量:{detail["Quantity"]}");
}
// ② 級聯(lián)刪除:刪除訂單后,其明細(xì)自動刪除
orderRow.Delete(); // 刪除訂單ID=1
int remainingDetails = dtOrderDetails.Select("OrderID = 1").Length;
Console.WriteLine($"訂單1刪除后,剩余明細(xì)數(shù)量:{remainingDetails}"); // 結(jié)果為0
// ③ 級聯(lián)更新:修改訂單ID后,明細(xì)的OrderID自動同步
DataRow orderRow2 = dtOrders.Rows.Find(2);
orderRow2["OrderID"] = 200; // 將訂單ID從2改為200
DataRow[] updatedDetails = dtOrderDetails.Select("OrderID = 200");
Console.WriteLine($"訂單ID更新后,關(guān)聯(lián)明細(xì)數(shù)量:{updatedDetails.Length}"); // 結(jié)果為1
}
}代碼說明
- 步驟1-2:完成了數(shù)據(jù)集、表結(jié)構(gòu)的定義和初始數(shù)據(jù)的填充,為關(guān)聯(lián)操作奠定基礎(chǔ)。
- 步驟3:通過
DataRelation明確了“訂單→訂單明細(xì)”的關(guān)聯(lián)關(guān)系,并啟用了級聯(lián)刪除和更新,確保數(shù)據(jù)一致性。 - 步驟4:演示了關(guān)聯(lián)查詢(父查子)、級聯(lián)刪除、級聯(lián)更新三種典型操作,體現(xiàn)了
DataRelation在簡化多層級數(shù)據(jù)處理中的優(yōu)勢。
直接運行上述代碼即可看到關(guān)聯(lián)操作的效果,可根據(jù)實際需求調(diào)整表結(jié)構(gòu)、數(shù)據(jù)和操作邏輯。
三、有關(guān)find函數(shù)的說明
在代碼的第二步中,我們注意到dtOrders.Rows.Find(1) 的作用是“在主鍵中尋找鍵值為1的行”。為什么find只找主鍵而不找其他的鍵或列呢?
之所以只會查找主鍵列中值為1的行,而不是其他列,核心原因是 Find 方法是.net中專門為“主鍵查詢”設(shè)計的,其邏輯嚴(yán)格依賴于 DataTable 中預(yù)設(shè)的 PrimaryKey(主鍵)配置。
關(guān)鍵原理:Find方法與主鍵的強綁定
PrimaryKey的預(yù)配置- 在步驟2中,我們通過以下代碼為
dtOrders(訂單表)設(shè)置了主鍵: dtOrders.PrimaryKey = new[] { dtOrders.Columns["OrderID"] };- 這行代碼明確告訴
DataTable:OrderID列是當(dāng)前表的主鍵列。主鍵的特性是:唯一標(biāo)識一行數(shù)據(jù),且值不可重復(fù)。 Find方法的查詢邏輯DataRowCollection.Find(object key)方法的內(nèi)部邏輯是:- 簡單說:
Find方法是“主鍵專屬查詢工具”,它完全無視其他非主鍵列,哪怕其他列(如假設(shè)的OtherID列)也有值為1的行,Find也不會去匹配。
只搜索 DataTable 中被標(biāo)記為 PrimaryKey 的列(此處即 OrderID 列)。
匹配傳入的 key 值(此處為 1)與主鍵列中的值,返回第一個匹配的行。
舉例驗證:其他列有值為1時,F(xiàn)ind仍只認(rèn)主鍵
假設(shè)我們給訂單表增加一個非主鍵列 OtherID,并插入一行 OtherID=1 的數(shù)據(jù):
// 給訂單表添加一個非主鍵列
dtOrders.Columns.Add("OtherID", typeof(int));
// 插入數(shù)據(jù):OrderID=3(主鍵),OtherID=1(非主鍵)
dtOrders.Rows.Add(3, DateTime.Now, 1);
// 用Find(1)查詢
DataRow foundRow = dtOrders.Rows.Find(1);
// 結(jié)果:foundRow 會指向 OrderID=1 的行(而非 OtherID=1 的行)
Console.WriteLine($"找到的行:OrderID={foundRow["OrderID"]}, OtherID={foundRow["OtherID"]}");輸出結(jié)果會是 OrderID=1 的行(假設(shè)其 OtherID 可能為 null 或其他值),因為 Find 只看主鍵列 OrderID。
對比:如果想查詢非主鍵列,該用什么?
如果需要查詢非主鍵列(如 OtherID=1),不能用 Find,而應(yīng)使用 Select 方法(基于條件篩選):
// 查詢非主鍵列 OtherID=1 的行
DataRow[] rows = dtOrders.Select("OtherID = 1");
Select 方法會掃描所有列,根據(jù)條件篩選,不依賴主鍵配置,這也是它與 Find 的核心區(qū)別。
總結(jié)
dtOrders.Rows.Find(1) 之所以精準(zhǔn)定位到“主鍵值為1的行”,是因為:
- 表已通過
PrimaryKey明確OrderID為主鍵列; Find方法的設(shè)計邏輯就是僅搜索主鍵列,與其他列無關(guān)。
這種機(jī)制保證了Find方法的高效性(基于主鍵索引)和精準(zhǔn)性(唯一標(biāo)識一行),是DataTable中主鍵查詢的最優(yōu)方式。
四、關(guān)于GetChildRows的使用
DataRow.GetChildRows 是 DataRow 類中用于查詢“關(guān)聯(lián)子表行”的核心方法,專門配合 DataRelation 使用,能夠快速獲取當(dāng)前父表行在子表中對應(yīng)的所有關(guān)聯(lián)數(shù)據(jù)行。以下是詳細(xì)介紹:
1、GetChildRows定義和功能
public DataRow[] GetChildRows(DataRelation relation);
- 作用:根據(jù)指定的
DataRelation(表關(guān)系),查詢與當(dāng)前DataRow(父表行)相關(guān)聯(lián)的所有子表行。 - 參數(shù):
DataRelation對象(需提前定義父表與子表的關(guān)聯(lián)規(guī)則)。 - 返回值:
DataRow[]數(shù)組,包含所有匹配的子表行;若沒有關(guān)聯(lián)行,返回空數(shù)組(非null)。
2、核心工作原理
GetChildRows 的邏輯完全依賴 DataRelation 中定義的關(guān)聯(lián)規(guī)則,步驟如下:
- 讀取關(guān)聯(lián)規(guī)則:從傳入的
DataRelation中獲取父表主鍵列(如Orders.OrderID)和子表外鍵列(如OrderDetails.OrderID)。 - 提取當(dāng)前父行的主鍵值:獲取當(dāng)前
DataRow(父行)中主鍵列的值(例如訂單1的OrderID=1)。 - 自動匹配子表行:在子表中篩選出“外鍵列值 = 父行主鍵值”的所有行(例如
OrderDetails中OrderID=1的所有明細(xì))。 - 返回結(jié)果:將篩選出的子表行封裝為數(shù)組返回。
3、關(guān)鍵特性
- 依賴
DataRelation - 必須先定義有效的
DataRelation,否則會拋出ArgumentException(例如關(guān)聯(lián)的表或列不存在)。 - 無需手動寫篩選條件
- 對比無
DataRelation時的手動篩選(如dtOrderDetails.Select("OrderID=1")),GetChildRows完全通過DataRelation自動處理關(guān)聯(lián)邏輯,避免硬編碼條件,減少錯誤。 - 支持多層級關(guān)聯(lián)
- 對于“父→子→孫”的多層級結(jié)構(gòu)(如“公司→部門→員工”),可嵌套調(diào)用
GetChildRows:
// 公司→部門(第一層子級)
DataRow[] depts = companyRow.GetChildRows(compDeptRel);
// 部門→員工(第二層子級)
foreach (var dept in depts)
{
DataRow[] emps = dept.GetChildRows(deptEmpRel);
}
- 性能高效
- 內(nèi)部基于
DataRelation的關(guān)聯(lián)信息優(yōu)化查詢,比手動遍歷或Select方法(全表掃描)更高效,尤其數(shù)據(jù)量大時差異明顯。
4、實例解析(結(jié)合訂單場景)
在之前的“訂單→訂單明細(xì)”例子中:
// 父行:訂單ID=1的訂單行 DataRow orderRow = dtOrders.Rows.Find(1); // 獲取該訂單的所有明細(xì)行 DataRow[] detailRows = orderRow.GetChildRows(orderDetailRel);
orderDetailRel定義了關(guān)聯(lián)規(guī)則:Orders.OrderID(父主鍵)→OrderDetails.OrderID(子外鍵)。GetChildRows自動提取orderRow的OrderID=1,然后在OrderDetails中篩選出所有OrderID=1的行,最終返回這兩行明細(xì)(手機(jī)、耳機(jī))。
五、DataRelation關(guān)于增刪改查的總結(jié)
結(jié)合1~4步的實例代碼,DataRelation 在增刪改查(CRUD)操作中相比“無關(guān)聯(lián)手動處理”的方式,核心優(yōu)勢體現(xiàn)在 “簡化關(guān)聯(lián)邏輯”“保障數(shù)據(jù)完整性”“提升開發(fā)效率” 三個方面,具體如下:
1、查詢(Read):無需手動關(guān)聯(lián),多層級查詢更簡潔
- 有
DataRelation時: - 通過
GetChildRows直接基于預(yù)設(shè)關(guān)系查詢子表數(shù)據(jù),無需手動提取父表主鍵、寫篩選條件。 - 例:查詢訂單1的所有明細(xì),僅需兩行代碼:
DataRow orderRow = dtOrders.Rows.Find(1); // 找父行 DataRow[] detailRows = orderRow.GetChildRows(orderDetailRel); // 直接獲取子行
- 邏輯清晰,且多層級場景(如“公司→部門→員工”)可通過嵌套
GetChildRows輕松實現(xiàn),無需重復(fù)寫篩選邏輯。 - 無
DataRelation時: - 需手動提取父表主鍵值,再用
Select方法寫條件篩選子表,代碼冗余且易出錯:
DataRow orderRow = dtOrders.Rows.Find(1);
int orderID = (int)orderRow["OrderID"]; // 手動提取主鍵
DataRow[] detailRows = dtOrderDetails.Select($"OrderID = {orderID}"); // 手動寫篩選條件
2、新增(Create):關(guān)聯(lián)關(guān)系自動維護(hù),減少人為錯誤
- 有
DataRelation時: - 新增子表數(shù)據(jù)時,只需設(shè)置外鍵值(如訂單明細(xì)的
OrderID),DataRelation會自動關(guān)聯(lián)到對應(yīng)的父表行,無需額外邏輯。 - 例:新增訂單明細(xì)時,只需確保
OrderID正確,無需手動驗證是否存在對應(yīng)訂單:
dtOrderDetails.Rows.Add(104, 1, "充電器", 3); // 直接設(shè)置OrderID=1(關(guān)聯(lián)訂單1)
- 無
DataRelation時: - 雖然新增步驟類似,但需開發(fā)者手動確保外鍵值與父表主鍵匹配(如檢查訂單1是否存在),否則會產(chǎn)生“無效關(guān)聯(lián)數(shù)據(jù)”(如明細(xì)的
OrderID=999但無對應(yīng)訂單),增加數(shù)據(jù)不一致風(fēng)險。
3、修改(Update):級聯(lián)更新自動同步,避免關(guān)聯(lián)斷裂
- 有
DataRelation時: - 啟用級聯(lián)更新后,修改父表主鍵值,子表外鍵會自動同步,無需手動遍歷子表修改。
- 例:將訂單2的
OrderID從2改為200,其關(guān)聯(lián)明細(xì)的OrderID自動變?yōu)?00:
orderRow2["OrderID"] = 200; // 父表主鍵修改 // 子表明細(xì)的OrderID自動同步為200(無需手動操作)
- 無
DataRelation時: - 需手動查詢所有關(guān)聯(lián)子表行,逐個修改外鍵值,步驟繁瑣且易遺漏:
orderRow2["OrderID"] = 200; DataRow[] details = dtOrderDetails.Select($"OrderID = 2"); // 手動查關(guān)聯(lián)子行 foreach (var d in details) d["OrderID"] = 200; // 手動逐個修改
4、刪除(Delete):級聯(lián)刪除自動清理,防止孤兒數(shù)據(jù)
- 有
DataRelation時: - 啟用級聯(lián)刪除后,刪除父表行時,所有關(guān)聯(lián)子表行會被自動刪除,避免“孤兒數(shù)據(jù)”(子表存在但無對應(yīng)父表行)。
- 例:刪除訂單1,其關(guān)聯(lián)的所有明細(xì)自動刪除:
orderRow.Delete(); // 刪除父行(訂單1) // 子表明細(xì)自動刪除(無需手動操作)
- 無
DataRelation時: - 需先手動查詢并刪除所有關(guān)聯(lián)子表行,再刪除父表行,步驟多且易因遺漏導(dǎo)致孤兒數(shù)據(jù):
DataRow[] details = dtOrderDetails.Select($"OrderID = 1"); // 手動查子行 foreach (var d in details) d.Delete(); // 手動刪子行 orderRow.Delete(); // 再刪父行
六、總結(jié):DataRelation的核心價值
- 邏輯解耦:關(guān)聯(lián)規(guī)則(如“訂單ID關(guān)聯(lián)”)集中定義在
DataRelation中,而非分散在增刪改查的代碼里,便于維護(hù)。 - 自動化處理:級聯(lián)更新/刪除、關(guān)聯(lián)查詢等操作由框架自動完成,減少手動編碼量和出錯概率。
- 數(shù)據(jù)一致性:通過約束和級聯(lián)操作,天然避免“無效關(guān)聯(lián)”“孤兒數(shù)據(jù)”等問題,保障內(nèi)存中數(shù)據(jù)集的完整性。
對于多層級數(shù)據(jù)(如“組織架構(gòu)”“分類目錄”),DataRelation 的優(yōu)勢會被進(jìn)一步放大,是 .NET 中處理內(nèi)存關(guān)聯(lián)數(shù)據(jù)的最優(yōu)方案。
到此這篇關(guān)于C#數(shù)據(jù)級聯(lián)操作的法寶DataRelation(操作步驟)的文章就介紹到這了,更多相關(guān)C#數(shù)據(jù)級聯(lián)datarelation內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.Net WInform開發(fā)筆記(二)Winform程序運行結(jié)構(gòu)圖及TCP協(xié)議在Winform中的應(yīng)用
中午沒事,把去年剛畢業(yè)那會畫的幾張圖翻出來了,大概介紹Winform應(yīng)用程序運行的過程,以及TCP協(xié)議在Winform中的應(yīng)用。感興趣的朋友可以了解下;如果有Windows消息機(jī)制等基礎(chǔ),很好理解這兩張2013-01-01
C#創(chuàng)建簡單windows窗體應(yīng)用(加法器)
這篇文章主要為大家詳細(xì)介紹了C#創(chuàng)建一個簡單windows窗體應(yīng)用的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-03-03
分享WCF文件傳輸實現(xiàn)方法---WCFFileTransfer
這篇文章主要介紹了分享WCF文件傳輸實現(xiàn)方法---WCFFileTransfer,需要的朋友可以參考下2015-11-11
C# Winform使用擴(kuò)展方法實現(xiàn)自定義富文本框(RichTextBox)字體顏色
這篇文章主要介紹了C# Winform使用擴(kuò)展方法實現(xiàn)自定義富文本框(RichTextBox)字體顏色,通過.NET的靜態(tài)擴(kuò)展方法來改變RichTextBox字體顏色,需要的朋友可以參考下2015-06-06
C# System.TypeInitializationException 異常處理方案
這篇文章主要介紹了C# System.TypeInitializationException 異常處理方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02

