.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检查是否发生变化了,一句话搞定。