透過vs.net資料窗體向導看Ado.net
作者:鄭佐2005-1-2
在csdn上經常碰到有人問一些Ado.net的問題,特别是開發資訊管理系統之類的跟資料庫比較密切的程式時,在資料和界面層的開發中會遇到不少常見問題,下面我們通過vs.net自帶的資料窗體向導來看看能它能幫我們決絕什麼問題。
一.使用向導
建立Windows 應用程式,為目前項目添加元件,選擇資料窗體向導,這裡名稱填寫為DataForm1.cs。單擊打開出現資料窗體向導對話框。建立新的類型化資料集MyDataSet。使用本地資料連接配接向導,這裡我選擇Northwind庫作為資料源。在選擇表或視圖那一步添加Categories表和Products表。添加一個表之間的關系取名CategoryProductRel。在選擇顯示樣式一步中選擇顯示資料的方式為單個控件中的單個記錄,這個就會有資料綁定到文本框。可以用資料導航來選擇父表的記錄。向導完成後會生成一個OleDbConnection,幾個表生成幾個OleDbDataAdapter負責資料的擷取和更新。另外就是一個強類型的資料集。
整個程式的運作界面如下:

基本功能都包括了,不過等你點選幾下,程式界面上就會出現小的bug,微軟可能也估計到沒有人會要這個窗體來處理資料,不過這個不是我們所關心的。
二.資料填充
先來看看資料集結構:
一個Categories表作為父表,Products表作為子表,CategoryID為外鍵,建立的資料表關系。
通過加載按鈕資料庫中的相關資料會被填充到資料集。執行的LoadDataSet()方法的過程如下:
使用DataAdatpter.Fill()方法填充資料到臨時的一個資料集,如果操作成功,将合并這個臨時資料集到原有的資料集,DataGrid通過表關系綁定子表。
// 嘗試填充臨時資料集。
this.FillDataSet(objDataSetTemp);
grdProducts.DataSource = null;
// 清空資料集中的舊記錄。
objMyDataSet.Clear();
// 将記錄合并到主資料集中。
objMyDataSet.Merge(objDataSetTemp);
grdProducts.SetDataBinding(objMyDataSet, "Categories.CategoryProductRel");
在資料填充的方法中我們注意到
dataSet.EnforceConstraints = false;
這一步會對資料填充效率會有所提高。
另外還有一個細節就是執行兩個以上DataAdapter的資料通路方法時顯式打開關閉資料連接配接效率會比較高。因為在執行DataAdapter的資料更新方法前和方法後資料連接配接Connection執行個體的狀态不會改變。如果下面代碼。
//this.oleDbConnection1.Open();
this.oleDbDataAdapter1.Fill(dataSet);
this.oleDbDataAdapter2.Fill(dataSet);
執行之前Connection的狀态是關閉的,那可想而知這一過程會執行兩次打開連接配接關閉連接配接。
其實一次就夠。
為了資料的嚴密性,填充完資料後不要忘了加上下面代碼,
// 重新打開限制檢查。
dataSet.EnforceConstraints = true;
如果是直讀那就無所謂了。
有了資料填充那就來看資料的單值綁定和多值綁定。
三.資料綁定
資料的單值綁定如下:
this.editCategoryID.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.objMyDataSet, "Categories.CategoryID"));
this.editCategoryName.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.objMyDataSet, "Categories.CategoryName"));
上面一段代碼把資料表的列綁定到了TextBox的Text屬性上。
資料的多值綁定如下:
grdProducts.SetDataBinding(objMyDataSet, "Categories.CategoryProductRel");
可見通過關系綁定資料相當友善。
四.資料浏覽
這裡通過BindingContext對象的索引得到BindingManagerBase執行個體,而BindingManagerBase.Position就是我們需要的,通過Position來顯示某一行的資料記錄。
例如下一條:
this.BindingContext[objMyDataSet,"Categories"].Position = (this.BindingContext[objMyDataSet,"Categories"].Position + 1);
最後一條:
this.BindingContext[objMyDataSet,"Categories"].Position = (this.objMyDataSet.Tables["Categories"].Rows.Count - 1);
另外調用PositionChanged()方法來改變導航按鈕之間的索引顯示。
五.資料編輯
從添加方法中我們可以看到下面代碼:
// 清除目前編輯内容
this.BindingContext[objMyDataSet,"Categories"].EndCurrentEdit();
經常有人在csdn上提問為什麼在編輯DataGrid或TextBox的時候,隻有當編輯框失去焦點的時候才會被儲存。要實作不改變焦點就儲存可以通過上面代碼實作。
相對應的取消如下:
this.BindingContext[objMyDataSet,"Categories"].CancelCurrentEdit();
删除資料的代碼如下:
this.BindingContext[objMyDataSet,"Categories"].RemoveAt(this.BindingContext[objMyDataSet,"Categories"].Position);
不過我們開發的時候可能用的更多的是DataRow的Delete()方法。
六.資料更新
向導生成的代碼如下:
public void UpdateDataSet()
{
// 建立一個新資料集來儲存對主資料集所做的更改。
WindowsApplication1.MyDataSet objDataSetChanges = new WindowsApplication1.MyDataSet();
// 停止目前的任何編輯。
this.BindingContext[objMyDataSet,"Categories"].EndCurrentEdit();
this.BindingContext[objMyDataSet,"Products"].EndCurrentEdit();
// 擷取對主資料集所做的更改。
objDataSetChanges = ((WindowsApplication1.MyDataSet)(objMyDataSet.GetChanges()));
// 檢查是否做了任何更改。
if ((objDataSetChanges != null))
{
try
{
// 需要做一些更改,是以嘗試通過調用 update 方法
// 和傳遞資料集以及任何參數來更新資料源。
this.UpdateDataSource(objDataSetChanges);
objMyDataSet.Merge(objDataSetChanges);
objMyDataSet.AcceptChanges();
}
catch (System.Exception eUpdate)
{
// 在此處添加錯誤處理代碼。
throw eUpdate;
}
// 添加代碼以檢查傳回的資料集中是否有任何可能已被
// 推入到行對象錯誤中的錯誤。
}
}
更新過程很經典,通過擷取修改過的資料集更新子集送出到資料源完成更新動作,接着合并子集到原有資料集,順便提一下,合并的過程是基于資料表主鍵來判斷的。通過調用DataSet.AcceptChanges()方法送出自加載此 DataSet 或上次調用 AcceptChanges 以來對 DataSet 進行的所有更改。對應的就是Data.RejectChanges();復原自建立 DataSet 以來或上次調用 DataSet.AcceptChanges 以來對 DataSet 進行的所有更改。
七.補充
對于資料的更新需要視程式環境而定,不能都通過擷取子集再合并的方法,具體請看ADO.NET中的多資料表操作淺析—修改一文。也有人問起怎樣實作這樣的功能:在資料庫中的某一個字段資料為0或1,而在程式顯示上想讓它顯示為是或否,這裡我認為最好不要在Sql語句上做文章,代替的方法就是使用Binding對象的Format 事件和Parse事件。Binding.Format 事件,當将某控件的屬性綁定到某個資料值時發生。Binding.Parse 事件,在資料綁定控件的值更改時發生。還有可以通過BindingManagerBase.PositionChanged事件來設定各個按鈕的狀态。需要具體的例子可以看MSDN或《Ado.net Core Reference》一書。另外,使用Try{}catch (System.Exception ex){}還是要視情況而定,不能一味的全都通過Exception基類把什麼都撲捉了,如果知道有可能會抛出什麼類型的異常,還是越具體越好,造成的反面效果就是要寫更多的代碼。具體為什麼要這麼做可以看《Applied Microsoft.NET Framework Programming》一書。