天天看点

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