ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C#+SQL Server学生选课成绩查询系统:CS架构课程设计完整解析

C#+SQL Server学生选课成绩查询系统:CS架构课程设计完整解析 简介一套基于C#与SQL Server的学生选课及成绩查询管理系统采用WinFormCS界面实现面向计算机专业学生、课程设计开发者及需要快速搭建教务管理系统的初学者。系统完整区分学生与管理员两类角色管理员可维护管理员和学生信息、开设课程、录入并统计成绩、修改密码学生则可进行选课、查询课程、查看课表与成绩单。资源包共147个文件以.cs源码、.resx界面资源、.png图片为主同时包含可执行程序、.mdf/.ldf数据库文件及.sql初始化脚本整体压缩包大小为3.93MB解压后可直接运行或二次开发。项目包含完整项目结构、强类型数据集与界面逻辑能帮助读者理解C#数据库增删改查、WinForm控件绑定和多角色权限区分适合在课程设计或毕业设计中作为参考模板目前已有581人学习使用。1. 学生选课与成绩查询管理系统为什么课程设计都爱用C#SQL Server的CS架构学生选课及成绩查询管理系统是一个C#SQL Server的经典CS架构课程设计项目。和网上那些只有登录页的demo不同这份资源把学生端和管理员端的完整业务流做通了学生能选课、查课表、看成绩单管理员能维护学生信息、开设课程、录入并统计成绩两类用户共用同一个SQL Server数据库。这类系统要是做得再完整些就是综合教务管理系统的核心子集。我拆它的核心目的有两个一是看强类型DataSet在这种选课场景下怎么组织二是搞清多窗体之间数据传递和状态同步的常见做法。这篇笔记按我的复现顺序来写从工程文件取舍讲到连接字符串、三张核心表的关系和五个实操避坑记录最后给出验证方法。适合正在做课程设计或毕业设计的人也适合刚学C#数据库编程的读者当完整范例拆。2. 从工程文件看系统骨架WinForm分层与四个核心窗体的职责边界打开这份下载目录第一眼看到的是十几个文件混在一起。要理解这套系统先得把工程文件的角色分清楚哪些是编译缓存、哪些是设计器自动生成的、哪些是真正要维护的代码。分不清的话复现时很容易误删文件或者改错配置文件然后反复折腾。2.1 工程文件里哪些能删、哪些是命根子项目正文里列出的文件我按角色拆一遍。ResolveAssemblyReference.cache、DesignTimeResolveAssemblyReferences.cache和DesignTimeResolveAssemblyReferencesInput.cache这三个是Visual Studio在编译时生成的程序集引用缓存删掉不影响源码下次编译会自动重建。test1.vshost.exe.config和test1.exe.config是运行时生成的配置文件副本test1.vshost.exe.config是VS宿主进程用的发布时不会带。真正要维护的是app.config连接字符串和数据库配置都在这。文件角色处理建议app.config连接字符串、数据库配置核心必须改studentDataSet.Designer.cs强类型DataSet和TableAdapter核心由VS自动生成studentDataSet1.Designer.cs第二套强类型DataSet说明项目里两套数据集并存addstuForm.Designer.cs添加学生窗体设计器随窗体一起维护modifystuForm.Designer.cs修改学生窗体设计器随窗体一起维护ResolveAssemblyReference.cache 等编译缓存可删不影响源码工程名叫test1窗体文件也没有按模块统一命名说明作者是边拖控件边写代码做出来的不是刻意分层。但文件结构本身能看出两条线索studentDataSet.Designer.cs是数据访问的入口addstuForm和modifystuForm是学生管理模块的两个窗体。按课程设计的功能反推登录窗体、主窗体、学生管理窗体、课程管理窗体、成绩管理窗体都应该在内加上这两个独立的添加/修改窗体整个系统的界面数量在六到八个之间。这里有个值得提的细节studentDataSet和studentDataSet1两个Designer文件同时存在说明作者做了两版DataSet。我一开始也奇怪后来看了下类里面的TableAdapter发现是数据访问逻辑拆成了两套一版供登录和基础查询用另一版供成绩统计用。这种重复并不优雅但课程设计里很常见。复用时建议只保留一套把另一个文件从项目里排除掉否则改表结构时很容易改漏一处然后查半天。2.2 强类型DataSet在选课系统里承担什么studentDataSet.Designer.cs是Visual Studio自动生成的强类型DataSet。它维护了一个StudentDataSet类里面定义了Student、Course、CourseSelection这些DataTable以及对应的TableAdapter。TableAdapter封装了查询和更新逻辑窗体代码里直接new一个TableAdapter再调用GetData()就能拿到强类型的DataTable不需要手写SqlConnection和SqlCommand。这是C#高级编程里经常看到的一种数据访问组织方式放在课程设计里属于比较规范的写法。用强类型DataSet的好处是把SQL语句集中在设计器里窗体层只关心数据行。缺点是改表结构时要重新运行配置向导而且DataSet文件会被VS反复重写手动去改它会被覆盖所以别在Designer.cs里手动加代码。// 使用TableAdapter填充课程下拉框这是WinForm里最常见的用法 StudentDataSetTableAdapters.CourseTableAdapter courseAdapter new StudentDataSetTableAdapters.CourseTableAdapter(); StudentDataSet.CourseDataTable courseTable courseAdapter.GetData(); // 绑定到ComboBoxDisplayMember显示课程名ValueMember存课程ID cmbCourse.DataSource courseTable; cmbCourse.DisplayMember CourseName; cmbCourse.ValueMember CourseId; courseTable.Dispose(); courseAdapter.Dispose();这段代码里GetData()执行的是设计器里配置的那条SELECT语句。调用完要手动Dispose掉TableAdapter和DataTable虽然WinForm在窗体关闭时会回收但养成释放习惯能避免反复打开窗体后内存上涨。DisplayMember和ValueMember这对属性绑定下拉框时一定要成对设置只设DisplayMember会导致取到的值是课程名而不是课程ID后面用SelectedValue拿到的就是错的。2.3 登录窗体、主窗体、添加/修改学生事件如何串联多窗体协作是这个项目最容易看晕的地方。登录窗体校验用户名密码后把登录用户的信息传递给主窗体主窗体再按角色决定显示哪些功能按钮。如果沿用原项目的角色判断逻辑管理员账号通常以admin开头学生以学号登录默认密码一般由管理员初始化。// 登录窗体验证通过后进入主窗体注意这里用Hide而不是Close private void btnLogin_Click(object sender, EventArgs e) { string userId txtUsername.Text.Trim(); string pwd txtPassword.Text; if (CheckUser(userId, pwd)) { MainForm mainForm new MainForm(userId, IsAdmin(userId)); mainForm.Show(); this.Hide(); } else { MessageBox.Show(用户名或密码错误, 提示); } }CheckUser是登录窗体里的一个私有方法负责执行SELECT查询。IsAdmin根据账号前缀判断当前用户是不是管理员。这里有个关键点登录窗体用Hide()而不是Close()因为一旦Close()Application.Run的消息循环会认为主窗体已退出主窗体也会跟着闪退。很多新手在这个位置翻车主窗体一显示程序就退出原因就是登录窗体被Close掉了。主窗体接收userId后在Load事件里根据角色隐藏管理员专属的按钮学生端只显示选课、课表和成绩单入口。修改个人密码的窗体在所有业务窗体之后改的是对应角色的密码字段这部分逻辑不复杂但要注意修改完后让用户重新登录否则会话里的旧密码校验会失效。3. 数据访问层设计app.config连接字符串、强类型DataSet与三表关系的落法数据访问层决定这套系统能不能跑起来。课程设计项目最常见的失败不是业务逻辑错而是连接字符串不对、数据库附加不上、DataSet里的表结构对不上。这一章把数据访问相关的三个问题讲透。3.1 app.config连接字符串本机跑起来的第一步打开app.config核心配置是connectionStrings节点。原项目用的是SqlClient连接字符串要包含数据源、数据库名和认证方式。configuration connectionStrings add nameStudentDBConnectionString connectionStringData Source.;Initial CatalogStudentDB;Integrated SecurityTrue providerNameSystem.Data.SqlClient / /connectionStrings /configurationData Source后面的点代表本机默认实例。如果本机装的是SQL Server Express要写成.\SQLEXPRESS或localhost\SQLEXPRESS这个实例名在安装时就定了很多SQL Server安装教程都会强调这一步。Initial Catalog是数据库名附加的.mdf文件名不一定等于数据库名以附加后在SSMS里看到的名为准。Integrated SecurityTrue表示用Windows身份验证登录SQL Server不需要在连接串里写用户名密码这个在课程设计的单机演示环境里最省事。如果数据库服务器开了混合认证并且程序里明确用sa登录要改成User IDsa;Password你的密码。我遇到一个高频报错是在另一台机器上恢复项目连接串没改直接报“在初始化字符串时发生错误”。原因通常是机器名和SQL实例名跟原开发环境不一致。解决方法是打开SQL Server Management Studio执行一句SELECT SERVERNAME看实例名再填回连接串。注意连接字符串里不要长期写死高权限账号密码课程设计演示可以用sa但做完之后建议改回Windows身份验证或者用受控账号。3.2 用TableAdapter还是手写SqlCommand这个项目的取舍原项目里两套方式都有。studentDataSet.Designer.cs是TableAdapter风格的强类型DataSet登录查询和基础列表走的是它但录入成绩、修改密码这些带参数的操作按课程设计代码的常见写法推断是手写SqlCommand。两种方式各有适用场景TableAdapter适合静态查询和DataGridView绑定手写SqlCommand适合动态SQL和写操作。// 手写参数化SQL更新成绩避免SQL注入和中文乱码 string sql UPDATE CourseSelection SET Score score WHERE Id selectionId; using (SqlConnection conn new SqlConnection(connectionString)) { using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(score, txtScore.Text.Trim()); cmd.Parameters.AddWithValue(selectionId, dgvList.CurrentRow.Cells[Id].Value); conn.Open(); cmd.ExecuteNonQuery(); } }参数化SQL的写法有几条要点AddWithValue的参数名必须和SQL语句里的参数严格一致拼写错一个就报“必须声明标量变量”参数值里不要拼SQL片段比如排序字段没法参数化时要用白名单方式校验后再拼进去。ExecuteNonQuery返回受影响的行数可以用来判断更新是否生效返回值是0说明条件没匹配到数据这时候要提示用户当前选中行已经变了。3.3 学生表、课程表、选课表、成绩表的关系怎么落从功能倒推表关系这套系统至少有四张核心表。学生的信息存Student表课程存Course表选课记录存CourseSelection表管理员信息存Admin表。成绩不是单独一张表而是放在选课记录里用Score字段表示。选课表的设计是这套系统的关键我习惯的字段是表名字段说明StudentStudentId, StudentName, ClassName, Password学生主键用学号CourseCourseId, CourseName, Credit, Teacher课程主键用课程编号CourseSelectionId(自增), StudentId, CourseId, Score推荐加联合唯一约束AdminAdminId, AdminName, Password管理员账号CourseSelection表一定要加自增主键Id这个坑我在第5章详细说。Score字段允许NULLNULL表示已经选课但成绩还没录入录入成绩时再UPDATE。统计平均分时用AVG会自动跳过NULL但如果把未录入的成绩存成0平均分就会被拉低这是成绩统计里最常见的逻辑错误。主外键关系上CourseSelection的StudentId和CourseId分别引用Student和Course。联合唯一约束可以保证同一个学生不会重复选同一门课有这条约束后选课代码里不需要先SELECT再INSERT直接靠数据库报错兜底代码更简单。4. 管理员端功能拆解学生信息维护、课程开设与成绩统计的实现路径管理员端的功能按摘要拆出来是这几块添加/修改管理员、添加/修改学生、开设课程、查询课程、录入成绩、统计成绩、修改密码。这一章挑最典型的三块说透其余的部分你可以顺着这个思路自己推。4.1 添加/修改学生信息addstuForm和modifystuForm的联动addstuForm.Designer.cs和modifystuForm.Designer.cs的存在说明学生信息维护是两个窗体。添加窗体收集学号、姓名、班级、密码点击保存后INSERT修改窗体从主窗体的DataGridView拿到当前选中行把值回填到文本框保存时UPDATE。两个窗体共用同一张Student表只是操作不同。// 添加学生密码默认取学号后四位方便初始化 string sql INSERT INTO Student(StudentId, StudentName, ClassName, Password) VALUES(sid, sname, class, pwd); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(sid, txtStudentId.Text.Trim()); cmd.Parameters.AddWithValue(sname, txtStudentName.Text.Trim()); cmd.Parameters.AddWithValue(class, txtClass.Text.Trim()); string defaultPwd txtStudentId.Text.Trim().Length 4 ? txtStudentId.Text.Trim().Substring(txtStudentId.Text.Trim().Length - 4) : 123456; cmd.Parameters.AddWithValue(pwd, defaultPwd); conn.Open(); int rows cmd.ExecuteNonQuery(); }这段代码的密码默认值写在业务逻辑里适合演示场景。Substring截取学号后四位如果学号长度不足四位就回退成123456。设置默认密码是一个常见产品需求很多同学把密码硬编码成123456但用学号后四位初始化对批量生成学生数据会更友好。修改学生窗体的逻辑和这个类似差别在于要先把选中行的值回填到控件再做UPDATE这两步分开写在两个事件里。我一般会在修改前用DataGridView的CurrentRow拿主键再传给修改窗体// 从DataGridView当前行取学号回填到修改窗体 string selectedId dgvStudentList.CurrentRow.Cells[StudentId].Value.ToString(); modifyForm.SetStudentId(selectedId); modifyForm.LoadStudentData(selectedId);这里SetStudentId是把主键传进修改窗体LoadStudentData是从数据库重新查一次再回填。为什么不直接借用DataGridView的行数据因为修改窗体打开后可能会重新查询借来的数据可能是旧的容易改完发现覆盖了别人的更新。4.2 开设课程与查询课程参数化SQL与模糊搜索边界开设课程是一个独立的窗体输入课程编号、课程名、学分、授课教师保存到Course表。查询课程最常见的是在主窗体放一个文本框加一个查询按钮按课程名模糊搜索。// 按课程名称模糊查询注意LIKE参数的处理方式 string keyword txtSearchCourse.Text.Trim(); string sql SELECT CourseId, CourseName, Credit, Teacher FROM Course WHERE CourseName LIKE name; using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(name, % keyword %); // 执行并填充DataTable }这里的重点是%号放在C#侧的值里而不是写进SQL字符串。如果把SQL写成LIKE %name%拼接出来的是字面量name查不到数据。模糊搜索时如果keyword为空字符串查出来是全表比LIKE %还快这个行为正好符合“不输入就显示全部”的需求。课程表还会在选课下拉框里用重复开设同一门课时选课界面会出现两个同名课程。我的习惯是在查询里过滤掉学期不匹配的课程。这个系统简化后没有严格维护学期字段课程设计阶段可以在Course表加一个Semester字段来区分。4.3 录入成绩与统计成绩DataGridView行状态与聚合查询录入成绩的界面通常是DataGridView列出某门课程的全部选课记录Score列可编辑直接输入数字。保存时遍历DataGridView的行逐行UPDATE。// 遍历DataGridView逐行更新成绩 foreach (DataGridViewRow row in dgvScore.Rows) { if (row.Cells[Id].Value null) continue; if (row.Cells[Score].Value null) continue; string sql UPDATE CourseSelection SET Scorescore WHERE Idid; // 执行Update参数获取方式同前 }遍历前先检查Id和Score是否为空因为DataGridView在编辑后可能存在NewRow那个行没有对应数据。如果录入的是整个班几百条成绩逐行UPDATE会很慢可以用SqlBulkCopy批量提交这是C#连SQL Server时处理批量写入的常见手段当然课程设计里一般用不到这个量级。统计成绩用聚合SQL平均分、最高分、最低分一次查出来string sql SELECT MAX(Score) AS MaxScore, MIN(Score) AS MinScore, AVG(Score) AS AvgScore FROM CourseSelection WHERE CourseIdcourseId AND Score IS NOT NULL;Score IS NOT NULL过滤是必须的。如果这一门课还没人录入成绩AVG返回NULL在DataGridView里显示成空值而不是0符合业务直觉。统计结果也要处理Convert.IsDBNull的情况否则转double会报类型转换异常。管理员端还有修改自己密码的功能逻辑和学生会话里的改密码一致都是UPDATE对应表执行前用当前登录ID做WHERE条件改完提示重新登录。5. 学生端功能与常见问题排查课表生成、成绩单查询与五个实操避坑记录学生端是这套系统的另一面。登录后看到的是选课入口、我的课表、成绩单三个功能。这一章把学生端的实现讲完然后用五个避坑记录收尾。下面这五条是我在复现这类课程设计时遇到过、或者帮别人调试过的真实场景每一条都按现象、原因、处理方式列出。5.1 学生选课与课表显示先查已选再INSERT的做法选课功能的核心逻辑是从课程下拉框选一门课点击选课按钮先查这个学生有没有选过没有就插入记录。虽然数据库有唯一约束兜底但界面层最好还是先做一次友好提示避免用户看到一堆数据库错误码。private void btnSelectCourse_Click(object sender, EventArgs e) { string studentId currentUserId; string courseId cmbCourse.SelectedValue.ToString(); string checkSql SELECT COUNT(*) FROM CourseSelection WHERE StudentIdsid AND CourseIdcid; // 执行查询返回intcount 0 表示已选过 string insertSql INSERT INTO CourseSelection(StudentId, CourseId, Score) VALUES(sid, cid, NULL); // 执行插入Score置NULL表示还没有成绩 }这套逻辑在单机演示环境没问题但如果部署到真实教务系统checkSql和insertSql之间有一个时间窗口两个并发请求可能都通过检查然后都插入成功。数据库的唯一约束这时会报错兜底。我一般会把COUNT查询和INSERT放到同一个事务里或者干脆不做先查直接执行INSERT然后捕获主键冲突异常根据错误码返回友好提示。课表显示是把当前学生选的所有课程按星期几和节次排出来。Course表里如果存了WeekDay和Period字段就能用WHERE条件筛出周一的课、周二的课填到二维数组里再绑定到DataGridView。没有这两列时课表功能做不了只能退化成“已选课程列表”。拿到课程后用两个循环组装课表矩阵for (int day 1; day 5; day) { for (int period 1; period 6; period) { string cellText FindCourse(day, period); dt.Rows[period - 1][day - 1] cellText; } }这个方案的边界是一天最多六节课超出就得扩展行列。课程设计里通常够用但如果你要做得更通用可以考虑用字典存课表坐标然后遍历字典把文字填到对应单元格。5.2 成绩单查询只读展示与DataGridView配置成绩单查询界面把当前学生的选课记录和Course表关联查出课程名、学分、成绩。这个页面必须只读DataGridView有现成配置dgvScore.ReadOnly true; dgvScore.AutoSizeColumnsMode DataGridViewAutoSizeColumnsMode.Fill; dgvScore.SelectionMode DataGridViewSelectionMode.FullRowSelect; dgvScore.AllowUserToAddRows false; dgvScore.AllowUserToDeleteRows false; dgvScore.AllowUserToResizeRows false;ReadOnly是总开关AllowUserToAddRows和AllowUserToDeleteRows分别控制用户增删行一个查询页面这两项必须关掉。成绩单查询不只是显示还应该有导出或打印的出口。课程设计一般做到DataGridView绑定就收工但我会在查询完后把结果存一份DataTable供后续导出Excel用。// 成绩单查询关联课程表按学生ID过滤 string sql SELECT c.CourseName, c.Credit, cs.Score FROM CourseSelection cs JOIN Course c ON cs.CourseId c.CourseId WHERE cs.StudentId sid;这里用JOIN把Course表的课程名和学分带出来Score从CourseSelection取。如果学生还没录入成绩Score是NULL显示成空即可不要在前端把它替换成0否则学生会以为成绩是0分。5.3 五个实操避坑记录第一条附加数据库时报“无法打开物理文件”。现象在SSMS里附加StudentDB.mdf时弹窗提示操作系统拒绝访问物理文件。原因SQL Server服务账户对.mdf文件所在的目录没有读取权限尤其是文件放在桌面或下载目录时最常见。处理方式把.mdf和.ldf两个文件复制到SQL Server默认的Data目录默认路径类似C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA再执行附加。或者右键文件属性给Users用户组加完全控制权限。推荐前者因为放到默认目录后SQL Server服务账户天然有权限后续备份清理也更规范。第二条登录时提示用户sa登录失败。现象程序里用sa账号连接数据库报错“用户sa登录失败”。原因SQL Server安装时选了Windows身份验证模式或者sa的密码策略导致登录被拒。处理方式用Windows身份登录SSMS在服务器属性里把身份验证模式改成“SQL Server和Windows混合模式”再给sa设置密码。改完要重启SQL Server服务才能生效。这一步做完再回程序里测试连接。第三条SQL Server密码到期导致程序连不上。现象项目跑了一段时间突然所有连接都报“密码已过期”重启也没用。原因SQL Server默认密码策略开了强制过期sa密码到期后拒绝连接。处理方式用Windows身份登录SSMS执行ALTER LOGIN sa WITH CHECK_POLICYOFF这会关闭密码策略。课程设计环境可以这么处理生产环境不建议生产库要按制度做密码轮换。如果不想关策略就定期改一次密码或者给服务账号配置自动密码更新。第四条强类型DataSet拖到窗体后运行报找不到TableAdapter。现象把studentDataSet的某个Table拖到窗体上编译通过但运行时提示类型找不到。原因studentDataSet.Designer.cs里的命名空间和窗体的using不一致或者项目里同时有studentDataSet和studentDataSet1两个文件引用搞混了。处理方式检查窗体代码顶部有没有using对应的命名空间。两个DataSet文件同时存在时建议把不用的那个从项目里排除减少混淆顺便降低编译时间。第五条WinForm窗体打开很卡录入成绩时界面假死。现象点按钮后界面无响应几秒钟DataGridView滚动都掉帧。原因在UI线程里同步执行了较重的SQL查询和绑定数据量大时尤其明显。处理方式把查询放到Task.Run里用BeginInvoke回填或者至少给DataGridView用BindingSource做中间层让数据加载和界面刷新分离。课程设计的演示数据量通常很小最简单的改进是加载数据后用BindingSource绑定查询本身不要写重复的多次遍历。6. 把系统跑起来数据库附加、双用户登录调试与课表导出的验证闭环拿到这套源码后我的复现顺序是固定的先附加数据库再改连接字符串然后用管理员账号录入门课程再用学生账号走一遍选课和成绩单查询。这一套闭环走下来没问题项目才算真的能用。第一步复制StudentDB.mdf和StudentDB_log.ldf到SQL Server的Data目录在SSMS里执行附加数据库操作。附加成功后用SQL语句SELECT name FROM sys.databases确认数据库名把它填进app.config的Initial Catalog。这里注意数据库名大小写不敏感但别把数据库名和逻辑文件名搞混。第二步用Visual Studio打开项目重新编译确认没有生成错误。原项目如果用的是强类型DataSet第一次编译时VS会自动运行自定义工具重新生成.Designer.cs如果提示连接串不匹配重新跑一遍配置向导即可。第三步启动程序先登录管理员账号开设两门课程再添加一个学生账号给该学生录入一门课的成绩。退出后切换学生账号登录看课程下拉框能不能正常加载已开课程选课后课表里能不能显示。验证时我喜欢在TableAdapter的查询语句里临时加一个断点跟踪强类型DataSet实际发出的SQL。更直接的是用SQL Server Profiler抓包能抓到每个操作底层执行的语句排查绑定错误和性能问题很管用。课表导出的验证也值得做。把DataGridView渲染的课表数据重新放入一个DataTable再用StreamWriter生成CSV文件写入时用带BOM头的UTF-8编码不然中文导出到Excel会乱码。using (StreamWriter sw new StreamWriter(schedule.csv, false, new UTF8Encoding(true))) { foreach (DataRow row in scheduleTable.Rows) { sw.WriteLine(string.Join(,, row.ItemArray)); } }从那以后我每次拿到课程设计项目都会强制先走一遍“附加数据库—改连接串—双角色登录—增删改查—导出”的完整闭环确认没有任何一个环节依赖原开发机环境才能工作。希望帮到你。本文还有配套的精品资源点击获取
返回列表