ADO.NET 離線架構的核心在於 DataSet 及其內部物件模型。許多開發者對 DataTable、DataView、DataGrid 之間的關係與行為感到困惑,本文系統梳理這些概念,並深入剖析資料繫結同步機制。
DataTable 代表記憶體中完整、獨立的資料表結構,包含 DataColumnCollection(結構定義)與 DataRowCollection(資料列集合)。它可來自資料庫查詢(DataAdapter.Fill),也可純代碼建立。關鍵特性:
DataColumn.Expression = "UnitPrice * 0.086"DataRelation 跨表彙總,如 "Sum(Child(Order2Detail).Price)"DataRow.RowState 包含 Detached、Added、Modified、Deleted、Unchanged 五種狀態,配合 AcceptChanges()/RejectChanges() 實現變更管理。對應資料庫外鍵關係,DataRelation(String, DataColumn, DataColumn) 建立兩 DataTable 間的父/子關聯,必須加入 ParentRelations 或 ChildRelations 集合。此關聯支援導覽(GetChildRows())、彙總運算、主從式 UI 繫結。
DataRow row = table.NewRow(); // Detached
row["Col"] = "Val";
table.Rows.Add(row); // Added
row["Col"] = "NewVal"; // Modified
row.Delete(); // Deleted (仍在 Rows 中)
table.Rows.Remove(row); // Detached (從集合移除)
table.AcceptChanges(); // 所有列歸為 Unchanged,Deleted 列真正移除
DataAdapter.Fill() 會自動呼叫 AcceptChanges(),初始狀態皆為 Unchanged。
DataView 提供單一 DataTable 的排序、篩選、搜尋、編輯視圖(類似 DB View 但不跨表)。核心屬性:
RowFilter:如 "LastName = 'Smith'"RowStateFilter:結合 DataRowView.RowVersion(Current/Original)與 RowState 篩選,支援 CurrentRows、OriginalRows、ModifiedCurrent 等模式。DataView.Count 反映套用篩選後的列數。DataRowView.Row 可取得底層 DataRow。
DataGrid 實際繫結的是 DataView(DataTable 繫結時自動取 DefaultView;DataSet 需指定 DataMember)。顯示格式由 DataGridTableStyle 控制:
DataGridTableStyle style = new DataGridTableStyle();
style.MappingName = "MyTable";
style.GridColumnStyles.Add(new DataGridTextBoxColumn {
MappingName = "OrderID", HeaderText = "訂單號"
});
dataGrid.TableStyles.Add(style);
DataGrid.ReadOnly = false 允許編輯,變更直接反映至 DataTable。搭配 DataAdapter 的 InsertCommand/UpdateCommand/DeleteCommand,呼叫 DataAdapter.Update(table) 即可依列狀態批次回寫。若無視覺化設計器產生命令,可用 SqlCommandBuilder 自動產生(需 SelectCommand 含主鍵)。
同一 BindingContext 下繫結相同資料源的控件,共享一個 BindingManagerBase 管理目前列位置。Position 變更會同步更新所有繫結控件顯示內容。
// 取得管理器
BindingManagerBase bm = this.BindingContext[myDataSet, "customers"];
// 程式化切換目前列
bm.Position = 10;
// 主從表同步:建立關聯後,子表管理器自動跟隨父表目前列
DataRelation rel = new DataRelation("CustOrders",
ds.Tables["customers"].Columns["CustomerID"],
ds.Tables["orders"].Columns["CustomerID"]);
BindingManagerBase childBM = this.BindingContext[ds, "customers.CustOrders"];
因 DataGrid.CurrentRowIndex 可能因排序/篩選與 DataTable 索引不符,必須透過 BindingManagerBase.Current 取得 DataRowView 再轉 DataRow:
DataRowView drv = (DataRowView)bm.Current;
DataRow row = drv.Row;
// 取得欄位值
object val = row[drv.DataView.Table.Columns["ColumnName"]];
若使用 TableStyles,欄位對應需透過 GridColumnStyles[gridColIndex].MappingName 取得實際 DataColumn 名稱。