
简介本资源是一套基于C#与SQL Server开发的学生选课及成绩查询管理系统的完整源码工程面向高校计算机专业初学者、课程设计实践者及.NET桌面应用入门开发者旨在解决教务场景中学生信息维护、课程管理、选课控制与成绩查询等核心业务需求。压缩包共123个文件含44个C#源码文件如studentDataSet.Designer.cs、modifystuForm.Designer.cs等覆盖数据集建模、窗体逻辑与UI交互、20个resources/resx本地化资源、3个可执行exe与配套config配置文件以及SQL Server数据库文件mdf/ldf和Visual Studio解决方案文件sln/csproj整体大小为4.15MB。已有364人学习下载资源结构规范模块划分清晰——涵盖学生管理、课程设置、选课事务、成绩查询、权限控制与报表生成六大功能单元附带完整项目配置与运行说明开箱即可编译调试是理解ADO.NET数据访问、WinForm界面开发与数据库设计协同落地的典型教学级实践案例。1. 为什么一个“学生选课及成绩查询系统”在2024年仍值得用C# SQL Server重做这不是课程设计作业的简单复刻而是高校教务系统轻量化落地的真实切口。当教务处还在用Excel手动核对重修名单、任课教师反复导出Excel再筛选学生成绩、学生查分要等教务老师下班后发邮件回复时——一个本地部署、响应快、权限清晰、能直接对接校园卡号或学号登录的C#桌面应用反而成了最务实的选择。它不追求高并发或微服务架构但必须解决三个硬需求SQL Server中多表关联查询的稳定性尤其涉及选课、课程、学生、成绩四张主表、C# WinForms界面在频繁数据刷新下的UI线程安全、以及部署时绕过Windows UAC和SQL Server实例权限配置的常见陷阱。本方案面向有.NET基础的开发人员或高校计算机专业高年级学生重点不在炫技而在交付一个能真正跑进教研室电脑、被教师日常使用的可维护系统。所有代码均基于.NET Framework 4.7.2与SQL Server 2019兼容性设计避免使用Entity Framework Core等引入额外依赖的ORM直连SQL命令SqlDataAdapter实现可控的数据流。2. 数据库建模从“学生-课程-选课-成绩”四张表出发定义可查询、可扩展的关系结构2.1 四张核心表的设计逻辑与字段约束学生选课与成绩管理的本质是典型的“多对多”关系建模问题。学生与课程之间通过“选课”表关联而成绩则作为该关联的属性存在。这种结构既支持一人多课、一课多人也允许同一学生多次修读同一门课如重修同时为后续扩展如平时分、实验分、考试类型留出空间。我们不采用单表冗余设计因为那会导致更新异常和存储浪费也不预设视图或存储过程先以干净的表结构打底再按需封装。提示所有主键均使用INT IDENTITY(1,1)而非GUID降低索引碎片和JOIN开销外键强制启用级联删除仅限开发环境验证逻辑生产环境建议改为ON DELETE NO ACTION并由业务层控制。2.1.1 学生表Student——身份锚点非空字段严格校验CREATE TABLE Student ( StudentID INT PRIMARY KEY IDENTITY(1,1), StudentNo NVARCHAR(12) NOT NULL UNIQUE, -- 学号如20220001001长度固定便于索引 Name NVARCHAR(20) NOT NULL, Gender CHAR(1) CHECK (Gender IN (M,F)), -- M/F不用0/1避免语义混淆 ClassID INT NOT NULL, -- 所属班级ID指向Class表未列出但实际项目中必存 EnrollmentDate DATE NOT NULL DEFAULT GETDATE(), CONSTRAINT CK_StudentNo_Format CHECK (StudentNo LIKE [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]) );StudentNo设为UNIQUE且带格式校验确保学号录入无误避免后期清洗EnrollmentDate默认当前日期减少插入时显式赋值ClassID虽未给出Class表定义但在实际开发中必须存在否则无法按班级统计选课率。2.1.2 课程表Course——教学资源元数据区分理论与实践课型CREATE TABLE Course ( CourseID INT PRIMARY KEY IDENTITY(1,1), CourseCode NVARCHAR(10) NOT NULL UNIQUE, -- 课程代码如CS101、MAT203 CourseName NVARCHAR(50) NOT NULL, Credit TINYINT NOT NULL CHECK (Credit BETWEEN 1 AND 8), -- 学分tinyint节省空间 CourseType NVARCHAR(10) NOT NULL DEFAULT Theory, -- Theory,Lab,Internship Department NVARCHAR(30) NOT NULL -- 开课院系用于按院系筛选课程 );CourseCode作为业务主键比自增ID更易被教师识别Credit用TINYINT而非INT因学分绝不会超过8物理存储节省3字节/行CourseType默认Theory避免NULL值导致WHERE条件复杂化。2.1.3 选课表Enrollment——关系枢纽记录选课行为本身CREATE TABLE Enrollment ( EnrollmentID INT PRIMARY KEY IDENTITY(1,1), StudentID INT NOT NULL, CourseID INT NOT NULL, Semester NVARCHAR(10) NOT NULL, -- 格式2023-2表示2023-2024学年第2学期 EnrollmentDate DATETIME2 NOT NULL DEFAULT GETDATE(), Status CHAR(1) NOT NULL DEFAULT A CHECK (Status IN (A,D,W)), -- AActive, DDropped, WWithdrawn CONSTRAINT FK_Enrollment_Student FOREIGN KEY (StudentID) REFERENCES Student(StudentID) ON DELETE CASCADE, CONSTRAINT FK_Enrollment_Course FOREIGN KEY (CourseID) REFERENCES Course(CourseID) ON DELETE CASCADE, CONSTRAINT UQ_Student_Course_Semester UNIQUE (StudentID, CourseID, Semester) );UNIQUE (StudentID, CourseID, Semester)是关键约束防止同一学生同一学期重复选同一门课Status字段支持选课状态跟踪为“退课”“缓考”等流程留出接口ON DELETE CASCADE在开发阶段方便清空测试数据上线前需评估是否保留。2.1.4 成绩表Score——结果载体支持多维度评分CREATE TABLE Score ( ScoreID INT PRIMARY KEY IDENTITY(1,1), EnrollmentID INT NOT NULL, FinalScore DECIMAL(5,2) NULL CHECK (FinalScore BETWEEN 0 AND 100), -- 总评成绩 MidtermScore DECIMAL(5,2) NULL CHECK (MidtermScore BETWEEN 0 AND 100), LabScore DECIMAL(5,2) NULL CHECK (LabScore BETWEEN 0 AND 100), AttendanceScore DECIMAL(5,2) NULL CHECK (AttendanceScore BETWEEN 0 AND 100), GradeLevel CHAR(2) NULL CHECK (GradeLevel IN (A,A,B,B,C,C,D,F)), -- 字母等级 ScoreDate DATE NOT NULL DEFAULT GETDATE(), CONSTRAINT FK_Score_Enrollment FOREIGN KEY (EnrollmentID) REFERENCES Enrollment(EnrollmentID) ON DELETE CASCADE, CONSTRAINT CK_AtLeastOneScore CHECK ( FinalScore IS NOT NULL OR MidtermScore IS NOT NULL OR LabScore IS NOT NULL OR AttendanceScore IS NOT NULL ) );FinalScore允许NULL因期末成绩可能滞后录入但至少一项成绩非空由CK_AtLeastOneScore保证GradeLevel与数值成绩分离便于后续按等级统计优良率ScoreDate默认当天避免手工填错日期。2.2 查询性能奠基为高频场景创建针对性索引仅建好表不够查询慢是桌面应用被弃用的首要原因。以下索引覆盖90%以上查询路径索引名表名字段组合用途说明IX_Student_StudentNoStudentStudentNo按学号快速定位学生登录、查分入口IX_Course_CourseCodeCourseCourseCode按课程代码检索课程信息教师录入成绩时IX_Enrollment_StudentID_SemesterEnrollmentStudentID, Semester查某学生某学期所有选课成绩查询主路径IX_Enrollment_CourseID_SemesterEnrollmentCourseID, Semester查某课程某学期所有选课学生教师端批量操作IX_Score_EnrollmentIDScoreEnrollmentID关联成绩与选课记录JOIN主键IX_Score_ScoreDateScoreScoreDate DESC按时间倒序查最新成绩首页展示“最近成绩”执行建索引命令以IX_Enrollment_StudentID_Semester为例CREATE NONCLUSTERED INDEX IX_Enrollment_StudentID_Semester ON Enrollment (StudentID, Semester) INCLUDE (CourseID, Status, EnrollmentDate);使用INCLUDE包含常用查询字段避免Key Lookup提升SELECT效率NONCLUSTERED是默认类型无需显式声明DESC仅对时间字段有意义其他字段升序即可。注意索引不是越多越好。每增加一个索引INSERT/UPDATE/DELETE性能下降约5~10%。上述6个索引已覆盖全部核心场景开发阶段勿盲目添加。3. C#数据访问层用SqlDataAdapterDataSet实现零ORM的稳定数据流3.1 连接字符串安全配置与动态实例识别SQL Server连接字符串不能硬编码在C#源码中尤其涉及密码。正确做法是将连接信息存入App.config并通过ConfigurationManager读取!-- App.config -- configuration connectionStrings add nameSchoolDB connectionStringData Source.\SQLEXPRESS;Initial CatalogSchoolDB;Integrated Securitytrue; providerNameSystem.Data.SqlClient / /connectionStrings /configurationData Source.\SQLEXPRESS表示本地SQL Server Express实例若为命名实例如MSSQLSERVER需写成.\MSSQLSERVERIntegrated Securitytrue启用Windows身份验证避免明文密码适合内网部署若必须SQL登录格式为User IDsa;Passwordyour_password;但强烈建议仅用于测试环境。C#中获取连接字符串string connStr ConfigurationManager.ConnectionStrings[SchoolDB].ConnectionString;提示首次运行时若提示“无法找到配置文件”请确认项目属性 → “生成” → “输出类型”为“Windows窗体应用(.NET Framework)”且App.config文件已设为“始终复制”。3.2 封装通用数据访问类避免重复打开/关闭连接直接在每个窗体里写SqlConnection.Open()极易出错。我们封装一个静态类DbHelper提供FillDataSet和ExecuteNonQuery两个核心方法public static class DbHelper { private static readonly string ConnStr ConfigurationManager.ConnectionStrings[SchoolDB].ConnectionString; public static DataSet FillDataSet(string sql, params SqlParameter[] parameters) { using (var conn new SqlConnection(ConnStr)) { var adapter new SqlDataAdapter(sql, conn); if (parameters ! null) adapter.SelectCommand.Parameters.AddRange(parameters); var ds new DataSet(); adapter.Fill(ds); // 自动打开/关闭连接异常时释放资源 return ds; } } public static int ExecuteNonQuery(string sql, params SqlParameter[] parameters) { using (var conn new SqlConnection(ConnStr)) { var cmd new SqlCommand(sql, conn); if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteNonQuery(); // 返回影响行数用于判断INSERT/UPDATE是否成功 } } }using确保连接及时释放即使发生异常也不会泄漏SqlDataAdapter.Fill()自动管理连接生命周期比手动Open()/Close()更安全ExecuteNonQuery返回int可用于判断“插入成功几行”例如if (DbHelper.ExecuteNonQuery(insertSql) 0)。3.3 成绩查询的核心SQL与参数化防注入学生查成绩的典型场景输入学号显示该生所有学期、所有课程、各科成绩及等级。SQL需JOIN四张表并按学期倒序排列SELECT s.StudentNo, s.Name, c.CourseCode, c.CourseName, e.Semester, sc.FinalScore, sc.GradeLevel, sc.ScoreDate FROM Student s INNER JOIN Enrollment e ON s.StudentID e.StudentID INNER JOIN Course c ON e.CourseID c.CourseID LEFT JOIN Score sc ON e.EnrollmentID sc.EnrollmentID WHERE s.StudentNo StudentNo ORDER BY e.Semester DESC, sc.ScoreDate DESC;C#调用方式在查询窗体中private void btnSearch_Click(object sender, EventArgs e) { string studentNo txtStudentNo.Text.Trim(); if (string.IsNullOrEmpty(studentNo)) { MessageBox.Show(请输入学号); return; } var param new SqlParameter(StudentNo, studentNo); var ds DbHelper.FillDataSet(sqlQuery, param); if (ds.Tables[0].Rows.Count 0) { dgvResults.DataSource null; MessageBox.Show(未找到该学号的成绩记录); return; } dgvResults.DataSource ds.Tables[0]; }StudentNo参数化防止SQL注入比字符串拼接安全百倍LEFT JOIN Score确保即使成绩未录入也能显示选课记录状态为“A”ORDER BY e.Semester DESC让最新学期排在最前符合用户直觉。4. WinForms界面优化解决UI线程阻塞与数据绑定卡顿问题4.1 避免DataGridView直接绑定大数据量表当一次查询返回上千条记录如全院学生某学期成绩直接dgv.DataSource dataTable会导致界面假死。根本原因是WinForms默认在UI线程执行数据绑定而DataTable构建、列自动推断、单元格渲染全部同步进行。解决方案分页 异步加载 虚拟模式VirtualMode三阶优化4.1.1 基础分页SQL层面限制返回行数修改查询SQL加入OFFSET-FETCHSQL Server 2012支持-- 获取第2页每页20条 SELECT ... FROM ... WHERE ... ORDER BY e.Semester DESC OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY;C#中动态生成OFFSETint pageSize 20; int currentPage 1; // 当前页码从1开始 int offset (currentPage - 1) * pageSize; string sql $ SELECT ... FROM ... WHERE ... ORDER BY e.Semester DESC OFFSET {offset} ROWS FETCH NEXT {pageSize} ROWS ONLY;注意OFFSET-FETCH必须配合ORDER BY否则报错且OFFSET值过大如百万级时性能下降此时应改用WHERE ID lastID游标分页。4.1.2 异步加载用BackgroundWorker解耦耗时操作将数据查询移出UI线程避免界面冻结private BackgroundWorker bgWorker; private void InitBackgroundWorker() { bgWorker new BackgroundWorker(); bgWorker.DoWork BgWorker_DoWork; bgWorker.RunWorkerCompleted BgWorker_RunWorkerCompleted; } private void btnSearch_Click(object sender, EventArgs e) { if (bgWorker.IsBusy) return; bgWorker.RunWorkerAsync(txtStudentNo.Text.Trim()); } private void BgWorker_DoWork(object sender, DoWorkEventArgs e) { string studentNo e.Argument as string; e.Result DbHelper.FillDataSet(sqlWithPagination, new SqlParameter(StudentNo, studentNo)); } private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error ! null) { MessageBox.Show($查询失败: {e.Error.Message}); return; } var ds e.Result as DataSet; dgvResults.DataSource ds?.Tables[0]; }BackgroundWorker是WinForms原生异步方案无需async/await复杂语法DoWork中执行数据库操作RunWorkerCompleted在UI线程回调可安全更新控件。4.2 列标题与数据显示定制提升可读性DataGridView默认列名是数据库字段名如StudentNo用户看不懂。需在绑定后重命名列并设置格式private void ConfigureDataGridView() { dgvResults.AutoGenerateColumns false; // 关闭自动列生成 dgvResults.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName StudentNo, HeaderText 学号, Width 100 }); dgvResults.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName Name, HeaderText 姓名, Width 80 }); dgvResults.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName CourseCode, HeaderText 课程代码, Width 100 }); dgvResults.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName FinalScore, HeaderText 总评成绩, Width 80, DefaultCellStyle new DataGridViewCellStyle { Format N2 } // 保留两位小数 }); dgvResults.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName GradeLevel, HeaderText 等级, Width 60 }); }AutoGenerateColumns false是关键否则自动生成的列会与手动添加的列重复DefaultCellStyle.Format N2将数字显示为92.50而非92.5列宽Width单位为像素根据内容长度调整避免横向滚动。5. 部署与排错解决“连接SQL Server失败”的三大高频场景5.1 连接失败诊断树从错误消息反向定位根因当SqlConnection.Open()抛出异常不要盲目重装SQL Server。先看异常Message字段按以下顺序排查错误消息关键词最可能原因解决动作A network-related or instance-specific error...SQL Server服务未启动或实例名错误运行services.msc→ 找到SQL Server (SQLEXPRESS)→ 右键启动检查连接字符串中Data Source是否匹配SQL Server配置器中的实例名Login failed for user xxx身份验证失败若用Windows认证确认当前Windows账户有SQL Server登录权限SSMS → 安全性 → 登录名 → 右键属性 → 用户映射若用SQL登录确认用户名密码正确且账户启用Cannot open database SchoolDB requested by the login数据库不存在或未附加在SSMS中执行SELECT name FROM sys.databases确认数据库存在若不存在右键“数据库” → “附加”选择.mdf文件路径The target principal name is incorrectKerberos双跳问题域环境仅企业域环境出现开发机建议改用Windows认证或SQL登录避开SPN配置5.2 一键检测脚本用PowerShell验证SQL Server可达性将以下脚本保存为Check-SqlConnection.ps1右键“使用PowerShell运行”$server .\SQLEXPRESS $database SchoolDB try { $connString Server$server;Database$database;Integrated Securitytrue; $conn New-Object System.Data.SqlClient.SqlConnection($connString) $conn.Open() Write-Host ✅ 连接成功服务器: $server数据库: $database -ForegroundColor Green $conn.Close() } catch { Write-Host ❌ 连接失败: $($_.Exception.Message) -ForegroundColor Red }替换$server为你实际的实例名如localhost\SQLEXPRESS或.\MSSQLSERVER此脚本不依赖.NET程序纯PowerShell排除C#环境干扰。5.3 安装SQL Server Express 2019的最小化步骤无GUI很多学校机房禁用图形安装。可用命令行静默安装SQL Server Express 2019SQLEXPR_x64_ENU.exe /Q /ACTIONInstall /FEATURESSQL,Tools /INSTANCENAMESQLEXPRESS /SECURITYMODESQL /SAPWDYourStrongPass1 /SQLSYSADMINACCOUNTSBUILTIN\Administrators /IACCEPTSQLSERVERLICENSETERMS/Q表示静默安装无界面/INSTANCENAMESQLEXPRESS指定实例名与连接字符串一致/SECURITYMODESQL启用混合模式/SAPWD设置sa密码必须含大小写字母、数字、符号/SQLSYSADMINACCOUNTS将本地管理员组加入sysadmin角色避免权限不足。注意下载SQLEXPR_x64_ENU.exe需从Microsoft官网获取搜索“SQL Server 2019 Express Download”选择“Download SQL Server Express”按钮勿从第三方渠道下载。5.4 C#项目发布时的必备清单将编译好的exe发给教师使用需附带以下文件文件来源说明YourApp.exe项目bin\Release\主程序YourApp.exe.config同上包含连接字符串必须与目标机SQL实例名匹配System.Data.dll.NET Framework自带通常无需复制但若目标机无.NET 4.7.2需确认SchoolDB.mdfSchoolDB_log.ldf数据库备份还原后得到数据库文件放在程序同目录或指定路径在App.config中可将数据库路径设为相对路径add nameSchoolDB connectionStringData Source.\SQLEXPRESS;AttachDbFilename|DataDirectory|\SchoolDB.mdf;Integrated Securitytrue; providerNameSystem.Data.SqlClient /|DataDirectory|自动解析为程序所在目录无需硬编码绝对路径发布时将.mdf和.ldf文件复制到YourApp.exe同级目录即可。最后一步在目标电脑上右键YourApp.exe→ “以管理员身份运行”——这是绕过UAC对SQL Server实例注册表写入权限的最简方案。本文还有配套的精品资源点击获取