ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

C#学生管理系统实训:WinForms+SQLite三层架构CRUD与报告撰写指南

C#学生管理系统实训:WinForms+SQLite三层架构CRUD与报告撰写指南 简介这份C#学生管理系统实训报告面向软件专业学生及C#.NET初学者对应《数据库与C#》课程实训场景帮助读者理解如何用C#结合SQL Server完成一个具备学生、教师、管理员三种角色的信息管理系统。报告完整呈现了项目实训目的、系统功能模块划分、总体设计思路与数据库设计涵盖学生表、教师表、课程表、选课表、用户信息表和班级表共6张核心表并给出各表字段类型与说明同时梳理了登录、成绩管理、课程管理、班级管理等模块的文件组织与功能逻辑。资源包为1个doc文档大小约1.68MB内容结构清晰适合作为课程设计、实训报告撰写与数据库应用开发的参考范例。目前已有201人学习可帮助读者快速把握C#数据库项目的整体架构与文档编写方法。1. 一份“C#学生管理系统实训报告.doc.doc”背后到底在练什么如果你手里正躺着一份名为“C#学生管理系统实训报告.doc.doc”的文件先别急着吐槽那个重复的.doc后缀——它大概率是某个同学从模板复制出来、改完内容忘了改名的产物。这个标题真正指向的不是一份文档排版任务而是一套完整的桌面端 CRUD 训练用 C# 加 WinForms 或 WPF把学生、班级、成绩这几张表管起来再配一份能讲清楚设计思路的实训报告。它适合刚学完 C# 基础语法、想找一个能写进简历或交作业的完整项目练手的人也适合带实训课的开发者拿来当参考骨架。核心难点从来不是“怎么连数据库”而是怎么把界面、数据访问、业务校验三层拆干净以及报告里怎么把“我为什么这么设计”讲明白。这一篇就按真实落地顺序从环境搭到报告结构把能抄的代码和能避的坑一次讲透。2. 先把项目骨架定下来WinForms 还是 WPF数据库选哪个动手之前最怕的就是选型反复横跳。我带过几轮实训最常见的翻车现场是第一天用 WinForms 拖控件第二天听说 WPF 更“高级”就重写结果两天都在搭界面。所以这一章先把选型理由讲清楚再给出可直接复用的三层目录结构。2.1 界面框架WinForms 够用WPF 看需求如果你的目标是两周内交出一个能增删改查、能导出成绩的系统WinForms 是更稳的选择。它的拖拽式设计器对新手友好DataGridView 绑定 DataTable 几乎零配置出问题也容易搜到答案。WPF 的优势在样式和数据绑定更灵活但 MVVM 模式对刚接触 C# 的人是一道额外的坎容易把时间耗在命令绑定和依赖属性上。我的建议很直接实训周期少于三周、以功能完整度为第一优先级选 WinForms如果课程明确要求 WPF 或你想顺便练 MVVM再上 WPF。两者在数据访问层和业务层的代码几乎可以复用切换成本主要在前端。2.2 数据库SQL Server LocalDB 与 SQLite 的取舍学生管理系统数据量小不需要考虑高并发。常见做法有两种方案优点缺点适用场景SQL Server LocalDB语法和正式 SQL Server 一致SSMS 可视化需要安装运行时换机器可能连不上学校机房统一环境SQLite单文件、零安装、拷贝即用部分 SQL 语法有差异个人开发、需要随项目打包我一般会选 SQLite因为实训报告经常要连同数据库文件一起交单文件最省事。下面统一用 SQLite 演示换成 LocalDB 只需改连接字符串和少量 SQL。2.3 三层目录结构别把 SQL 写进按钮事件里新手最容易犯的错是在button1_Click里直接拼 SQL。这样写出来的代码改一个字段要翻遍所有窗体。正确的做法是分三层StudentMgr/ ├── Models/ // 实体类对应数据库表 │ └── Student.cs ├── DAL/ // 数据访问层只负责增删改查 │ └── StudentDal.cs ├── BLL/ // 业务层做校验和组合逻辑 │ └── StudentBll.cs └── UI/ // 窗体只负责展示和收集输入 └── MainForm.cs这个结构的好处是UI 层不出现任何 SQL 字符串DAL 层不出现任何 MessageBox。职责一分开调试时能快速定位问题出在哪一层。2.4 建库建表的最小 SQL在项目根目录放一个init.sql第一次运行时执行-- 学生表学号做主键避免重名 CREATE TABLE Student ( StuNo TEXT PRIMARY KEY, -- 学号业务主键 Name TEXT NOT NULL, Gender TEXT CHECK(Gender IN (男,女)), ClassName TEXT, BirthDate TEXT ); -- 成绩表用自增 id外键关联学号 CREATE TABLE Score ( Id INTEGER PRIMARY KEY AUTOINCREMENT, StuNo TEXT NOT NULL, Course TEXT NOT NULL, Points REAL CHECK(Points BETWEEN 0 AND 100), FOREIGN KEY (StuNo) REFERENCES Student(StuNo) );逻辑说明学号用TEXT而不是自增整数是因为真实学号常带字母和年份前缀用自增 id 反而要在界面上多维护一个字段。CHECK约束把性别和分数范围卡在数据库层即使业务层漏校验脏数据也进不来。外键约束在 SQLite 里默认不开启需要在连接字符串或每次连接后执行PRAGMA foreign_keys ON;这一点后面避坑章节会再提。3. 数据访问层怎么写参数化查询与实体映射这一章是整篇的核心代码量最大。目标是把 StudentDal 写成一个“换数据库只改一个文件”的类同时彻底杜绝 SQL 注入。3.1 连接字符串与数据库文件位置// DAL/DbHelper.cs public static class DbHelper { // 数据库文件放在程序运行目录下方便随项目拷贝 private static readonly string DbPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, student.db); public static string ConnStr $Data Source{DbPath};; // 每次取连接时开启外键约束 public static SQLiteConnection GetConn() { var conn new SQLiteConnection(ConnStr); conn.Open(); using (var cmd new SQLiteCommand(PRAGMA foreign_keys ON;, conn)) { cmd.ExecuteNonQuery(); } return conn; } }参数说明AppDomain.CurrentDomain.BaseDirectory指向 exe 所在目录比Environment.CurrentDirectory更可靠因为后者会随“打开文件对话框”等操作改变。把PRAGMA放在GetConn里保证每个连接都生效不用在每个 DAL 方法里重复写。3.2 实体类与 DataTable 的取舍有人喜欢直接用DataTable绑定 DataGridView省去实体类。小项目可以但一旦要做业务校验和单元测试实体类更清晰。我一般定义简单的 POCO// Models/Student.cs public class Student { public string StuNo { get; set; } public string Name { get; set; } public string Gender { get; set; } public string ClassName { get; set; } public DateTime? BirthDate { get; set; } }BirthDate用可空DateTime?因为录入时可能留空用DateTime会默认0001-01-01显示出来很难看。3.3 增删改查四个方法的完整写法// DAL/StudentDal.cs public class StudentDal { // 查询支持按姓名或学号模糊搜索 public ListStudent Query(string keyword ) { var list new ListStudent(); using (var conn DbHelper.GetConn()) using (var cmd conn.CreateCommand()) { // 参数化绝不拼接字符串 cmd.CommandText SELECT StuNo,Name,Gender,ClassName,BirthDate FROM Student WHERE StuNo LIKE kw OR Name LIKE kw ORDER BY StuNo; cmd.Parameters.AddWithValue(kw, $%{keyword}%); using (var reader cmd.ExecuteReader()) { while (reader.Read()) { list.Add(new Student { StuNo reader.GetString(0), Name reader.GetString(1), Gender reader.IsDBNull(2) ? null : reader.GetString(2), ClassName reader.IsDBNull(3) ? null : reader.GetString(3), BirthDate reader.IsDBNull(4) ? (DateTime?)null : DateTime.Parse(reader.GetString(4)) }); } } } return list; } // 新增返回受影响行数调用方据此判断成功与否 public int Insert(Student s) { using (var conn DbHelper.GetConn()) using (var cmd conn.CreateCommand()) { cmd.CommandText INSERT INTO Student(StuNo,Name,Gender,ClassName,BirthDate) VALUES(no,name,gender,cls,birth); cmd.Parameters.AddWithValue(no, s.StuNo); cmd.Parameters.AddWithValue(name, s.Name); cmd.Parameters.AddWithValue(gender, (object)s.Gender ?? DBNull.Value); cmd.Parameters.AddWithValue(cls, (object)s.ClassName ?? DBNull.Value); cmd.Parameters.AddWithValue(birth, s.BirthDate?.ToString(yyyy-MM-dd) ?? (object)DBNull.Value); return cmd.ExecuteNonQuery(); } } // 修改以学号为条件更新其余字段 public int Update(Student s) { using (var conn DbHelper.GetConn()) using (var cmd conn.CreateCommand()) { cmd.CommandText UPDATE Student SET Namename,Gendergender, ClassNamecls,BirthDatebirth WHERE StuNono; cmd.Parameters.AddWithValue(no, s.StuNo); cmd.Parameters.AddWithValue(name, s.Name); cmd.Parameters.AddWithValue(gender, (object)s.Gender ?? DBNull.Value); cmd.Parameters.AddWithValue(cls, (object)s.ClassName ?? DBNull.Value); cmd.Parameters.AddWithValue(birth, s.BirthDate?.ToString(yyyy-MM-dd) ?? (object)DBNull.Value); return cmd.ExecuteNonQuery(); } } // 删除先删成绩再删学生避免外键报错 public int Delete(string stuNo) { using (var conn DbHelper.GetConn()) using (var tran conn.BeginTransaction()) { try { using (var cmd conn.CreateCommand()) { cmd.Transaction tran; cmd.CommandText DELETE FROM Score WHERE StuNono; cmd.Parameters.AddWithValue(no, stuNo); cmd.ExecuteNonQuery(); cmd.CommandText DELETE FROM Student WHERE StuNono; cmd.ExecuteNonQuery(); } tran.Commit(); return 1; } catch { tran.Rollback(); throw; } } } }逻辑说明查询用LIKE kw配合%通配符一个参数同时匹配学号和姓名界面上只需要一个搜索框。新增和修改里可空字段统一转成DBNull.Value否则 SQLite 会把 C# 的null当成空字符串处理导致IsDBNull判断失效。删除操作放在事务里先删子表再删主表这是外键约束下最稳妥的顺序。参数说明AddWithValue虽然方便但在某些数据库上会因类型推断导致索引失效。SQLite 场景下影响不大如果换成 SQL Server 且字段有索引建议显式指定SQLiteParameter的DbType。3.4 业务层的校验放在哪// BLL/StudentBll.cs public class StudentBll { private readonly StudentDal _dal new StudentDal(); public string Add(Student s) { if (string.IsNullOrWhiteSpace(s.StuNo)) return 学号不能为空; if (string.IsNullOrWhiteSpace(s.Name)) return 姓名不能为空; if (s.StuNo.Length 20) return 学号长度不能超过 20; // 查重学号已存在则拒绝 var exist _dal.Query(s.StuNo) .Any(x x.StuNo s.StuNo); if (exist) return 该学号已存在; return _dal.Insert(s) 0 ? null : 新增失败; } }返回string而不是抛异常是为了让 UI 层直接MessageBox.Show(result)新手更容易理解。如果团队有统一异常规范改成抛自定义异常也可以但要在 UI 层统一捕获。4. 界面与数据联动DataGridView 绑定和输入校验界面层最容易写出“面条代码”这一章把绑定、刷新、校验三件事拆开讲。4.1 DataGridView 绑定 List 而不是 DataTable// UI/MainForm.cs private readonly StudentBll _bll new StudentBll(); private void RefreshGrid(string keyword ) { // 用 BindingList 才能在增删后自动通知界面刷新 var list _bll.Query(keyword); gridStudents.AutoGenerateColumns true; gridStudents.DataSource new BindingListStudent(list); }逻辑说明直接赋ListT也能显示但增删后不会自动刷新需要重新赋值。BindingListT实现了IBindingList配合DataGridView能获得更好的编辑体验。AutoGenerateColumns true让列名自动取属性名如果要做中文表头就在设计器里手动加列并设置DataPropertyName。4.2 新增与修改共用一个窗体的技巧新手常写两个几乎一样的窗体改一个字段要改两处。我的做法是给编辑窗体加一个IsEdit标志public partial class StudentEditForm : Form { public Student Result { get; private set; } private readonly bool _isEdit; public StudentEditForm(Student stu null) { InitializeComponent(); _isEdit stu ! null; if (_isEdit) { txtStuNo.Text stu.StuNo; txtStuNo.ReadOnly true; // 学号是主键编辑时不允许改 txtName.Text stu.Name; // ... 其余字段回填 } Text _isEdit ? 编辑学生 : 新增学生; } private void btnOk_Click(object sender, EventArgs e) { // 收集输入交给业务层校验 Result new Student { StuNo txtStuNo.Text.Trim(), Name txtName.Text.Trim(), Gender rbMale.Checked ? 男 : 女, ClassName txtClass.Text.Trim(), BirthDate dtpBirth.Checked ? dtpBirth.Value : (DateTime?)null }; DialogResult DialogResult.OK; } }参数说明txtStuNo.ReadOnly true是关键主键一旦允许修改就要处理“改学号后成绩表外键怎么办”的连锁问题实训阶段直接禁掉最省心。dtpBirth.Checked配合ShowCheckBox true让用户可以选择不填生日。4.3 删除前必须二次确认private void btnDelete_Click(object sender, EventArgs e) { if (gridStudents.CurrentRow?.DataBoundItem is not Student s) return; var ok MessageBox.Show( $确定删除学号 {s.StuNo} 的学生及其成绩吗, 确认删除, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (ok ! DialogResult.Yes) return; try { _bll.Delete(s.StuNo); RefreshGrid(); } catch (Exception ex) { MessageBox.Show(删除失败 ex.Message); } }逻辑说明DataBoundItem拿到的是绑定的实体对象比从单元格里读字符串再拼对象更可靠。删除涉及两张表用 try-catch 兜住避免外键异常直接崩掉程序。5. 避坑与排查那些让实训报告返工的问题这一章按“现象 → 原因 → 解决”记录我踩过的五个坑每一个都真实消耗过时间。5.1 现象程序在自己电脑能跑拷到别人机器报“找不到 student.db”原因数据库文件路径写成了绝对路径或者文件没有设置为“始终复制”。解决用AppDomain.CurrentDomain.BaseDirectory拼路径并在解决方案资源管理器里把student.db的“复制到输出目录”设为“如果较新则复制”。如果数据库是首次运行创建的就在程序启动时判断文件是否存在不存在则执行建表 SQL。5.2 现象删除学生时报“FOREIGN KEY constraint failed”原因SQLite 的外键约束默认关闭但一旦通过PRAGMA开启删除主表记录时如果子表还有引用就会失败。解决按 3.3 的写法在事务里先删Score再删Student。另一种做法是建表时加ON DELETE CASCADE但级联删除对新手来说不够直观出问题时更难排查我倾向于显式删除。5.3 现象DataGridView 里修改了单元格数据库却没变原因DataGridView默认只是把数据展示出来编辑后没有回写数据库的逻辑。解决要么把单元格设为只读所有修改走编辑窗体要么监听CellEndEdit事件在事件里调用 DAL 的 Update。实训项目推荐前者逻辑更清晰也避免用户误改。5.4 现象搜索框输入%或_后查询结果异常原因这两个字符在 SQL 的LIKE里是通配符用户输入%会匹配所有记录。解决在拼接参数前做转义把%替换成\%_替换成\_并在 SQL 里加ESCAPE \。代码片段var safe keyword.Replace(\\, \\\\) .Replace(%, \\%) .Replace(_, \\_); cmd.CommandText ... WHERE StuNo LIKE kw ESCAPE \\ ...; cmd.Parameters.AddWithValue(kw, $%{safe}%);5.5 现象实训报告里代码截图和正文对不上原因先写报告后改代码或者截图时用的是旧版本。解决把报告里的关键代码用 Markdown 代码块贴出来而不是截图。代码块可以直接从源文件复制改代码时同步改报告版本一致。如果学校要求截图就在最终定稿后统一截一次并在图注里写明对应的类名和方法名。6. 实训报告怎么写才像做过项目而不是抄的最后一章落到报告本身。很多人代码写得不错报告却写成“功能列表 截图”读起来像说明书。一份能拿得出手的实训报告核心是讲清楚“我遇到了什么问题、怎么权衡、为什么这么选”。6.1 报告结构建议不要用“第一章 绪论、第二章 需求分析”这种模板硬套。我一般按这个顺序写项目目标与功能边界明确说清楚做了哪几个模块没做哪几个为什么不做。技术选型与理由WinForms 还是 WPF、SQLite 还是 SQL Server各写两三条理由。数据库设计贴建表 SQL说明主键、外键、约束的设计意图。关键代码与设计思路挑 DAL 的参数化查询、BLL 的校验、UI 的绑定三处展开。遇到的问题与解决直接复用第 5 章的避坑记录但要用自己的话重写。不足与改进方向诚实写“没有做分页”“没有做权限”比硬吹功能全更加分。6.2 用一张表把测试用例写清楚报告里加一张测试表比大段文字描述更有说服力用例编号操作输入预期结果实际结果T01新增学生学号 2024001姓名张三列表出现该记录通过T02新增重复学号学号 2024001提示“该学号已存在”通过T03删除有成绩的学生学号 2024001学生和成绩同时删除通过T04搜索特殊字符输入%不匹配全部记录通过6.3 一个让报告加分的细节把“后悔药”写进去我在报告里会专门留一小节叫“如果重做我会怎么改”。比如把连接字符串抽到配置文件而不是硬编码把 DataGridView 的列宽和格式在CellFormatting事件里统一处理给 BLL 加单元测试而不是全靠手点。这一节不需要长三四条就够但能明显看出你是真的动手做过而不是复制粘贴。6.4 最后说一个习惯每次改完代码先跑一遍新增、修改、删除、搜索四条主流程再截图更新报告。这个习惯帮我省掉了无数次“报告和代码对不上”的返工。实训项目不难难的是把每一步都留下可复现的记录。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表