核心關係:DataSet = DataTable 集合 + 關聯 + 約束
- DataTable:單一記憶體資料表 (Rows, Columns, Constraints)。
- DataSet:多 DataTable 容器,支援 **DataRelation** (父子關聯)、**UniqueConstraint/ForeignKeyConstraint**。
- **效能**:單表操作用 `DataTable` 較輕量;多表關聯查詢、離線編輯、XML 序列化用 `DataSet`。
基本互轉操作
1. DataTable → DataSet (加入)
DataTable dt = GetCustomers(); // 既有 DataTable
DataSet ds = new DataSet();
ds.Tables.Add(dt); // dt 現在屬於 ds,Tables["Customers"] 可存取
// 或指定表名
dt.TableName = "Customers";
ds.Tables.Add(dt);
2. DataSet → DataTable (取出)
DataSet ds = GetDataSet();
// 依索引
DataTable dt1 = ds.Tables[0];
// 依表名 (推薦)
DataTable dt2 = ds.Tables["Customers"]; // 找不到回傳 null
// 安全檢查
if (ds.Tables.Contains("Orders"))
DataTable dt3 = ds.Tables["Orders"];
3. 複製 DataTable 到新 DataSet (不影響來源)
// Clone 只複製結構 (Schema),不複製資料
DataSet dsClone = ds.Clone();
// Copy 複製結構 + 資料
DataSet dsCopy = ds.Copy();
// 單表複製
DataTable dtCopy = dt.Copy(); // 結構 + 資料
DataTable dtClone = dt.Clone(); // 僅結構
4. 移除 DataTable
ds.Tables.Remove("OldTable"); // 依名稱
ds.Tables.RemoveAt(0); // 依索引
ds.Tables.Remove(dt); // 依物件參考
進階:DataRelation 建立主從關聯
DataSet ds = new DataSet();
DataTable dtParent = ds.Tables.Add("Categories");
DataTable dtChild = ds.Tables.Add("Products");
// 定義欄位
dtParent.Columns.Add("CategoryId", typeof(int));
dtParent.Columns.Add("CategoryName", typeof(string));
dtChild.Columns.Add("ProductId", typeof(int));
dtChild.Columns.Add("CategoryId", typeof(int));
dtChild.Columns.Add("ProductName", typeof(string));
// 設定主鍵
dtParent.PrimaryKey = new[] { dtParent.Columns["CategoryId"] };
// 建立關聯
DataRelation rel = new DataRelation("Category_Products",
dtParent.Columns["CategoryId"], // 父欄位
dtChild.Columns["CategoryId"]); // 子欄位
ds.Relations.Add(rel);
// 現在可導航
foreach (DataRow parent in dtParent.Rows)
{
DataRow[] children = parent.GetChildRows(rel); // 取得子列
Console.WriteLine($"{parent["CategoryName"]} 有 {children.Length} 個產品");
}
實用擴充方法
public static class DataSetExtensions
{
/// 將多個 DataTable 合併為單一 DataSet
public static DataSet ToDataSet(this IEnumerable tables, string dataSetName = "Merged")
{
var ds = new DataSet(dataSetName);
foreach (var dt in tables)
ds.Tables.Add(dt.Copy()); // 複製避免跨 DataSet 共用
return ds;
}
/// DataSet 序列化為 XML (含 Schema)
public static string ToXml(this DataSet ds, bool writeSchema = true)
{
using (var sw = new StringWriter())
{
ds.WriteXml(sw, writeSchema ? XmlWriteMode.WriteSchema : XmlWriteMode.IgnoreSchema);
return sw.ToString();
}
}
/// 從 XML 還原 DataSet
public static DataSet FromXml(string xml)
{
var ds = new DataSet();
using (var sr = new StringReader(xml))
ds.ReadXml(sr);
return ds;
}
}
效能與記憶體考量
| 操作 | 記憶體影響 | 建議 |
| ds.Copy() | 完整複製所有資料,記憶體翻倍 | 大量資料避免使用,改用 Clone + ImportRow |
| dt.ImportRow(row) | 逐列複製,可篩選 | 大量資料分頁複製 |
| ds.ReadXml/WriteXml | 完整序列化 | 大資料改用 SqlBulkCopy / 二進制格式 |
| DataRelation | 建立索引,增加記憶體 | 僅在需要導航/約束時建立 |
常見錯誤
- 跨 DataSet 直接 Add 同一 DataTable 實例:會拋 `This table already belongs to another DataSet`。解決:用 `dt.Copy()` 或 `ds.Tables.Add(dt.Copy())`。
- Tables["Name"] 回傳 null 卻直接用:一定要 `Contains` 或 `?.` 檢查。
- PrimaryKey 未設定就建立 DataRelation**:父表必須有 PrimaryKey,否則關聯無效。
- 修改 Clone() 出來的空 DataTable 結構**:會影響原 DataSet (Clone 共用 Column 集合參考)。改用 `dt.Copy().Clone()` 完全隔離。
總結
DataTable ↔ DataSet 互轉核心 API:
• `Tables.Add/Remove/Contains` 管理集合
• `Copy/Clone/ImportRow` 控制複製深度
• `DataRelation` 建立關聯導航
• `ReadXml/WriteXml` 序列化持久化
單表場景**優先用 DataTable** 降低記憶體;多表關聯、離線編輯、XML 匯出入才用 DataSet。理解 `Copy` (深層) vs `Clone` (淺層結構) 差異,是避免記憶體洩漏與資料汙染的關鍵。
- 上一篇文章: C# const 與 readonly 深度比較:編譯期常數 vs 執行期常數、靜態語意、版本相容性陷阱完整解析
- 下一篇文章: ASP.NET 圖片水印實作全攻略:上傳時靜態加水印 vs HttpHandler 動態加水印完整代碼與選型指南
Copyright ©
程式碼之家 All Rights Reserved