尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C#课程管理系统开发详解:WinForms与SQL Server的数据库设计及权限控制

C#课程管理系统开发详解:WinForms与SQL Server的数据库设计及权限控制 简介一份面向高校C#课程设计和期末大作业的课程管理系统完整源代码基于Visual Studio 2017与SQL Server数据库环境开发。系统从学生、教师、管理员三个角色出发完整实现了学生查询开课、选课退课、维护个人信息、查看教师信息教师为学生课程评分管理员统一管理课程、安排任课教师、维护学生与教师信息等核心功能业务流程闭环适合期末答辩演示、毕业设计参考或初学者模仿学习。压缩包共一百一十一个文件核心逻辑集中在三十五个C#源文件中配合十六个窗体资源文件、五十一张界面截图以及解决方案与工程文件清晰呈现代码与界面对应关系整体仅四点五九兆字节方便下载使用。已有三百四十九人学习浏览对需要快速完成同类课设任务的同学具有直接借鉴价值。1. C#课程管理系统期末大作业的完整拆解与复现思路这门课设几乎囊括了 C# WinForms 加 SQL Server 的常见考点多角色登录、DataGridView 数据绑定、增删改查、外键关联、选课与退课的并发边界。拿到这套 C# 期末大作业课程管理系统源码时我先扫了一遍文件列表发现核心逻辑并不在窗体设计器里而在 DBHelper.cs 和那几个业务 Form 的按钮事件中。项目基于 VS2017 与 SQL ServerC# 语言实现角色分为学生、教师、管理员学生可查课、选课、退课、维护个人信息、查询教师信息教师可给学生打分管理员可管理课程、安排授课教师、维护学生与教师信息。适合做课程设计参考也适合想搞懂 WinForms 分层写法的开发者。2. 数据库表设计与 DBHelper 封装先把地基打牢2.1 五张核心表的字段规划课设系统最常见的败笔是选课记录表里没有复合主键导致同一学生重复选同一门课时数据冗余。我对照源码中的数据库设计思路整理出下面这套表结构实际建库时可以按这个骨架调整字段名。表名关键字段说明StudentSId, SName, SNo, SPassword, SMajor学生信息SNo 唯一TeacherTId, TName, TNo, TPassword, TTitle教师信息CourseCId, CName, CHours, CCredit, TId课程信息TId 外键关联教师SCSId, CId, Score选课及成绩表复合主键 (SId, CId)AdminAId, AName, APassword管理员登录信息选课表 SC 是整套系统的枢纽学生选课时插入记录退课时删除记录教师打分时更新 Score 字段。关键设计点在于把 Score 直接放在 SC 表中而不是单独建成绩表——课程设计级别没有必要拆分数线查询成绩时少一次 JOIN成绩字段也天然跟随选课记录存在。2.2 建表 SQL 与主外键约束CREATE TABLE Student ( SId INT IDENTITY(1,1) PRIMARY KEY, SNo VARCHAR(20) NOT NULL UNIQUE, SName NVARCHAR(20) NOT NULL, SPassword VARCHAR(32) NOT NULL DEFAULT 123456, SMajor NVARCHAR(50) ); CREATE TABLE Teacher ( TId INT IDENTITY(1,1) PRIMARY KEY, TNo VARCHAR(20) NOT NULL UNIQUE, TName NVARCHAR(20) NOT NULL, TPassword VARCHAR(32) NOT NULL DEFAULT 123456, TTitle NVARCHAR(20) ); CREATE TABLE Course ( CId INT IDENTITY(1,1) PRIMARY KEY, CName NVARCHAR(50) NOT NULL, CHours INT, CCredit DECIMAL(3,1), TId INT NULL, CONSTRAINT FK_Course_Teacher FOREIGN KEY (TId) REFERENCES Teacher(TId) ); CREATE TABLE SC ( SId INT NOT NULL, CId INT NOT NULL, Score DECIMAL(5,2) NULL, PRIMARY KEY (SId, CId), FOREIGN KEY (SId) REFERENCES Student(SId), FOREIGN KEY (CId) REFERENCES Course(CId) );参数说明SNo 用 VARCHAR 而不用 INT学号经常以 0 开头转成 INT 会丢前导零这是新手常见错误。SPassword 设默认值 123456管理员批量导入学生信息后不用逐个设置初始密码。SId 和 TId 用 IDENTITY 自增业务上不关心具体数字只做关联使用。Course.TId 允许 NULL因为存在没有安排教师的课程管理员批量建课时可以先建课程再安排教师。2.3 DBHelper 里到底该放什么源码中的 DBHelper.cs 是访问数据库的统一入口我建议把连接管理、通用查询、通用增删改三部分都收进来。App.config 中的连接字符串指向本机 SQL Server 实例常见写法如下connectionStrings add nameCourseDB connectionStringData Source.;Initial CatalogCourseManage;Integrated SecurityTrue; providerNameSystem.Data.SqlClient / /connectionStrings说明Data Source 为本机默认实例如果安装了命名实例则需要写成 计算机名\实例名。Integrated SecurityTrue 表示使用 Windows 身份验证部署到其他机器时不需要改密码。如果学校机房环境要求 SQL Server 身份验证则改为 User IDsa;Passwordxxx。DBHelper 的典型封装如下public static class DBHelper { private static string connStr ConfigurationManager.ConnectionStrings[CourseDB].ConnectionString; public static DataTable Query(string sql, SqlParameter[] paras null) { using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (paras ! null) cmd.Parameters.AddRange(paras); SqlDataAdapter da new SqlDataAdapter(cmd); DataTable dt new DataTable(); da.Fill(dt); return dt; } } public static int ExecuteNonQuery(string sql, SqlParameter[] paras null) { using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { conn.Open(); if (paras ! null) cmd.Parameters.AddRange(paras); return cmd.ExecuteNonQuery(); } } }这里有个细节值得注意每次打开新连接而不是共用一个静态连接对象。SQL Server 连接池默认开启频繁打开关闭连接的成本远低于维护一个长期占用且状态不可控的全局连接。课上很多同学图省事把 conn 设为静态字段结果程序长时间运行后连接状态失效调试时浪费大量时间。所有 SQL 语句一律用 SqlParameter 传参拼接字符串会导致引号闭合问题和 SQL 注入风险——课程管理系统属于典型的教学演示项目期末检查时老师很可能故意输入 1 OR 11 测试你的查询代码。3. 多角色登录与权限路由从登录窗体重定向到不同主窗体3.1 登录验证的字段冗余设计源码中登录相关的文件有两个Form登录.cs 和 学生登录.Designer.cs说明登录窗体同时处理了三种角色验证。先看核心的登录按钮事件代码思路private void btnLogin_Click(object sender, EventArgs e) { string role cmbRole.SelectedItem.ToString(); string username txtUsername.Text.Trim(); string password txtPassword.Text.Trim(); if (username || password ) { MessageBox.Show(用户名和密码不能为空); return; } if (role 学生) { string sql SELECT SId, SName FROM Student WHERE SNono AND SPasswordpwd; SqlParameter[] paras { new SqlParameter(no, username), new SqlParameter(pwd, password) }; DataTable dt DBHelper.Query(sql, paras); if (dt.Rows.Count 0) { CurrentUser.SId Convert.ToInt32(dt.Rows[0][SId]); CurrentUser.Name dt.Rows[0][SName].ToString(); new FormStudentMain().Show(); this.Hide(); } else { MessageBox.Show(学号或密码错误); } } else if (role 教师) { // 查询 Teacher 表逻辑同上 } else if (role 管理员) { // 查询 Admin 表进入管理员主窗体 } }角色选择用 ComboBox三种身份共用一个登录窗体是课设演示阶段的常见做法毕竟不需要处理并发登录同一账号的问题。登录成功后把用户 ID 和姓名存入一个静态上下文类 CurrentUser后续窗体的所有 SQL 查询都可以直接引用当前用户 ID这是权限路由的第一步。3.2 为什么不能只靠界面隐藏来限制功能很多同学做多角色系统时直接在主窗体界面上对管理员隐藏增删改按钮凑合起来就是一个“看起来已经区分权限”的系统。但这样做在数据库层面没有任何约束——只要把按钮隐藏逻辑去掉学生照样能执行管理员操作。这套源码的做法是在每个业务窗体加载时再校验一次当前用户角色比如 Form课程管理.cs 的 Load 事件中判断 CurrentUser.Role ! 管理员 则直接关闭窗体。界面控制负责体验而每次操作前重新校验身份才能保证至少在当前程序运行的会话里角色边界不会因为界面状态变化而被绕过。权限控制的核心落点有几个选课操作必须检查当前登录的学生身份拿到 SId不能从前台传入任意学号打分操作必须限定为当前教师所授课程的选课记录管理员维护教师信息时删除操作要处理该教师名下是否已有课程否则外键约束直接报错。源码中这三条路径覆盖了系统的主要业务风险面。4. 学生选课退课与教师打分事务、约束和幂等验证4.1 选课操作的正确写法选课逻辑是这门课设中技术含量最高的部分。学生点击选课按钮时前端拿到的是 DataGridView 当前选中行的 CId但数据源中应该同时保留 CId 和课程名称避免用户看到编号不知道选的是什么。核心的选课代码如下private void btnSelectCourse_Click(object sender, EventArgs e) { int courseId Convert.ToInt32(dgvCourses.CurrentRow.Cells[CId].Value); string checkSql SELECT COUNT(*) FROM SC WHERE SIdsid AND CIdcid; SqlParameter[] checkParas { new SqlParameter(sid, CurrentUser.SId), new SqlParameter(cid, courseId) }; int exists Convert.ToInt32(DBHelper.Query(checkSql, checkParas).Rows[0][0]); if (exists 0) { MessageBox.Show(你已经选过这门课了); return; } string insertSql INSERT INTO SC (SId, CId) VALUES (sid, cid); SqlParameter[] insertParas { new SqlParameter(sid, CurrentUser.SId), new SqlParameter(cid, courseId) }; if (DBHelper.ExecuteNonQuery(insertSql, insertParas) 0) { MessageBox.Show(选课成功); LoadSelectedCourses(); } }这里的重点在于先查重复再插入两个步骤之间存在时间窗口。如果两个选课请求恰好同时执行前端的重复检查都通过了插入时就会报主键冲突。课程管理系统作为单机课设这种并发场景几乎不会发生但如果你后续扩展为多客户端版本建议把检查与插入合并到一条存储过程中用事务包裹或者在 SC 表上保留复合主键让数据库来兜底挡住重复插入。4.2 退课与成绩更新中的幂等操作退课和打分分别是 SC 表的删除与更新。退课上最容易犯的错误是 DELETE 语句漏掉条件或者条件只写课程 ID 不写学生 IDDELETE FROM SC WHERE SIdsid AND CIdcid两个条件同时存在才能保证学生只能退自己的课。若只按 CId 删除会把其他学生选的同一门课全部删掉这种事故在答辩演示时属于必炸类问题。教师打分模块源码中FormScore.cs 负责展示当前教师所授课程的选课学生列表并为每个学生录入分数。成绩更新 SQL 是 UPDATE 语句这里有一个实际开发中值得留意的点多次提交相同分数结果保持一致即操作是幂等的。不像选课存在重复插入问题打分更新不会因为重复提交产生副作用所以不需要提前检查是否已经打过分。打分前还需要限定课程归属确保教师只能对自己名下的课程打分SELECT SC.SId, Student.SName, Course.CName, SC.Score FROM SC JOIN Course ON SC.CId Course.CId JOIN Student ON SC.SId Student.SId WHERE Course.TId tid这里用 JOIN 而不是子查询是因为需要同时展示学生姓名和课程名称三条独立查询再组合数据的方式会复杂很多。注意 WHERE 条件过滤的是 Course.TId而非 SC.TId——SC 表根本没有该字段这也是源码设计上将该列放置在 Course 表的原因。更新成绩时补一句金额与学分的校验也是课设常见的加分项数据库中没有成绩范围约束所以代码层面对 Score 做 0 到 100 的校验decimal score numericUpDownScore.Value; if (score 0 || score 100) { MessageBox.Show(成绩必须在0到100之间); return; }5. 管理员课程分配与师生信息维护DataGridView 绑定与排错实战5.1 课程管理和教师分配的级联处理管理员的职责包括课程增删改查、为课程安排授课教师以及维护学生和教师信息。课程管理的核心窗体将 DataGridView 与 DataTable 直接绑定选用的是 SqlDataAdapter CommandBuilder 的方案避免手写上百行按钮事件更新数据库。private void LoadCourses() { string sql SELECT Course.CId, Course.CName, Course.CHours, Course.CCredit, Teacher.TName AS 授课教师 FROM Course LEFT JOIN Teacher ON Course.TId Teacher.TId; da new SqlDataAdapter(sql, DBHelper.ConnStr); DataTable dt new DataTable(); da.Fill(dt); dgvCourses.DataSource dt; } private void btnSaveChanges_Click(object sender, EventArgs e) { SqlCommandBuilder builder new SqlCommandBuilder(da); da.Update(dt); MessageBox.Show(保存成功); }SqlCommandBuilder 的使用逻辑是当 DataTable 被用户直接编辑后调用 da.Update 时构建 UPDATE / INSERT / DELETE 命令将界面上所有的修改批量回写数据库。这样教师可直接在 DataGridView 中修改课程信息也可以把界面上一行拖到表格底部来新增课程。需要注意两点第一查询语句中的 SELECT 必须包含主键 CIdCommandBuilder 需要主键才能生成正确的 SQL第二LEFT JOIN 查询出来的结果集只读更新 Course 表本身如果直接调用 Update 会报错需要通过自定义更新 SQL 或分离表来处理。为课程安排授课教师是通过下拉框或独立选择窗口更新 Course.TId 字段实现的。常见做法是在 DataGridView 中增加一列 DataGridViewComboBoxColumn数据源为 Teacher 表显示 TName值是 TId。用户选择某位教师后保存时执行的 SQL 为UPDATE Course SET TIdtid WHERE CIdcid如果删除一位教师而该教师名下还有课程数据库会抛出外键约束冲突异常。源码中解决这个问题的策略是先检查课程表再决定是否允许删除string checkSql SELECT COUNT(*) FROM Course WHERE TIdtid; int count Convert.ToInt32(DBHelper.Query(checkSql, paras).Rows[0][0]); if (count 0) { MessageBox.Show(该教师名下还有课程请先将课程安排给其他教师); return; }5.2 DataGridView 刷新与刷新卡顿的边界热搜中频繁出现“C# 循环数据采集和 UI 刷新卡顿”课设里对应的场景就是反复调用 LoadCourses() 重置 DataSource。每当用户点击一次按钮就重新执行一遍 da.Fill(dt) 并重新设置 DataSourceDataGridView 内部会重建绑定上下文如果数据量大且操作频繁界面会出现明显的闪烁与延迟。优化手段是在重新填充之前使用 this.SuspendLayout() 暂停界面布局填充完成后再执行 ResumeLayout()。更彻底的方案是使用 BindingSource 作为中间层只在需要时调用 ResetBindings(false) 刷新列表避免整表重建。若 DataGridView 只需要展示几百行课程数据这两种方法中前者已经完全够用。5.3 调试期最常见的三个报错定位第一连接串失败。SQL Server 实例名不对或远程连接未开启报错信息通常是“建立与服务器的连接时出错”。处理办法是在 App.config 中确认 Data Source 写法本地实例用点号即可。第二“当前不会命中断点”。这个报错在课设调试阶段极其常见一般是因为你打开了 Debug 改代码但实际运行时加载的还是上一次的 Release 版本或者启动的项目选错了。在解决方案资源管理器中右键需要调试的项目设为启动项目再确认构建配置是 Debug 模式。源码如果有 exe 版本注意检查是否存在多个窗体重复注册导致启动入口指向了旧的窗体。第三外键约束冲突。管理员删除学生或教师时最常触发。排查思路是逐个检查 SC 表关联的学生与课程以及 Course 表关联的教师是否存在引用记录。提供一个快速定位的排查 SQLSELECT * FROM SC WHERE SIdsid SELECT * FROM Course WHERE TIdtid最后补充一个对答辩演示很有用的细节把初始密码统一设置为 123456并在登录界面加上提示文案。老师验收时不用你输入一遍完整信息直接拿默认账号进去体验会顺畅很多。这也恰好体现了这套 C# 课程管理系统源代码在课设场景下的完整闭环——从建库、连接、权限到业务数据流转每个环节都有明确的落点和可校验结果。本文还有配套的精品资源点击获取
返回列表