C#班级通讯录管理系统实训代码解析:OleDb、DataSet与WinForms实战
2026/9/19 13:13:30 网站建设 项目流程

简介:C#班级通讯录管理系统实训报告是一份面向C#初学者的实践教学文档,完整呈现了利用Visual Studio 2005与.NET Framework开发班级通讯录系统的全过程。报告以2013年春季学期实训为背景,涵盖实训主题、目的要求、重要知识点和具体实现步骤,并从系统总体结构设计、功能模块划分、流程图绘制和数据库表结构设计入手,围绕显示、查询、添加、修改、删除等核心功能,详细讲解了ADO.NET中.NET Framework数据提供程序与DataSet两大组件的使用方法,以及如何通过OleDb连接Access数据库。资源包内仅含1个doc文档,大小551KB,已有138人下载学习。文档还给出了登录模块与主界面设计思路,数据库字段包括学号、姓名、班级、性别、年龄、电话、地址、邮政编码和邮箱等,并附有基于System.Data.OleDb的完整代码示例,覆盖连接字符串定义、数据集获取与SQL命令执行等典型操作。整份报告结构清晰、代码可直接参考,对于完成课程设计、复习C#数据库编程或编写同类实训报告都具有实用价值。

1. 一份 C# 班级通讯录实训报告,为什么值得拆开看

2013 年的 Visual Studio 2005 加 Access 组合,放在今天看像是上个时代的东西,但这套《C#班级通讯录管理系统实训报告》恰恰是大量 C# 老项目的缩影:OleDb 连接、DataSet 离线数据、WinForms 窗体跳转、字符串拼 SQL。你如果正在维护一套十年前遗留的 C# 教务系统,或者刚接手一个用 Access 当库的小工具,这份报告里的代码就是你最先遇到的形态。报告本身是课程作业结构:总体图、流程图、数据库设计、逐窗体代码,但把它当成代码仓库来读,能看到一条清晰的主线:DataAccess 类做数据库统一入口,登录窗体做身份校验,主窗体用 MenuStrip 路由到功能窗体,增删改查分布在三个窗体里。下面按这条主线逐段拆解,顺带指出哪些写法能直接抄,哪些必须在真实项目里改掉。

2. 数据访问层:OleDb 连接封装与 DataSet 取数的取舍

把数据库操作集中在 DataAccess 类里,是这份报告最值得保留的做法。数据访问层独立之后,后续加窗体只需要DataAccess data = new DataAccess(); data.dataCon();就能在任意位置拿到连接和数据,不需要每处新写一遍连接逻辑。

2.1 连接字符串与 Jet 数据库引擎的坑

报告把连接定义放在dataCon()方法里,指向程序运行目录下的 Access 文件:

private string strDSN; OleDbConnection oledbCon; public void dataCon() { strDSN = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath.ToString() + "\\class.mdb"; oledbCon = new OleDbConnection(strDSN); }

Provider=Microsoft.Jet.OLEDB.4.0指定的是 Jet 引擎,负责读写 Access 2003 及更早版本的 .mdb 文件。Data Source后接物理文件路径,这里用Application.StartupPath拼相对路径,意味着 class.mdb 必须和 exe 在同一目录,否则连接直接失败。这是 Access 程序最常见的部署问题,发布时漏掉数据库文件的现象非常普遍,后面会专门讲验证方法。

注意一个隐性问题:dataCon()没有返回值,也不检查oledbCon是否成功创建,调用方必须保证先执行它再执行其它方法。后面所有窗体的代码都遵循data.dataCon(); data.getDataset(strSql);的固定顺序,一旦漏掉就会抛 NullReferenceException。

2.2 getDataset 与 DataSet 的选型理由

查询方法用 OleDbDataAdapter 填充 DataSet:

public DataSet getDataset(string sql) { DataSet ds = new DataSet(); oledbCon.Open(); try { OleDbCommand oledbCom = new OleDbCommand(sql, oledbCon); OleDbDataAdapter oledbDa = new OleDbDataAdapter(oledbCom); oledbDa.Fill(ds); return ds; } catch (Exception ex) { throw new Exception(ex.ToString()); } finally { oledbCon.Close(); } }

执行顺序是:打开连接、创建 OleDbCommand 承载 SQL、用 OleDbDataAdapter.Fill 填数据、finally 里关连接。finally 是必须的,不管查询成功还是抛异常,连接都要归还给连接池。这里不用 DataReader 而选 DataSet,背后是绑定场景的考虑:

对比点DataSetDataReader
数据形态内存中的离线数据集只读向前的数据库游标
连接占用Fill 后可断开需保持连接直到读取完
多表支持支持多个 DataTable 与关系单结果流
绑定 DataGridView可直接作为 DataSource需要逐行转 DataTable
适用场景窗体频繁开关、数据回显轻量快速读取

实训系统每个窗体都要把结果绑定到 DataGridView,DataSet 取数后立刻 Close 连接,在多窗体频繁切换时更不容易造成连接泄漏。

2.3 sqlExec 的参数与双层异常处理

写操作方法同样值得推敲:

public bool sqlExec(string sql) { try { oledbCon.Open(); } catch { MessageBox.Show("数据库未连接"); } try { OleDbCommand oledbCom = new OleDbCommand(sql, oledbCon); oledbCom.ExecuteNonQuery(); return true; } catch (Exception ex) { MessageBox.Show(ex.Message, "提示"); return false; } finally { oledbCon.Close(); } }

ExecuteNonQuery用于执行 INSERT、UPDATE、DELETE 这类不返回结果集的命令,返回受影响行数。这里用两个 try/catch 分开处理:外层捕获连接失败,内层捕获 SQL 执行错误。但从排错角度看不理想,连接失败和 SQL 失败的提示在界面上区分度太低。实践做法是把外层 catch 改成throw new Exception("数据库连接失败,请检查 class.mdb 是否就位", ex),让日志里能直接定位阶段。

3. 登录与主界面:WinForms 窗体传参和菜单路由的常见写法

登录窗体承担整个程序的入口校验,button1_Click 先检查两个文本框非空,再查 userslogin 表确认身份。

3.1 登录校验逻辑与三个边界

核心代码如下:

private void button1_Click(object sender, EventArgs e) { string username, password; string strSql = ""; DataSet ds; if (textBox1.Text.Trim() != "" && textBox2.Text.Trim() != "") { username = textBox1.Text.Trim(); password = textBox2.Text.Trim(); strSql = "select * from userslogin where userId='" + username + "' and userPwd='" + password + "'"; DataAccess data = new DataAccess(); data.dataCon(); ds = data.getDataset(strSql); if (ds.Tables[0].Rows.Count == 1) { Main fMain = new Main(); fMain.Show(); this.Hide(); } else { MessageBox.Show("用户名或密码错误!", "登录失败"); } } }

判定逻辑是行数为 1 即成功,.Trim()把首尾空格去掉,这属于 WinForms 里基础的防御性写法。三个值得注意的边界:

第一,字符串拼接 SQL,输入' or '1'='1这类内容时登录校验会被绕过,这是典型注入入口,下一章专门展开。

第二,登录成功后用fMain.Show()非模态显示,登录窗体this.Hide()隐藏而不 Close。一旦 Close,登录窗体资源被释放,主窗体的 Application 消息循环可能受影响。好在这里主窗体关闭时调用了Application.Exit()强制结束进程,小工具里可接受,但不优雅。C# 窗体生命周期管理在真实项目中通常会引入一个 Program.cs 层的全局导航控制。

第三,UsersLogin_Activated事件里调用textBox1.Focus()把光标定位到用户名输入框,这类小交互在实训代码里很少见,算是作者主动加的体验优化。

3.2 主界面 MenuStrip 到功能窗体的路由

主界面代码不含业务逻辑,全部是菜单事件路由:

private void 添加联系人ToolStripMenuItem_Click(object sender, EventArgs e) { AddLinkman frm2 = new AddLinkman(); frm2.Show(); } private void 删除联系人ToolStripMenuItem_Click(object sender, EventArgs e) { ManageLinkman frm4 = new ManageLinkman(); frm4.Show(); } private void 修改联系人ToolStripMenuItem_Click(object sender, EventArgs e) { ManageLinkman frm3 = new ManageLinkman(); frm3.Show(); }

删除、修改、查询三个菜单都打开同一个 ManageLinkman 窗体,这是有意义的设计决策:三者第一步都是显示联系人列表并定位目标记录,后续动作由窗体内的按钮触发。与其复制三个近乎相同的列表窗体,不如一个窗体承载三种操作。事件方法名和菜单项一一对应是设计器自动生成的,自己手写 MenuStrip 时双击菜单项就会跳转到 Click 事件,不需要手动挂委托。窗口实例变量名 frm3/frm4 不一致只是命名习惯问题,不影响运行。

4. 增删改查落地:条件拼接、DataGridView 与 SQL 注入边界

ManageLinkman 是整份代码里信息量最大的窗体,同时承担条件查询、删除选中行、把选中行转给修改窗体三个职责。

4.1 条件组合查询与 where 1=1 写法

查询按钮的核心是动态拼接条件:

string conditon = ""; if (textBox1.Text != "") { conditon += "and stuNo='" + textBox1.Text + "'"; } if (textBox2.Text != "") { conditon += "and stuName='" + textBox2.Text + "'"; } if (textBox3.Text != "") { conditon += "and classId='" + textBox3.Text + "'"; } if (comboBox1.Text != "") { conditon += "and Sex='" + comboBox1.Text + "'"; } strSql = "select stuNo as 学号, stuName as 姓名, Sex as 性别, birthday as 出生日期, " + "classId as 班级, tel as 电话, address as 家庭地址, postalcode as 邮政编码, " + "email as 电子邮箱, remark as 备注 from usersinfo where 1=1" + conditon;

两个关键点。第一是列别名,查询列全部用as 中文别名重命名,DataGridView 绑定后表头直接显示“学号、姓名、性别”,省去手动设置列标题的步骤,这是数据展示很省事的做法,代价是 SQL 里字段名和界面列名之间的映射关系需要维护。第二是where 1=1恒真条件,后面每个分支统一用and前缀拼接,不需要判断是否是第一个条件,动态查询的经典写法,性能影响可以忽略,优化器会直接处理掉。

但拼接方式本身有问题。所有输入都用单引号包住,输入含单引号就改变 SQL 语义。比如姓名框输入张三' or '1'='1,条件变成stuName='张三' or '1'='1',整表被返回。数据量小看不出问题,生产环境就是标准的 SQL 注入入口。改成参数化也不复杂:

strSql = "select stuNo as 学号, stuName as 姓名, Sex as 性别, birthday as 出生日期, " + "classId as 班级, tel as 电话, address as 家庭地址, postalcode as 邮政编码, " + "email as 电子邮箱, remark as 备注 from usersinfo " + "where 1=1 and stuNo=@stuNo and stuName=@stuName"; OleDbCommand cmd = new OleDbCommand(strSql, oledbCon); cmd.Parameters.Add("@stuNo", OleDbType.VarChar).Value = textBox1.Text.Trim(); cmd.Parameters.Add("@stuName", OleDbType.VarChar).Value = textBox2.Text.Trim();

注意 OleDb 参数占位符同样是@名称,但参数只能按位置绑定,名称不能复用。如果同一 SQL 里两次出现@stuName,OleDb 会按出现顺序传相同索引的参数,这与 SqlClient 的行为有明显差异,迁移数据库时要格外留意。

提示:OleDb 参数不支持像 SqlClient 那样按名复用,同一个参数名出现两次会把第二个值覆盖到第一个位置,排查时优先怀疑参数顺序。

4.2 删除、修改与静态属性跨窗体传值

删除按钮直接取当前选中行第一列的值拼 DELETE:

string strSql; DataAccess data = new DataAccess(); strSql = "delete from usersinfo where stuNo ='" + dataGridView1.CurrentRow.Cells[0].Value.ToString() + "'"; data.dataCon(); if (data.sqlExec(strSql)) { MessageBox.Show("删除成功!"); } else { MessageBox.Show("删除失败!"); }

dataGridView1.CurrentRow.Cells[0].Value取的是当前选中行第一列单元格值,查询结果第一列是 stuNo,所以拿到的是学号。执行删除后界面不会自动刷新,需要重新点一次查询按钮,或手动再次调用查询逻辑,这是个使用细节。

修改按钮通过静态属性跨窗体传值:

public static string stuNo; public static string StuNo { get { return stuNo; } set { stuNo = value; } } // 在 ManageLinkman 按钮事件中 ManageLinkman.StuNo = dataGridView1.CurrentRow.Cells[0].Value.ToString(); EditLinkman s1 = new EditLinkman(); s1.Show();

这是实训代码里最值得学习的设计点。静态属性 StuNo 生命周期是整个进程,不随窗体实例销毁,因此值能在 ManageLinkman 和 EditLinkman 之间可靠传递。代价是全局可见性,两个窗体同时操作时会互相覆盖,比事件委托或构造函数传参更脆弱。小工具里这样做没问题,但 C# 高级编程里通常会考虑改成事件驱动或使用窗体间数据上下文,以便控制访问范围。

4.3 添加窗体与表单验证粒度

添加联系人的 INSERT 语句:

strSql = "insert into usersinfo(stuNo,stuName,classId,birthday,tel,postalcode,email,address,remark,Sex) values ('" + textBox1.Text + "','" + textBox2.Text + "','" + textBox3.Text + "','" + textBox4.Text + "','" + textBox5.Text + "','" + textBox6.Text + "','" + textBox7.Text + "','" + textBox8.Text + "','" + textBox9.Text + "','" + comboBox1.Text + "')";

字段顺序和 values 顺序一一对应。这里暴露的问题是表单验证粒度过粗:只判断文本框非空,没有判断学号是否已存在。如果数据库没有给 stuNo 设主键,重复数据会直接插入,所以实训里应该把 stuNo 设为主键,或者在添加前先执行一次存在性检查。

4.4 实训代码里的两处典型 SQL 错误

修改窗体的 UPDATE 语句里藏着两个问题,第一处是表名拼写:

strSql = "update uesrsinfo set ... where stuNo ='" + ManageLinkman.stuNo + "'";

uesrsinfousersinfo的错误键入。因为用的是字符串拼接,拼出的 SQL 在运行时才报“表名无效”,编译器不会提前发现。这类错误在实训代码里很典型,最可靠的排查方式是把拼出来的 strSql 弹窗显示或写进日志,看实际 SQL 再对表名。

第二处是末位字段闭合后多了逗号:

"... email='" + textBox7.Text + "',remark='" + textBox9.Text + "',where stuNo ='" ...

remark字段值闭合后多出,where,直接语法报错。拿到这份 doc 时要先通读所有 SQL,把明显错误的逗号和表名清掉再运行,这是照抄实训代码前的基本功课。

5. 把 Access 换成 SQL Server:OleDb 到 SqlClient 的迁移技巧

数据访问层全部基于 OleDb,换数据库改造成本不大,因为业务窗体只依赖 DataAccess 的方法签名,连接字符串和数据提供程序是主要变化点。

5.1 连接字符串与类型适配

对比两种连接的写法:

// Access (Jet) "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + "\\class.mdb" // SQL Server 2008+ 用 SqlClient "Server=.;Database=ClassDB;User Id=sa;Password=你的密码;"

Provider 和 Data Source 换成 Server 和 Database,OleDbConnection 换成 SqlConnection。如果不想逐窗体改代码,可以只在 DataAccess 内部替换。换完后有一个容易忽略的差异:Jet 里日期用#2024-01-01#包裹,SQL Server 是'2024-01-01'。原报告 birthday 直接拿文本框字符串拼 SQL,在 Access 宽松类型转换下能运行,SQL Server 会更严格。

5.2 连接自检与字段级验证

迁移后建议写一个自检方法,放在窗体 Load 事件里确认数据库文件在不在:

public bool CheckConnection() { try { dataCon(); oledbCon.Open(); using (OleDbCommand cmd = new OleDbCommand("select count(*) from usersinfo", oledbCon)) { int count = (int)cmd.ExecuteScalar(); return count >= 0; } } catch { return false; } finally { oledbCon.Close(); } }

ExecuteScalar返回第一行第一列的值,count(*) 拿到的就是联系人总数。这个自检能同时验证连接串、文件路径和表是否存在,日志里记录一条连接状态。字段适配还有两项要注意:Access 的“是/否”字段绑到 DataGridView 后显示 True/False,需要在列格式化事件里转成“是/否”;Access 的 memo 类型对应 SQL Server 的 ntext 或 nvarchar(max),Jet 对末尾空格会做截断,SQL Server 不会,where tel='13801380000'这类等值查询在两边结果可能不同。验收检查点放在三个位置:按学号、姓名、性别组合查询能否精确命中,修改后值是否在列表里更新,删除后行数是否减少。这三个检查点对应用户对增删改查最直接的预期,走完一遍,四个基础模块的回归就完成了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询