之前寫過一篇文章關于 DataGridView 如何利用 BindingSource 綁定1:N的多表資料(.NET 2.0 - WinForm Control - DataGridView 資料綁定) 。這裡翻出來重寫下,将綁定的資料源由 DataSet 換成現在流行的 Entity Framework~
示例代碼下載下傳:http://download.csdn.net/source/3273686
使用的是ms sql express資料庫,資料庫檔案的路徑需要自行修改。
1.資料的準備:
資料庫使用的 Northwind 裡的3個表: customers, orders, order details
由資料庫導出 Entities Model 的過程這裡不說了,直接看看導出的EF:
相比之前的版本 DataSet 通過 Relations 屬性來保持上面3個表的關系。
DataSet 添加 Relation 的示例代碼:
objDataSet.Relations.Add("CustomerOrder", objDataSet.Tables("Customers").Columns("CustomerID"), objDataSet.Tables("Orders").Columns("CustomerID"));
objDataSet.Relations.Add("OrderDetail", objDataSet.Tables("Orders").Columns("OrderID"), objDataSet.Tables("OrderDetails").Columns("OrderID"));
而在EF中,DB中如果已經設定了外鍵關聯,那麼EF就能夠自動生成實體的關系,就是 Navigation Properties。
通過代碼: var order = northwind.Orders.First().Order_Details 可以傳回對應 order 的 order_details
2. 資料準備完畢,現在來看看綁定部分的代碼:
因為EF已經幫你做完ORM了(不用你寫sql,不用你Adapter.Fill),你直接用就可以了。因為 linq2Entities 的延遲加載特性,每次customer變化的時候,其order,order_deails都是即時查詢而不是一開始就讀入再過濾,是以我們保持一份 ObjectContext 的執行個體。如果 ObjectContext 在一次綁定之後就銷毀,将導緻綁定連動失效。在Form_Load時執行個體化,Form_Closing時銷毀:
private void Form1_Load(object sender, EventArgs e)
{
northwind = new NORTHWNDEntities();
BindData();
}
private void DataBindDemo_FormClosing(object sender, FormClosingEventArgs e)
{
if (northwind != null)
northwind.Dispose();
}
3. 資料綁定:
1) customerBindingSource直接綁定northwind.Customers
2) orderBindingSource綁定customerBindingSource,并設定DataMember為Customers的Navigation Property——"Orders"
3) orderDetailBindingSource則綁定orderBindingSource,并設定DataMember為Orders的Navigation Property——"Order_Details"
private void BindData()
{
customerBindingSource.DataSource = northwind.Customers;
lstCustomer.DataSource = customerBindingSource;
lstCustomer.ValueMember = "ContactName";
lstCustomer.DisplayMember = "ContactName";
orderBindingSource.DataSource = customerBindingSource;
orderBindingSource.DataMember = "Orders";
dgvOrders.DataSource = orderBindingSource;
dgvOrders.Columns["Customers"].Visible = false;
dgvOrders.Columns["Order_Details"].Visible = false;
orderDetailBindingSource.DataSource = orderBindingSource;
orderDetailBindingSource.DataMember = "Order_Details";
dgvOrderDetails.DataSource = orderDetailBindingSource;
dgvOrderDetails.Columns["Orders"].Visible = false;
txtName.DataBindings.Add("Text", customerBindingSource, "ContactName");
txtContactTitle.DataBindings.Add("Text", customerBindingSource, "ContactTitle");
txtAddress.DataBindings.Add("Text", customerBindingSource, "Address");
txtCity.DataBindings.Add("Text", customerBindingSource, "City");
txtRegion.DataBindings.Add("Text", customerBindingSource, "Region");
txtPostalCode.DataBindings.Add("Text", customerBindingSource, "PostalCode");
txtPhone.DataBindings.Add("Text", customerBindingSource, "Phone");
txtFax.DataBindings.Add("Text", customerBindingSource, "Fax");
txtCountry.DataBindings.Add("Text", customerBindingSource, "Country");
}
4.資料的Navigate,直接調用BindingSource的MoveXXX()方法就可以了。
private void btnFirst_Click(object sender, EventArgs e)
{
customerBindingSource.MoveFirst();
}
private void btnPre_Click(object sender, EventArgs e)
{
customerBindingSource.MovePrevious();
}
private void btnNext_Click(object sender, EventArgs e)
{
customerBindingSource.MoveNext();
}
private void btnLast_Click(object sender, EventArgs e)
{
customerBindingSource.MoveLast();
}
到此為止,用BindingSource綁定EF就完成了。運作下看看綁定連動的效果:
接下來,實作一下更新DB的代碼,不過已經和BindingSource沒什麼關系了。
因為預設的綁定是雙向綁定,也就是畫面上任何的修改,都會影響到EF的緩存,可以通過ObjectStateManager獲得所有修改過的Entity(傳回的是 IEnumerable<ObjectStateEntry>):
var updated = northwind.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
ObjectStateEntry裡儲存着修改過的Entity, 還有所有修改過的屬性名,修改前的值,修改後的值,是以你可以利用下面的方法打出log:
private string Log(ObjectStateEntry entry)
{
var keys = "";
foreach (var kv in entry.EntityKey.EntityKeyValues)
keys += string.Format("{0}={1},", kv.Key, kv.Value);
keys = keys.TrimEnd(',');
var log = string.Format("{0} ({1})/n",
entry.Entity.GetType().Name, keys);
log += "............................../n";
foreach (var property in entry.GetModifiedProperties())
{
log += string.Format("{0}: [{1}] -> [{2}]", property,
entry.OriginalValues[property], entry.CurrentValues[property].ToString());
log += "/n";
}
return log;
}
彈出Alert資訊:
var updated = northwind.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
var updaterAlert = "Confirm to update this data:/n/n";
updaterAlert += "-------------------------------------------/n";
foreach (var upd in updated)
{
updaterAlert += Log(upd);
updaterAlert += "-------------------------------------------/n";
}
var confirmSave = MessageBox.Show(updaterAlert, "Confirm", MessageBoxButtons.OKCancel);
看看效果:提示中有所有修改的Entity的Key,以及修改的屬性,修改前和修改後的值。
如果使用者點選OK,那麼直接調用:northwind.SaveChanges(); 就可以了。
如果使用者點選Cancel,還要放棄修改過的資料怎麼辦?我們可以利用 ObjectContext.Refresh 方法,Refresh 第一參數是一個枚舉:
1) RefreshMode.StoreWins 表示放棄本地資料,接受DB的資料。
2) RefreshMode.ClientWins 表示繼續保持目前的資料,直到調用SaveChanges() 以目前資料更新到DB。
PS: Refresh主要運用場景是處理并發錯誤,比如你修改的一條資料,正好也被别的用戶端修改了,當調用SaveChanges()的時候會抛出:OptimisticConcurrencyException,在該異常裡通過 Refresh 來進一步處理資料。(是放棄還是堅持更新)
扯的有點遠了,關于 EF 的并發錯誤處理,詳細可以看 MSDN:http://msdn.microsoft.com/zh-cn/library/bb399228.aspx
調用EF更新的代碼:
private void btnUpdate_Click(object sender, EventArgs e)
{
var updated = northwind.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
var updaterAlert = "Confirm to update this data:/n/n";
updaterAlert += "-------------------------------------------/n";
foreach (var upd in updated)
{
updaterAlert += Log(upd);
updaterAlert += "-------------------------------------------/n";
}
var confirmSave = MessageBox.Show(updaterAlert, "Confirm", MessageBoxButtons.OKCancel);
if (confirmSave == System.Windows.Forms.DialogResult.OK)
{
northwind.SaveChanges();
MessageBox.Show("All changes is saved.");
}
else
{
var confirmReject = MessageBox.Show("Reject all changes?", "Confirm", MessageBoxButtons.OKCancel);
if (confirmReject == System.Windows.Forms.DialogResult.OK)
{
northwind.Refresh(RefreshMode.StoreWins, northwind.Customers);
northwind.AcceptAllChanges();
MessageBox.Show("All changes is rejected.");
}
}
}
Refresh 裡隻用了 northwind.Customers 是因為3個表已經有關聯關系,是以隻從最上面的表重新整理就可以了。
引申:使用 ObjectStateManager.GetObjectStateEntities 還可以簡化的畫面變化檢查處理,不再需要一個個TextBox檢查是否發生變化了,一句話搞定。