【6】页面数据和控件的自动交换机制
阅读目录
只要使用了数据库,那么管理和维护数据的工作就是不可避免的。应用程序中,对于数据库中数据的管理和维护,有两种情况。一种是与业务逻辑密切相关的数据,往往通过关系-对象映射的方法转换为对象,应用程序的运行就围绕这些对象进行,所以这类数据的管理维护的过程其实就是应用程序的运行过程。另外一种,就是很多的资料性的数据,涉及到数据表和数据字段都很多,但使用频度不高。对于这类数据,每个都转换为对象的话,一方面太复杂,另一方面由于使用率不高,也比较浪费。
对于后者,通过一个管理页面直接访问和操作数据库,反而简单有效。这类管理页面的技术难度不大,但是需要的步骤繁琐,对此类操作进行优化,可以大大提高开发的生产效率。
数据维护的流程
要优化数据管理页面,必须要了解典型的数据维护页面有哪些操作,才能有的放矢,既简化操作又能适应需求的变化。下面通过一个简单的数据管理页面,来剖析数据管理的一般过程。假如有以下的一个教师信息表,字段结构如下:
在Default.aspx页面,为每个字段添加一个对应的控件,如下所示:
对于部分控件,需要进行初始化,比如加载备选的条目、设定控件外观等操作。这些操作,需要在初次加载页面时进行:
if (!Page.IsPostBack) //初始化控件 { ddlTitle.Items.Add("助教"); ddlTitle.Items.Add("讲师"); ddlTitle.Items.Add("副教授"); ddlTitle.Items.Add("教授"); }
控制初始化以后的效果:
数据的加载
接下来,就可以进行数据的加载了。数据管理页面,一般都通过传递一个数据的ID给页面,在页面中进行加载。假如数据库中已经存在如下数据:
就可以通过default.aspx?ID=1的方式打开页面并加载数据。
需要说明的是,下面的数据库访问采用CommonCode下的AccessDB完成,因此,可以不用过多地考虑数据库的种类、数据库的操作等细节。
控件加载数据记录的代码如下:
if (!Page.IsPostBack) //加载数据 { if (Request.QueryString["ID"] != null) { DataTable dt = AccessDB.GetData("select * from Teacher where f_ID=" + Request.QueryString["ID"].ToString()); if (dt.Rows.Count == 1) { txtID.Text = dt.Rows[0]["f_ID"].ToString(); txtName.Text = dt.Rows[0]["f_Name"].ToString(); txtBegin.Text = dt.Rows[0]["f_BeginYear"].ToString(); ddlSex.SelectedValue = dt.Rows[0]["f_Sex"].ToString(); ddlTitle.SelectedValue = dt.Rows[0]["f_Title"].ToString(); } } }
对于下拉列表而言,显示的文本和值是不同的,所以后两个DropDownList赋值时只要按照Value来赋值即可。下图是ID参数为1时,加载相应数据的界面。
数据的修改
数据成功加载后,就可以通过界面对控件的数据进行各种操作。当完成编辑后,单击保存,就可以将控件内容保存到数据库中。在进行保存操作时,需要将控件中的内容提出,更新到数据库中,操作代码如下所示:
protected void Button2_Click(object sender, EventArgs e) { //保存数据 AccessDB.DoNonQuery("update Teacher set f_Name='" + txtName.Text + "'," +"f_Sex="+ddlSex.SelectedValue+"," +"f_Title='"+ddlTitle.SelectedValue+"'," +"f_BeginYear="+txtBegin.Text +" where f_ID="+txtID.Text); }
数据的添加
添加新记录操作往往需要两步,第一步,确定要执行的是添加动作,将所有的控件清空;第二步,将控件中的数据插入到数据库。
第一步的操作非常简单,对“添加”按钮编写事件:
protected void Button1_Click(object sender, EventArgs e) { txtID.Text = ""; txtName.Text = ""; txtBegin.Text = ""; ddlSex.SelectedIndex = 0; ddlTitle.SelectedIndex = 0; }
第二步的操作中,由于修改和新建两个处理往往放在同一个按钮事件中,所以需要判断当前操作的类型。对比可见,修改和新建的最大区别,在于ID字段。和其他字段有所不同,ID字段是自增的关键字段,不需要用户录入,也不允许修改。所以,在修改操作时txtID控件有内容,而新建操作时txtID控件没有内容。(为了防止用户在新建时的无意填入,可以把txtID控件设置为只读状态)
根据txtID判断状态后,进行相应的处理,相应的代码为:
if (txtID.Text != "") { //保存数据 AccessDB.DoNonQuery("update Teacher set f_Name='" + txtName.Text + "'," + "f_Sex=" + ddlSex.SelectedValue + "," + "f_Title='" + ddlTitle.SelectedValue + "'," + "f_BeginYear=" + txtBegin.Text + " where f_ID=" + txtID.Text); } else { AccessDB.DoNonQuery("insert into Teacher (f_Name,f_Sex,f_Title,f_BeginYear) values(" +"'"+txtName.Text+"'," +ddlSex.SelectedValue+"," +"'"+ddlTitle.SelectedValue+"'," +txtBegin.Text+")"); }
单击“添加”后,填入新的记录,再单击保存:
在数据库中可以看到,新记录已经被插入了。
到此,我们把一个最简单的页面的维护基本完成了,可以实现数据的加载、修改、添加。
自动交换机制
假如上述的教师表的字段需要扩展,根据上面的代码,至少这些地方是必须要进行修改:
1、对新字段添加控件
2、加载控件数据的代码中,添加新的控件的赋值
3、保存时,增加新的字段
4、新建时,增加新的字段
实际项目中表的字段数量往往非常多,几十上百个也不奇怪。那么,上述的修改代码量将是非常巨大的。这些繁杂的代码,存在以下的问题:
1、这些代码其实都是复制粘贴而来,费时费力,也不便于修改
2、两个主要的sql语句,随着字段的增加复杂性也随之增加,调试的困难度也不断增加
因此,如果能够把上面的机械操作变得自动化,将大大降低页面的代码量。自动化处理的思路如下:
AccessDB是公共的数据库处理,提供简单、快速的数据库操作支持。在AccessDB之上,由PageX完成控件和数据之间的数据交换。
PageX在初始化时,通过注册方法保存了控件和字段之间的对应关系。以后的加载数据、读取数据都可以通过这个对应关系自动完成了。
PageX的实现
PageX的结构如下:
public class PageX { public Hashtable Xmap; //注册控件和字段的对应关系 public void RegisterControl(string key, object ctl) { if (Xmap == null) Xmap = new Hashtable(); Xmap.Add(key, ctl); } }
其中的Xmap保存了对应关系,RegisterControl则进行注册。
有了对应关系,加载数据到控件或者相反的从控件读入数据就变得非常简单了,以加载数据为例,实现代码如下:
public void SetControls(DataRow dr) { if (Xmap == null) return; foreach (DictionaryEntry de in Xmap) { switch (de.Value.GetType().Name) { case "TextBox": ((TextBox)de.Value).Text = dr[de.Key.ToString()].ToString(); break; case "DropDownList": //其他控件…… } } }
支持的控件较多,包括TextBox、DropDownList、RadioButtonList、HtmlInputText、Label、Image、CheckBox等,代码较长不再完全展示。
跟它相对应,从控件中读取的方法也类似。这样,原来的加载方法就变得非常简单了。
public void GetControlsData(ref DataRow dr, string KeyField) { if (Xmap == null) return; foreach (DictionaryEntry de in Xmap) { //得到查询结果中,该字段的字段名称 string sFieldCName = de.Key.ToString(); switch (de.Value.GetType().Name) { case "TextBox": //处理逻辑:文本型字段原样复制,数值型字段,未设置时取0(关键字未设置时留空) if (TypeUtil.IsNumeric(dr.Table.Columns[sFieldCName].DataType) && ((TextBox)de.Value).Text == "") { if (sFieldCName == KeyField) break; //关键字不能自动赋0值 ((TextBox)de.Value).Text = "0"; } dr[sFieldCName] = ((TextBox)de.Value).Text; break; case "DropDownList": //其他控件…… } }
使用PageX来完成数据的自动加载
有了PageX,加载和保存数据就变得异常简单了,只要为页面定义一个PageX对象,对它登记控件和字段之间的对应关系后,加载或读出数据都变得简单了:
PageX px = new PageX(); //页面交换对象 protected void Page_Load(object sender, EventArgs e) { //其他初始化代码... //注册控件-不管是初次访问还是回调,都必须注册,因为回调后px对象无法保持状态 px.RegisterControl("f_ID", txtID); px.RegisterControl("f_Name", txtName); px.RegisterControl("f_Sex", ddlSex); px.RegisterControl("f_Title", ddlTitle); px.RegisterControl("f_BeginYear", txtBegin); if (!Page.IsPostBack) //初次打开,加载数据 { if (Request.QueryString["ID"] != null) { DataTable dt = AccessDB.GetData("select * from Teacher where f_ID=" + Request.QueryString["ID"].ToString()); if (dt.Rows.Count == 1) { px.SetControls(dt.Rows[0]); } } } } protected void Button2_Click(object sender, EventArgs e) { DataRow dr = AccessDB.GetEmptyRow("Teacher"); px.GetControlsData(ref dr, "f_ID"); if (txtID.Text != "") { //保存数据 AccessDB.Update("Teacher", dr, "f_ID"); } else { //添加数据 AccessDB.Add("Teacher", dr); } }
上述代码中,修改和新建操作时,已经把数据从控件存入到DataRow中了,AccessDB中提供了数据的自动添加和更新的方法,只要提供表名和关键字列,以及DataRow,就可以自动实现数据的添加和更新。
可以看到,通过PageX的数据交换机制,代码量大大减少了。如果遇到和前面一样的问题,教师表增加新的字段,那么,除了添加新的控件之外,代码中只要增加注册对应关系就行了。
非标准数据的处理
除了标准的数据之外,可能还有很多非标准的数据。PageX的机制支持比较自由的扩展。对于非标准的数据,可以不进行注册,在控件加载后进行单独的操作,而且数据更新或者插入前,对DataRow进行特定的操作。
px.SetControls(dt.Rows[0]); //在此读取dt.Rows[0]中的非标准字段,修改相应的控件 px.GetControlsData(ref dr, "f_ID"); //在此从非标准的控件读取数据,写入dr