天天看點

.NET 4.0 - Winform Control - DataGridView 資料綁定(ADO.NET Entity Framework)

 之前寫過一篇文章關于 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:

.NET 4.0 - Winform Control - DataGridView 資料綁定(ADO.NET Entity Framework)

相比之前的版本 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。

.NET 4.0 - Winform Control - DataGridView 資料綁定(ADO.NET Entity Framework)
.NET 4.0 - Winform Control - DataGridView 資料綁定(ADO.NET Entity Framework)

通過代碼: 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就完成了。運作下看看綁定連動的效果:

.NET 4.0 - Winform Control - DataGridView 資料綁定(ADO.NET Entity Framework)

接下來,實作一下更新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,以及修改的屬性,修改前和修改後的值。

.NET 4.0 - Winform Control - DataGridView 資料綁定(ADO.NET Entity Framework)

如果使用者點選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檢查是否發生變化了,一句話搞定。