
简介这是一套面向教育机构及初学者的C#学生选课与成绩查询管理系统完整项目适用于学习Windows桌面应用开发、数据库建模与权限控制可作为课程设计、毕业设计或二次开发的参考底稿。项目覆盖学生信息维护、选课容量与冲突校验、成绩按课程/学期等多条件查询等核心业务同时给出Windows Forms的界面实现与C#后端交互逻辑能完整演示选课系统从数据层到表现层的协作方式。资源包共122个文件压缩后约4.14MB内部以cs源代码、resources/resx界面资源、dll运行库、exe可执行程序为主另含sql脚本、mdf/ldf数据库文件、docx详细设计文档和README说明文件类型齐备目录结构清晰。已有71人学习浏览。整套资料既便于理解学生选课业务流程也能帮助快速搭建可运行环境并对照源码梳理关键技术点实践价值较高。1. 先说结论这套 C# 学生选课及成绩查询管理系统源码、设计文档、数据库脚本一条龙如果你正在找一份能直接改、能跑、能拿去答辩的 C# 课程设计学生选课及成绩查询管理系统是出现率最高的题目之一。这个压缩包里不只有源码还带了详细设计文档、README、数据集定义文件以及一堆看起来不知道能不能删的 .cache 文件。用我的话说这就是典型的作业级但五脏俱全的 Windows Forms 项目学生管理、课程管理、选课、成绩查询该有的模块都在。适合两类人一是做 C# 课设、毕业设计的学生二是刚入行想练手 WinForms ADO.NET DataSet 这套老但经典技术栈的开发新人。它能让你少走弯路——只要你愿意先把项目结构看懂而不是直接双击 .sln 然后对着报错干瞪眼。2. 系统到底拆成了几块从三张表到三个窗体2.1 核心数据表学生、课程、选课成绩各管各的这类系统不管界面做成什么样数据库设计九成是同一套路。学生一张表课程一张表选课和成绩合成一张表或者拆成选课表 成绩表。文件列表里能看到studentDataSet.Designer.cs和studentDataSet1.Designer.cs说明用的是强类型数据集也就是说表结构在设计器里已经固定好了。常见设计如下表名关键字段作用StudentStudentId, StudentNo, Name, Gender, Class学生基本信息CourseCourseId, CourseNo, CourseName, Credit, MaxCount课程及容量SCSCId, StudentId, CourseId, Score, Semester选课记录兼作成绩表学生和课程之间是多对多关系所以SC表里存的是两个外键。加一个Semester字段非常有用因为成绩查询往往要求按学期查没有这个字段后期只能硬编码。MaxCount是选课容量选课的时候拿当前已选人数和它比对这就是系统里最核心的一条规则。我一般会建议拿到项目先别急着打开 Visual Studio先把这三张表的字段结构抄到纸上再对照窗体看思路会清晰很多。这个压缩包里如果有 SQL 脚本或者 MDF 文件直接看建表语句最直观。2.2 studentDataSet 和 studentDataSet1强类型 DataSet 不是二次开发的黑匣子看到studentDataSet.Designer.cs和studentDataSet1.Designer.cs两个文件很多人会懵以为是两个数据集。实际上多数情况下这是设计器帮你生成的强类型 DataSet 类一个是对应主数据库操作另一个可能是后来加的一版或者在复制项目时自动生成的开发时可能只用了其中一个。强类型 DataSet 的好处是把 SQL 查询结果映射成带属性的DataTable写代码时可以这么用// 实例化强类型数据集 studentDataSetTableAdapters.SCTableAdapter scAdapter new studentDataSetTableAdapters.SCTableAdapter(); // 查询某个学生的全部选课记录 studentDataSet.SCDataTable scTable scAdapter.GetDataByStudentId(studentId); foreach (studentDataSet.SCRow row in scTable) { Console.WriteLine(row.CourseName row.Score); }这里GetDataByStudentId是 TableAdapter 设计器里可以自己配置的查询方法双击.xsd文件就能加。强类型 DataSet 的价值在于列名不会拼错row.Score编译期就能检查出来比写dt.Rows[i][score].ToString()这种字符串访问安全得多。缺点是改表结构后要重新生成数据集否则界面和代码对不上这是最常见的翻车点之一。2.3 窗体的职责划分addstuForm、modifystuForm 与主窗体的分工从文件名看这个项目里有addstuForm新增学生、modifystuForm修改学生还应该有主窗体MainForm或类似的东西。WinForms 项目的通病是逻辑全堆在按钮事件里但这个小系统还算清晰管理窗体负责列表展示和导航新增、修改分别开独立窗体。典型流程是主窗体加载时Fill()数据集DataGridView 绑定到BindingSource点新增弹addstuForm点修改弹modifystuForm。新增窗体里接收主窗体传过来的 DataGridView 行数据或者用一个公共参数传学号改完调用同一个Update()方法写回数据库。理解了这个结构你要扩展删除学生就很简单了——复制一个修改窗体的模式把 SQL 换成 DELETE 就行。3. 把下载的解决方案跑起来连接字符串、附加数据库、清缓存三步走3.1 打开 app.config先改连接字符串再谈运行拿到项目后第一件事不是点绿色运行按钮而是打开app.config。这个文件是整个系统能不能跑起来的开关。里面通常长这样?xml version1.0 encodingutf-8 ? configuration connectionStrings add nameStudentDBConnectionString connectionStringData Source.;Initial CatalogStudentDB;Integrated SecurityTrue providerNameSystem.Data.SqlClient / /connectionStrings /configurationData Source.表示本机默认实例如果你装的是命名实例比如SQLEXPRESS就要改成Data Source.\SQLEXPRESS。Integrated SecurityTrue是 Windows 身份验证如果你只能用 SQL Server 账号登录就改成User Idsa;Password你的密码。这里最容易踩的坑是连接串里的数据库名和实际数据库名对不上或者根本没附加数据库后面运行起来必然报无法打开登录所请求的数据库。改完连接串后项目里同时有app.config、test1.exe.config、test1.vshost.exe.config三个文件。注意test1.vshost.exe.config是 Visual Studio 宿主进程用的你改app.config是正路宿主进程的配置文件一般在调试时会自动同步不用手动改。如果你看到test1.exe.config内容跟app.config不一致以app.config为准。3.2 附加数据库用 MDF 文件还是执行 SQL 脚本这个项目没有明确给出数据库文件但从数据结构来说你获得数据库的方式一般是两种。第一种是压缩包里带.mdf文件右键 SQL Server 里的数据库节点选择附加指定路径即可。第二种是只有.sql脚本在 SQL Server Management Studio 里执行建库建表语句再手动插入几条测试数据。无论哪种方式附加或执行之后都要确认数据库名和连接串里的Initial Catalog一致。我习惯的做法是先执行一段查询确认表已经建好USE StudentDB; GO SELECT name FROM sys.tables;如果查询结果里能看到 Student、Course、SC 这几张表说明数据库正常。这里有个小技巧如果你的 MDF 文件是拷到别的电脑上的附加时可能会因为日志文件路径问题失败干脆把 MDF 文件拷到 SQL Server 的 Data 目录下或者新建一个同名数据库然后用脱机/联机的方式替换这是老手常用的后悔药。3.3 清掉一堆 .cache 文件再生成ResolveAssemblyReference 系列缓存项目里有一堆ResolveAssemblyReference.cache、DesignTimeResolveAssemblyReferences.cache、DesignTimeResolveAssemblyReferencesInput.cache这些不是源码是 Visual Studio 解析程序集引用的缓存文件。它们会因为项目路径改变、引用版本变化而产生脏数据导致明明代码没改编译却报一堆奇怪的找不到引用的错误。我的处理习惯是拿到项目后在解决方案资源管理器里删除所有.cache文件然后重新生成。这些文件在编译时会自动重新生成删掉不会影响任何功能。如果删完还有引用报错就检查项目里引用的System.Data、System.Windows.Forms是不是还在WinForms 项目偶尔会因为目标框架不一致丢掉默认引用。清理完缓存之后按 CtrlShiftB 重新生成解决方案。生成成功后bin\Debug下会出现新的test1.exe此时再运行报错概率会低很多。3.4 登录与角色管理员、教师、学生的权限是怎么落的学生管理系统一般有三级角色管理员管学生和课程信息教师录成绩学生查成绩和选课。这个项目的登录逻辑通常放在LoginForm里登录后会根据角色设置主窗体的按钮可用性。最简单粗暴的做法是给用户表加一个Role字段登录时查出来存到一个全局变量里然后控制各个按钮的Visible属性。// 登录成功后把角色存到公共类 public static class UserSession { public static string UserName { get; set; } public static string Role { get; set; } } // 主窗体加载时根据角色初始化界面 if (UserSession.Role 学生) { btnAddStudent.Visible false; btnManageCourse.Visible false; btnScoreQuery.Visible true; }这种实现能跑但安全性很弱因为角色判断全在界面层绕过界面直接调用业务逻辑还是能越权。如果只是想应付课设这样够用。如果是想写成能拿得出手的项目建议把权限判断下沉到业务层或者在 SQL 层就用角色参数来控制可执行操作。这是后期加分的一个点。4. 选课与成绩查询的核心实现事务、条件组合与数据刷新4.1 选课合法性判断容量、重复选、冲突课的 SQL 写法选课功能是这个系统的灵魂也是答辩时老师最喜欢追问的地方。一次合格选课要检查三件事人满了吗、选过了吗、时间和已有课冲突吗。很多初版代码只查了前两件冲突判断干脆没有结果就是同一时间选了三门课数据里全是脏数据。我一般建议把容量和重复判断放在 SQL 里一次做完用事务包起来防止并发。核心 SQL 长这样BEGIN TRANSACTION; DECLARE CourseId INT 2; DECLARE StudentId INT 10; DECLARE MaxCount INT (SELECT MaxCount FROM Course WHERE CourseId CourseId); DECLARE SelectedCount INT (SELECT COUNT(*) FROM SC WHERE CourseId CourseId); -- 容量判断 IF SelectedCount MaxCount BEGIN ROLLBACK TRANSACTION; RAISERROR(该课程已满, 16, 1); RETURN; END -- 重复选课判断 IF EXISTS (SELECT 1 FROM SC WHERE StudentId StudentId AND CourseId CourseId) BEGIN ROLLBACK TRANSACTION; RAISERROR(不能重复选择同一门课程, 16, 1); RETURN; END INSERT INTO SC (StudentId, CourseId, Semester) VALUES (StudentId, CourseId, 2025-2026-1); COMMIT TRANSACTION;时间冲突判断更复杂一点需要在课程表里加上课节次字段比如周一第5-6节然后用EXISTS查学生已有的选课记录里有没有相同节次的课。如果没有这个字段就在代码里查两次再比较但正确性会差一些。这里的亮点是容量判断和插入必须在同一个事务里否则选课人数临界时会出现两个人同时选最后两个名额各自查到的已选人数都小于 MaxCount最后都插入成功超员了。这在单机版作业里基本遇不到但写出来是你理解事务并发的证据。4.2 成绩查询按课程、学期、学号组合筛选的查询接口成绩查询模块最实用的功能是支持模糊条件和多条件组合。用户可能只输学号只输学期或者学号课程名一起输查询逻辑要能对这几个条件动态拼 SQL。推荐用参数化 SQL 而不是字符串拼接防止 SQL 注入答辩时这也是一个加分点。private DataTable QueryScore(string studentNo, string courseName, string semester) { string sql SELECT s.StudentNo, s.Name, c.CourseName, sc.Score, sc.Semester FROM SC sc INNER JOIN Student s ON sc.StudentId s.StudentId INNER JOIN Course c ON sc.CourseId c.CourseId WHERE 11; using (SqlCommand cmd new SqlCommand()) { if (!string.IsNullOrEmpty(studentNo)) { sql AND s.StudentNo LIKE studentNo; cmd.Parameters.AddWithValue(studentNo, % studentNo %); } if (!string.IsNullOrEmpty(courseName)) { sql AND c.CourseName LIKE courseName; cmd.Parameters.AddWithValue(courseName, % courseName %); } if (!string.IsNullOrEmpty(semester)) { sql AND sc.Semester semester; cmd.Parameters.AddWithValue(semester, semester); } cmd.CommandText sql; cmd.Connection new SqlConnection(connString); SqlDataAdapter da new SqlDataAdapter(cmd); DataTable dt new DataTable(); da.Fill(dt); return dt; } }注意这里WHERE 11是拼接条件时偷懒的写法目的是让后面的AND不用判断是不是第一条。运行效果没问题但有些团队规范会禁止写注释说明一下即可。LIKE % param %虽然无法利用索引但在课设这种数据量下完全无所谓别纠结性能。4.3 数据绑定DataGridView 显示与刷新时最容易翻车DataGridView 显示查询结果通常有两种做法一种是直接dataGridView1.DataSource dt;另一种是先把dt放进BindingSource再绑定。直接绑定的缺点是如果你把同一个DataTable又重新Fill了一次界面不一定自动刷新得手动重新设置DataSource。每次改完数据后强制走一遍清空 DataSource → 重新 Fill → 重新绑定三步能避免八成界面刷新问题。// 刷新成绩列表 dataGridView1.DataSource null; DataTable dt QueryScore(txtStudentNo.Text.Trim(), txtCourseName.Text.Trim(), cmbSemester.Text.Trim()); dataGridView1.DataSource dt;把DataSource先设成null再赋新值这样 DataGridView 才会强制重新绘制。如果你发现查询按钮点了以后表格数据没变化通常就是少了这一步。另外在DataGridView的DataError事件里自己处理格式异常比如成绩字段是DBNull时会避免一堆运行时的ArgumentException弹窗。5. 避坑清单从下载到运行C# 学生系统最常见的五个翻车点5.1 现象运行后报无法打开登录所请求的数据库或登录失败原因连接字符串里的数据库名与真实库名不一致或者数据库文件没有附加到当前 SQL Server 实例也可能是用的是 Windows 身份验证而 SQL Server 只开了混合验证模式。解决先打开 SQL Server Management Studio连接本地实例确认左边有没有你想要的数据库名。没有就附加或执行脚本。有的话检查 app.config 里的Initial Catalog是否完全一致包括大小写。再用 SQL Server 配置管理器确认实例名写没写对命名实例要写成.\实例名。最后如果确认没错试着用 SQL Server 身份验证登录sa再改一下连接串。5.2 现象点击选课按钮后没有反应也没有报错原因最常见的是只调用了studentDataSet1.SC.AddSCRow(...)修改了内存中数据集却没有调用 TableAdapter 的Update()方法把改动写回数据库。DataSet 是内存副本它不会自动同步数据库。解决所有对 DataSet 的增删改操作之后必须显式调用对应的TableAdapter.Update()this.scTableAdapter.Update(this.studentDataSet1.SC);如果用的是SqlCommand模式则要确认ExecuteNonQuery()有没有被调用。这个问题特别好排查——断点打在按钮末尾看Update()有没有执行没有的话就是漏了。5.3 现象数据库里的数据已经变了但界面列表里还是旧数据原因DataGridView 绑定的是内存中的DataTable对象SqlDataAdapter.Fill()会在已有行的基础上追加而不是覆盖刷新。如果你同一个DataTable实例再次Fill旧行还在新插入的行排后面看起来就是数据没变。解决每次查询前先清空DataTable的Rows集合或者干脆新建一个DataTable实例再Fill。我在前面 4.3 节给出的刷新三步法就是为了治这个。记住Fill不是Refresh它只负责把查询结果塞进内存表不会主动帮你清理旧数据。5.4 现象编译时报程序集版本冲突或者DataReader未关闭异常原因项目的目标框架和 SQL Server 客户端库版本不一致比如用了旧的System.Data.SqlClient但项目目标是 .NET 6或者代码里用了SqlDataReader没有及时Close()导致连接被占用第二次查询时连接池报连接正忙。解决WinForms 课设通常选 .NET Framework 4.x 运行得最稳不要轻易改成 .NET Core。SqlDataReader用完立刻关闭建议直接用using包裹using (SqlConnection conn new SqlConnection(connString)) using (SqlCommand cmd new SqlCommand(sql, conn)) using (SqlDataReader reader cmd.ExecuteReader()) { while (reader.Read()) { // 处理数据 } }这样不管有没有异常连接都会释放。这是我写 C# 数据库程序多年的血泪经验以前吃过忘记关连接导致系统越跑越慢的亏。5.5 现象项目里多了很多 .cache 文件删了怕出错留着又碍眼原因.cache是 Visual Studio 的编译状态文件其中ResolveAssemblyReference.cache是程序集解析缓存。当你移动项目目录、改引用或换开发机后这些缓存里记录的是旧路径就会引发找不到命名空间之类的假报错。解决放心删。这类文件全部位于obj目录下重新生成解决方案时会自动重新创建。删掉后如果还报错就执行清理解决方案再重新生成。这不是玄学是 MSBuild 的已知恶心行为。我每次拷项目给别人之前都会手动删一遍obj和bin再打个干净压缩包省得对方一打开就是红的。6. 二次开发与验证把这个作业改成能进你简历的项目6.1 给系统加一个全局异常处理原版的按钮事件基本都是裸奔的数据库连接一断就弹个英文异常。做一个Program.cs级别的全局异常捕获是所有二次开发里性价比最高的一步[STAThread] static void Main() { Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.ThreadException (sender, e) { MessageBox.Show(操作失败 e.Exception.Message, 系统提示, MessageBoxButtons.OK, MessageBoxIcon.Warning); }; Application.Run(new LoginForm()); }这样用户看到的失败的提示是不带堆栈的友好提示聊天式呈现。更重要的是异常日志里记下完整堆栈方便你在开发时溯源。6.2 必做的数据验证清单改动任何一个模块后我建议按这份清单走一遍能防住大多数翻车添加学生后能否立即在列表看到新记录选同一门课连续点两次选课第二次是否被拦截选满容量的课程看是否报课程已满修改成绩后刷新平均分显示是否合理教师登录是否看不到学生管理按钮。这套验证做完答辩时基本不会被问倒。从那以后我每次拿到别人的 WinForms 项目都强制自己先做三件事看连接串、清缓存、跑通一个完整业务流再谈改代码。这个习惯救了我无数次。改这份源码时最值得自豪的不是改了多少功能而是你把它的边界摸清了——哪个按钮对应哪段逻辑、哪张表承载哪些数据门儿清。希望帮到你。本文还有配套的精品资源点击获取