简介:本资源是一套基于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')), -- A=Active, D=Dropped, W=Withdrawn 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_StudentNo | Student | StudentNo | 按学号快速定位学生(登录、查分入口) |
| IX_Course_CourseCode | Course | CourseCode | 按课程代码检索课程信息(教师录入成绩时) |
| IX_Enrollment_StudentID_Semester | Enrollment | StudentID, Semester | 查某学生某学期所有选课(成绩查询主路径) |
| IX_Enrollment_CourseID_Semester | Enrollment | CourseID, Semester | 查某课程某学期所有选课学生(教师端批量操作) |
| IX_Score_EnrollmentID | Score | EnrollmentID | 关联成绩与选课记录(JOIN主键) |
| IX_Score_ScoreDate | Score | ScoreDate 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#数据访问层:用SqlDataAdapter+DataSet实现零ORM的稳定数据流
3.1 连接字符串安全配置与动态实例识别
SQL Server连接字符串不能硬编码在C#源码中,尤其涉及密码。正确做法是将连接信息存入App.config,并通过ConfigurationManager读取:
<!-- App.config --> <configuration> <connectionStrings> <add name="SchoolDB" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=SchoolDB;Integrated Security=true;" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration>Data Source=.\SQLEXPRESS表示本地SQL Server Express实例;若为命名实例(如MSSQLSERVER),需写成.\MSSQLSERVER;Integrated Security=true启用Windows身份验证,避免明文密码,适合内网部署;- 若必须SQL登录,格式为
User ID=sa;Password=your_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-FETCH(SQL Server 2012+支持):
-- 获取第2页,每页20条 SELECT ... FROM ... WHERE ... ORDER BY e.Semester DESC OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY;C#中动态生成OFFSET:
int 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 incorrect | Kerberos双跳问题(域环境) | 仅企业域环境出现,开发机建议改用Windows认证或SQL登录,避开SPN配置 |
5.2 一键检测脚本:用PowerShell验证SQL Server可达性
将以下脚本保存为Check-SqlConnection.ps1,右键“使用PowerShell运行”:
$server = ".\SQLEXPRESS" $database = "SchoolDB" try { $connString = "Server=$server;Database=$database;Integrated Security=true;" $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 2019:
SQLEXPR_x64_ENU.exe /Q /ACTION=Install /FEATURES=SQL,Tools /INSTANCENAME=SQLEXPRESS /SECURITYMODE=SQL /SAPWD="YourStrong@Pass1" /SQLSYSADMINACCOUNTS="BUILTIN\Administrators" /IACCEPTSQLSERVERLICENSETERMS/Q表示静默安装,无界面;/INSTANCENAME=SQLEXPRESS指定实例名,与连接字符串一致;/SECURITYMODE=SQL启用混合模式,/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.mdf&SchoolDB_log.ldf | 数据库备份还原后得到 | 数据库文件,放在程序同目录或指定路径 |
在App.config中,可将数据库路径设为相对路径:
<add name="SchoolDB" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\SchoolDB.mdf;Integrated Security=true;" providerName="System.Data.SqlClient" />|DataDirectory|自动解析为程序所在目录,无需硬编码绝对路径;- 发布时将
.mdf和.ldf文件复制到YourApp.exe同级目录即可。
最后一步:在目标电脑上右键YourApp.exe→ “以管理员身份运行”——这是绕过UAC对SQL Server实例注册表写入权限的最简方案。
本文还有配套的精品资源,点击获取