作者:郑佐 2005-1-1
在 csdn 上经常碰到有人问一些 Ado.net 的问题,特别是开发信息管理系统之类的跟数据库比较密切的程序时,在数据和界面层的开发中会遇到不少常见问题,下面我们通过 vs.net 自带的数据窗体向导来看看能它能帮我们决绝什么问题。
一.使用向导
新建 Windows 应用程序,为当前项目添加组件,选择数据窗体向导,这里名称填写为 DataForm1.cs 。单击打开出现数据窗体向导对话框。创建新的类型化数据集 MyDataSet 。使用本地数据连接向导,这里我选择 Northwind 库作为数据源。在选择表或视图那一步添加 Categories 表和 Products 表。添加一个表之间的关系取名 CategoryProductRel 。在选择显示样式一步中选择显示数据的方式为单个控件中的单个记录,这个就会有数据绑定到文本框。可以用数据导航来选择父表的记录。向导完成后会生成一个 OleDbConnection ,几个表生成几个 OleDbDataAdapter 负责数据的获取和更新。另外就是一个强类型的数据集。
整个程序的运行界面如下:
<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"></shapetype><stroke joinstyle="miter"></stroke><formulas></formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f><lock aspectratio="t" v:ext="edit"></lock>
基本功能都包括了,不过等你点击几下,程序界面上就会出现小的
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
》一书。