第五天 -- 《2014-07-11 ADO》2 -- 一个电话簿小项目体现:多表连接、CRUD、控件绑定等等

一、下午《01、多表连接》--《05、返回实体类集合》

1、数据库的连接字符串应该放到哪里?

  因为一个项目中有可能多个类代码都会去操作数据库,或者一个解决方案中多个项目去操作数据库(当然我们真正开发时,会分层,会有架构,减少或避免这种情况)。所以(应用在单机、无并发小程序里)我们一般把连接字符串(字段)封装到SqlHelper这样的帮助类里面去,该类从配置文件中读取连接字符串内容。并且该类封装了需要的CRUD操作。其实写SqlHelper类,关键的方法也就三个:增删改(返回受影响行数)、检索结果集(可以多行多列)、返回单个值(只读取首行首列)。

另外:写SqlHelper类遇到的一些问题和错误,详见《不该活着的SqlHelper和DBHelper》

 

<1>将连接字符串放到.config配置文件中。(以Winform的App.config为例)

1 <configuration>
2   <!--需要创建一个能够保存连接字符串的节点-->
3   <connectionStrings> <!--  加s说明可以在里面创建多种数据库连接字符串-->
4     <!--添加两个连接字符串-->
5     <add name="connStr" connectionString="Data Source=.;Initial Catalog=MySchool;Integrated Security=True"/>
6     <add name="connStr1" connectionString="Data Source=.;Initial Catalog=MySchool;uid=sa;pwd=123"/>
7   </connectionStrings>
8 </configuration>

 

<2>在SqlHelper中读取配置文件,获取连接字符串

 1 //1、先引用System.Configuration.dll程序集
 2 
 3 using System.Configuration;
 4 
 5 ...
 6 
 7 public class SqlHelper
 8 {
 9     public static readonly string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; 
10     
11     //执行查询结果集
12     public static DataTable ExecuteTable(string sql, params SqlParameter[] ps)
13     {
14         SqlDataAdapter da = new SqlDataAdapter(sql, connStr);
15         DataTable dt = new DataTable();
16         //添加参数
17         da.SelectCommand.Parameters.AddRange(ps);
18         da.Fill(dt);
19         return dt;
20     } 
21     
22 ...
23 }

 

2、使用实体类,并将DataTable对象转List<实体类>

这种转换方法一般就直接放在SqlHelper类中了。如下所示:(实体类略)

 1 public class SqlHelper
 2 {
 3 ...
 4     public static List<PhoneNum> GetPhoneNumList(DataTable dt)
 5     {
 6         List<PhoneNum> lists = null;
 7         if (dt.Rows.Count > 0) //判断表中是否有数据行
 8         {
 9             lists = new List<PhoneNum>();
10             //遍历表的每 一行记录
11             foreach (DataRow row in dt.Rows)
12             {
13                 //每一行数据对应着一个对象
14                 PhoneNum temp = new PhoneNum();
15                 //一般建议使用字符串索引器,且在创建实体类的时候会将类的属性的名称创建为与表的字段一致,这样方便使用
16                 temp.Pid = (int)row["Pid"];
17                 
18                 if(!(row["Ptypeid"] is DBNull))
19                 {//如果Ptypeid列不是数据库null的话,再转换。
20                     temp.Ptypeid = (int)row["Ptypeid"];
21                 }
22                 
23                 //注意System.DBNull.ToString()返回String.Empty
24                 temp.Pname = row["Pname"].ToString();
25                 temp.Pcellphone = row["Pcellphone"].ToString();
26                 temp.Phomephone = row["Phomephone"].ToString();
27                 temp.Ptname = row["Ptname"].ToString();
28                 
29                 //将对象添加到集合
30                 lists.Add(temp);
31             }
32         }
33         return lists;
34     } 
35 
36 ...
37 }

 

 

二、下午《06、dgv控件的属性设置》--《07、绑定下拉列表的数据》

Winform控件DataGridView、ComboBox、ListView、TreeView的数据绑定相关,详见其链接。

1、《电话簿项目》UI 和 数据库表结构

                       

2、窗体 FrmPhoneManager.cs 代码

<1>窗体加载 和 查询

 1 //窗体加载时
 2 private void FrmPhoneManager_Load(object sender, EventArgs e)
 3 {
 4     #region 绑定dgv控件的数据
 5     string sql = @"select pid, ptypeid, pname, pcellphone, phomephone, ptname 
 6                     from phonetype,phonenum where PhoneNum.pTypeId=PhoneType.ptId";//内连接
 7     DataTable dt = SqlHelper.ExceuteTable(sql);
 8     this.dgvList.DataSource = SqlHelper.GetPhoneNumList(dt);//将DataTable转为实体对象列表
 9     #endregion
10 
11     #region 绑定下拉列表的数据
12     DataTable dttype = SqlHelper.ExceuteTable("select ptid, ptname from phonetype");
13     this.cboGroup.DisplayMember = "ptname";//下拉列表显示每个实体的ptname属性值
14     this.cboGroup.ValueMember = "ptid";//选中值SelectValue为实体的ptid属性值
15     this.cboGroup.DataSource = SqlHelper.GetPhoneTypeList(dttype);
16     
17     //这里实际上可以将上面一行集合拷贝一份为下面控件做绑定。但不可以两个下拉列表控件绑定同一个集合,否则选择的时候两个控件一起变动。
18     List<PhoneType> lists = SqlHelper.GetPhoneTypeList(dttype);
19     //在集合中添加“请选择”
20     lists.Insert(0, new PhoneType() { Ptid = -1, Ptname = "请选择" });//插入此项为了去除该列的检索条件
21     this.cboSeaGroup.DisplayMember = "ptname";
22     this.cboSeaGroup.ValueMember = "ptid";
23     this.cboSeaGroup.DataSource = lists;
24     #endregion
25 } 
 1 private void btnSearch_Click(object sender, EventArgs e)
 2 {//点击查询按钮后
 3     string sql = @"select pid, ptypeid, pname, pcellphone, phomephone, ptname 
 4                     from phonetype, phonenum where PhoneNum.ptypeid = PhoneType.ptid";
 5     List<SqlParameter> ps = new List<SqlParameter>();
 6     if(this.cboSeaGroup.Text != "请选择")
 7     {//如果用户做出了联系人组的筛选
 8         sql = sql + " and ptypeid = @id";
 9         ps.Add( new SqlParameter("id", this.cboSeaGroup.SelectedValue));//因为下拉列表选中值就是联系人组的Id
10     }
11 
12     if(!string.IsNullOrEmpty(txtSeaName.Text.Trim()))
13     {//如果用户输入了姓名
14         //模糊查询的%不是在sql语句中添加的
15         sql = sql + " and pname like @pname";
16         //而是在参数中添加的
17         ps.Add(new SqlParameter("pname", "%" + txtSeaName.Text.Trim() + "%"));
18     }
19     //如果用户输入了号码筛选条件,处理同上。
20   
21     DataTable dt = SqlHelper.ExceuteTable(sql, ps.ToArray());
22     this.dgvList.DataSource = SqlHelper.GetPhoneNumList(dt);
23 }

 注意:

(1)ComboBox控件绑定数据源(DataTable或实体集合)之前,最好指定DisplayMember和ValueMember属性。也就是选项应该显示啥,选中后的值是啥。

(2)为ComboBox的数据源插入第一个元素,用来除去此筛选条件。一般第一个元素显示为 “请选择” 或者  “All”

(3)此例中ComboBox控件是绑定数据源的(而不是手动Add项),所以不用担心它会多次加载重复项。(如果是手动Add项,那么每次重新添加前,要先Clear掉)

(4)因为(1)中设定了ValueMember属性,需要时就可以读取ComboBox的SelectedValue属性来得到当前选择项,对应的ptid值(PhoneType表的ptid列)。

(5)参数化查询的字符串模糊查询,不是在sql中添加通配符,而是加到参数值中。

  如sql中“ and  pname like  @pname”   

   ps.Add(new SqlParameter("pname", "%"+  "模糊查询的输入文本"  + "%"));

 

<2>选中表格的行时,将各列值显示到下面可编辑的控件内。

 1 private void dgvList_Click(object sender, EventArgs e)
 2 {//表格被点击后
 3     if (this.dgvList.SelectedRows.Count == 0)
 4     {//如果没有行被选中,不处理
 5         return;
 6     }
 7 
 8     //因为DataGridView绑定的数据源是实体对象集合,所以每行绑定了一个实体对象
 9     PhoneNum obj = this.dgvList.CurrentRow.DataBoundItem as PhoneNum;
10     txtCellPhone.Text = obj.Pcellphone;
11     txtHomePhone.Text = obj.Phomephone;
12     txtName.Text = obj.Pname;
13     
14     //将pid值存储到控件的TAG值中,这样做方便后面保存时Update
15     this.dgvList.Tag = obj.Pid;
16     //cboGroup.Text = obj.Ptname;//推荐使用下面方式显示Ptname,因为更合理
17     cboGroup.SelectedValue = obj.Ptypeid;//指定SelectedValue,可以自动选中对应ComboBox项
18 }

注意:

(1)判断DataGridView是否有行被选中,可以用以下两种方式

  if (this.dgvList.SelectedRows.Count == 0)
  或if (this.dgvList.CurrentRow != null)

(2)本例控件DataGridView绑定的数据源是实体对象集合(这么做后面处理很方便),所以每个行的DataBoundItem属性就是绑定的实体对象。

(3)可以每次在用户点选某行后,把行绑定的实体的主键id存到DataGridView.Tag(这么做后面保存Update时很方便)

(4)可以为ComboBox的SelectedValue赋值(由于之前指定了它的ValueMember属性),使得控件自动选中对应项。

 

<3>点击保存,Update行记录

 1 private void btnSave_Click(object sender, EventArgs e)
 2 {//点击保存按钮,将文本控件内容Update到数据库
 3 
 4     string sql1 = @"update phonenum set ptypeid = @ptypeid ,pname = @pname, 
 5                     pcellphone = @pcellphone, phomephone = @phomephone where pid = @pid";
 6 
 7     SqlParameter[] ps = {   //因为ComboBox绑定的数据源是实体对象集合,所以每个项都是个实体对象。此处用SelectedValue也可以(就不用转换了)。
 8                             new SqlParameter("ptypeid",(this.cboGroup.SelectedItem as PhoneType).Ptid),
 9                             new SqlParameter("pname",txtName.Text.Trim()),
10                             new SqlParameter("pcellphone",txtCellPhone.Text.Trim()),
11                             new SqlParameter("phomephone",txtHomePhone.Text.Trim()),
12                             new SqlParameter("pid",this.dgvList.Tag)//因为之前将pid存到Tag里了
13                          };
14 
15     if (SqlHelper.ExecuteNonQuery(sql1, ps) == 1)
16     {//如果更新OK,重新加载所有记录
17         MessageBox.Show("ok");
18         string sql2 = @"select pid, ptypeid, pname, pcellphone, phomephone, ptname 
19                         from phonetype, phonenum where PhoneNum.pTypeId = PhoneType.ptId";//内连接
20         DataTable dt = SqlHelper.ExceuteTable(sql2);
21         this.dgvList.DataSource = SqlHelper.GetPhoneNumList(dt);
22     }
23 
24 }

 

3、SqlHelper类中的其余重要代码

 1 public static List<PhoneType> GetPhoneTypeList(DataTable dt)
 2 {//DataTable转phoneType集合
 3     List<PhoneType> lists = null;
 4     if (dt.Rows.Count > 0)
 5     {
 6         lists = new List<PhoneType>();
 7         foreach (DataRow row in dt.Rows)
 8         {
 9             PhoneType pty = new PhoneType();
10             pty.Ptid = (int)row["Ptid"];
11             pty.Ptname = row["Ptname"].ToString();
12             lists.Add(pty);
13         }
14     }
15     return lists;
16 }
17 
18 
19 public static int ExecuteNonQuery(string sql, params SqlParameter[] ps)
20 {//执行增加修改和删除
21     using (SqlConnection conn = new SqlConnection(connStr))
22     {
23         conn.Open();
24         SqlCommand comm = new SqlCommand(sql, conn);
25         comm.Parameters.AddRange(ps);
26         return comm.ExecuteNonQuery();
27     }
28 }

 

 

三、《2014-07-12 SQL进阶》上午《01、回顾》

1、可选的多条件检索时,where 1=1的问题。

  多条件检索时,一般UI上有多个可选条件供用户选择,这样的sql语句就需要根据用户的选择进行多条件的拼接。这时很多人喜欢先写一个where 1=1 在前面,然后根据用户的选择在后面拼接 and XXX=@XX。然而这种方式并不高效。

  如果使用这种方式,在数据库中会做全表扫描(对每行数据都进行扫描,比对。),会无法使用索引等优化查询的策略。建的索引将会失效。解决办法如下所示。

 1 //解决办法:
 2 string sql = "select * from TblStudent";
 3 bool hasWhere = false;
 4 if (!string.IsNullOrEmpty(textBox1.Text.Trim()))
 5 {
 6     if (!hasWhere)
 7     {
 8         sql += " where col1 = txt1";
 9         hasWhere = true;
10     }
11     else
12     {
13         sql += " and col1 = txt1";
14     }
15 }
16 
17 if (!string.IsNullOrEmpty(textBox2.Text.Trim()))
18 {
19     if (!hasWhere)
20     {
21         sql += " where col2 = txt2";
22         hasWhere = true;
23     }
24     else
25     {
26         sql += " and col2 = txt2";
27     }
28 }
29 
30 if (!string.IsNullOrEmpty(textBox3.Text.Trim()))
31 {
32     if (!hasWhere)
33     {
34         sql += " where col3 = txt3";
35         hasWhere = true;
36     }
37     else
38     {
39         sql += " and col3 = txt3";
40     }
41 }
42 
43 if (!string.IsNullOrEmpty(textBox4.Text.Trim()))
44 {
45     if (!hasWhere)
46     {
47         sql += " where col4 = txt4";
48         hasWhere = true; 
49     }
50     else
51     {
52         sql += " and col4 = txt4";
53     }
54 }
55 
56 MessageBox.Show(sql);

注意:当然拼接sql的时候,最好使用StringBuilder类

 

posted on 2017-08-18 17:39  困兽斗  阅读(176)  评论(0)    收藏  举报

导航