简介:这份源码包是C#编写的在线考试系统完整项目,面向需要学习C# Web开发或部署在线考试平台的学生、教师与开发者。系统基于VS2010与SQL Server 2008,实现学生、教师、管理员三角色管理,涵盖自动生成试卷、教师自拟题目、自动批改、学生信息与成绩管理等功能。压缩包共133个文件,以C#源码(47个cs)、ASP.NET页面(39个aspx)为主,辅以数据库文件(mdf/ldf)、样式配置及少量图片素材,包体约1.66MB,便于本地还原与调试。已有1379人学习下载。通过阅读源码,可深入理解C#分层架构、数据库表设计、题库随机抽取及评分规则实现,也可直接修改题目与权限,作为课程设计或毕业设计的实用参考。
1. 拿到一套C#在线考试系统源码,先别急着编译
从网盘拉下来解压后,这套C#在线考试系统源码其实是个 WinForms 桌面工程,不少人的第一反应是“不是在线考试吗,怎么没有网页”。它确实不是 B/S 网页,而是 C/S 架构:Windows 客户端 + SQL Server。教师端负责题目自拟、批量导入、手工组卷,学生端负责随机抽题、在线答题、自动交卷,管理员端管账号和考试场次。这套源码把三角色的逻辑放在一个解决方案里,主线清晰,特别适合课程设计、毕业设计拿来改界面后直接交付。但最忌拿到手就按 F5 跑,先得把数据库脚本建起来,否则登录窗体都弹不出来。
2. 三角色权限体系与自动批改:四个模块怎么咬合
这套系统的入口是一个登录窗体,角色管理没那么玄,users 表里一个 Role int 字段就分完了。真正决定代码质量的,是登录后每个角色看到的界面和能执行的 SQL 不一样。下面把登录分发、题库设计、抽题算法、自动批改四块拆开讲,这也是你改代码时最可能动刀子的位置。
2.1 三角色入口:一个 users 表用 Role 字段区分
常见做法是登录时查 users 表,拿到 Role 后路由到不同的窗体。密码存储一般是 MD5 十六进制字符串,不是明文,这也是课程设计里最通用的做法。C# 端封装的 MD5 方法长这样:
private string Md5(string input) { using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create()) { byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); StringBuilder sb = new StringBuilder(); foreach (byte b in bytes) sb.Append(b.ToString("x2")); return sb.ToString(); } }这段代码把用户输入的密码先转 UTF-8 字节,再算 MD5,最后转成 32 位小写十六进制。注意 x2 的意思是每个字节固定输出两位小写十六进制,这样出来的结果和 SQL Server 端HASHBYTES('MD5', '123456')转出的小写格式能对齐,否则两边算出来不一样,登录永远失败。
登录分发逻辑的核心代码:
private void btnLogin_Click(object sender, EventArgs e) { string userName = txtUserName.Text.Trim(); string pwdMd5 = Md5(txtPassword.Text.Trim()); string sql = "SELECT UserID, UserName, Role FROM users WHERE UserName=@u AND Password=@p"; using (SqlConnection conn = new SqlConnection(AppConfig.ConnStr)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@u", userName); cmd.Parameters.AddWithValue("@p", pwdMd5); conn.Open(); using (SqlDataReader reader = cmd.ExecuteReader()) { if (reader.Read()) { int role = reader.GetInt32(2); GlobalCache.LoginUser = new UserModel { UserId = reader.GetInt32(0), UserName = reader.GetString(1), Role = role }; if (role == 0) new FrmAdmin().Show(); else if (role == 1) new FrmTeacher().Show(); else new FrmStudent().Show(); this.Hide(); } else { MessageBox.Show("用户名或密码错误"); } } } }逻辑说明:先用参数化 SQL 查询用户,避免字符串拼接导致 SQL 注入;读到用户后把当前登录人信息放进 GlobalCache,方便后续窗体直接拿 UserId 和 Role。参数说明:@u 是用户名,@p 是 MD5 后的密码;Role 用 0、1、2 分别对应管理员、教师、学生。
这里有个容易翻车的地方:new 出来的子窗体关闭时,如果直接 Application.Exit() 会让整个进程退出,但只 Hide 登录窗体又会让任务管理器里残留进程。我一般建议在 Program.cs 里把主窗体设计成带容器的 MainForm,登录窗体 Dialog 方式弹出,这样角色切换更干净,也方便演示时来回退出。
2.2 题目自拟:题库表与题型约束
“题目自拟”在教师端的体现就是题库表 questions,你可以手工录入,也可以从 Excel 导入。这张表是整套系统的地基,字段设计直接决定后面抽题和批改好不好写。这套源码里常见的字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| QuestionID | int 自增 | 题库主键 |
| Type | int | 1-单选 2-多选 3-判断 4-简答 |
| Content | nvarchar(500) | 题干文本 |
| OptionA ~ OptionD | nvarchar(200) | 选项内容,非选择题可为空 |
| RightAnswer | nvarchar(50) | 单选存“A”,多选存“ABD”,判断存“T/F” |
| Score | decimal(4,1) | 单题分值 |
| Difficulty | int | 1容易、2中等、3难 |
| Status | int | 1可用,0停用,抽题时过滤 |
这里有个容易忽略的点:判断题的 RightAnswer 在导入模板里最好统一成“T/F”或“对/错”其中一种,不要混着录。自动批改的代码通常只认一种格式,混了就会出现有的题永远判不对。多选的答案顺序也要约定好,按字母排序存,比如正确选项是“A、B、D”,就存“ABD”,学生作答时如果选“ADB”,批改逻辑就要做字符集合比较,而不是简单字符串相等。
2.3 随机抽题组卷:ORDER BY NEWID() 的分层抽取
教师组卷时如果选择“随机生成”,系统就会按题型和难度从题库里抽取题目。SQL Server 里的随机抽取最常见的方式是ORDER BY NEWID(),每行生成一个随机 GUID,排序后取前 N 条。核心 SQL 是这样:
DECLARE @PaperId int = 202504001; DECLARE @Type int = 1; -- 1 为单选题 DECLARE @Num int = 20; -- 本次抽题数量 DECLARE @Score decimal(4,1) = 5; -- 单题分值 INSERT INTO paper_details(PaperID, QuestionID, Score) SELECT @PaperId, QuestionID, @Score FROM ( SELECT TOP (@Num) q.QuestionID FROM questions q WHERE q.Type = @Type AND q.Difficulty <= 3 AND q.Status = 1 ORDER BY NEWID() ) AS t;逻辑说明:内存层先按题型过滤,再按难度过滤,最后用 NEWID() 打乱顺序取前 @Num 条。参数说明:@PaperId 是试卷主键,你要先往 papers 表插一条记录拿到自增 ID;@Type 和 @Num 决定抽什么题、抽多少;@Score 是这批题的单题分值。
需要注意的是TOP (@Num)括号不能省,SQL Server 里参数化 TOP 必须带括号,否则直接报语法错误。这套源码抽完题后 paper_details 就是一张试卷的快照,之后不管题库怎么改,已经生成的试卷不受影响,这点对考试公平性很重要。如果你的版本是 MySQL,这段 SQL 需要改成ORDER BY RAND(),但大数据量下性能会差一些,课程设计规模不用担心。
2.4 自动批改边界:哪些题能自动判,哪些必须人工复核
自动批改不是全自动。单选、多选、判断这类客观题可以机器判,简答题基本只能交人工。把这层边界理清楚,你才能理解为什么这套源码的成绩表里既有 AutoScore 又有 FinalScore 两个字段。
自动批改的核心循环:
decimal autoScore = 0m; DataTable dt = ds.Tables["answers"]; // 包含学生的作答和标准答案 foreach (DataRow row in dt.Rows) { int qType = Convert.ToInt32(row["Type"]); if (qType == 4) continue; // 简答题,走人工评分 string stdAns = row["RightAnswer"].ToString().Trim().ToUpper(); string stuAns = row["StudentAnswer"].ToString().Trim().ToUpper(); if (stdAns.Equals(stuAns)) { autoScore += Convert.ToDecimal(row["Score"]); } }逻辑说明:循环里先判断题型,简答题直接跳过,不参与自动计分;客观题把学生答案和标准答案都去空格、转大写后再比较。这里用 Trim() 是血泪经验,Excel 导入的答案很容易带前后空格,不去掉就会出现学生明明选对了却判 0 分。
参数说明:如果多选支持“ABD”和“ADB”都算对,这段代码是不够的,需要把字符串拆成字符集合,用 SetEquals 之类的逻辑判断。主观题的 ManualScore 字段默认是 0,教师在成绩管理界面里手动录入后,最终成绩才完整。
3. 从解压到跑起来:环境配置、建库、第一次上线
这套源码要跑起来,关键不在 C# 代码,而在环境。下面按顺序走一遍,照着做就能在半小时内把系统拉起来,不至于第一次打开就面对一堆红色报错。
3.1 修改连接字符串:App.config 里的三处关键参数
源码的连接字符串一般放在 App.config 或主窗体的构造函数里。WinForms 程序最常见的配置是这样:
<connectionStrings> <add name="OnlineExam" connectionString="Data Source=.;Initial Catalog=OnlineExam;User ID=sa;Password=123456;TrustServerCertificate=True" providerName="System.Data.SqlClient" /> </connectionStrings>参数说明:Data Source 是数据库地址,点号表示本机默认实例,如果是命名实例要写成计算机名\SQLEXPRESS;Initial Catalog 是数据库名,要和初始化脚本里创建的库名一致;User ID 和 Password 是 SQL Server 登录账号。如果本机用的是 Windows 身份验证,把User ID=sa;Password=123456;整段换成Integrated Security=True;就行。
注意:改完 App.config 要重启程序,连接字符串只在程序启动时读一次,运行中修改不会生效。很多新手在这个坑里浪费半小时,改完配置不重启就直接 F5。
3.2 数据库从零初始化:脚本与账号准备
别指望 Handy 的界面帮你建库,这份源码的库是脚本形成的。用 SQL Server Management Studio 打开源码里的 Database.sql 执行,核心是这几步:
IF DB_ID('OnlineExam') IS NULL BEGIN CREATE DATABASE OnlineExam; END GO USE OnlineExam; GO IF OBJECT_ID('users') IS NOT NULL DROP TABLE users; IF OBJECT_ID('questions') IS NOT NULL DROP TABLE questions; IF OBJECT_ID('papers') IS NOT NULL DROP TABLE papers; IF OBJECT_ID('paper_details') IS NOT NULL DROP TABLE paper_details; IF OBJECT_ID('exam_sessions') IS NOT NULL DROP TABLE exam_sessions; IF OBJECT_ID('answers') IS NOT NULL DROP TABLE answers; GO CREATE TABLE users( UserID int IDENTITY(1,1) PRIMARY KEY, UserName nvarchar(50) UNIQUE NOT NULL, Password nvarchar(64) NOT NULL, Role int NOT NULL DEFAULT 2, RealName nvarchar(50) NULL, ClassName nvarchar(100) NULL ); GO CREATE TABLE questions( QuestionID int IDENTITY(1,1) PRIMARY KEY, Type int NOT NULL, Content nvarchar(500) NOT NULL, OptionA nvarchar(200) NULL, OptionB nvarchar(200) NULL, OptionC nvarchar(200) NULL, OptionD nvarchar(200) NULL, RightAnswer nvarchar(50) NOT NULL, Score decimal(4,1) NOT NULL, Difficulty int NOT NULL DEFAULT 2, Status int NOT NULL DEFAULT 1, CreateTime datetime NOT NULL DEFAULT GETDATE() ); GO INSERT INTO users(UserName, Password, Role, RealName) VALUES('admin', CONVERT(varchar(32), HASHBYTES('MD5', '123456'), 2), 0, '系统管理员');逻辑说明:这个脚本先建库,然后删旧表重建,最后插入一个管理员账号。参数说明:HASHBYTES 是 SQL Server 计算 MD5 的函数,CONVERT 的第三个参数 2 表示把结果转成不带 0x 前缀的十六进制字符串,这样和 C# 端的 MD5 结果对得上。papers、exam_sessions、answers 等表按同样的思路在源码脚本里补齐,字段对应我第四节列的结构。
注意:这段脚本会清空所有表数据。如果数据库里已经有考试记录,执行前务必先备份。实际项目里我更习惯把 DROP 语句单独放一个文件,正式建库脚本只跑 CREATE,避免手滑。
3.3 教师出题:手工录入与 Excel 批量导入的细节
题目自拟有两种录题方式:界面上逐题录入,或者用 Excel 模板批量导入。批量导入的代码普遍用 NPOI 读 xlsx,因为 NPOI 是开源库,服务器上不装 Office 也能跑。关键循环长这样:
using NPOI.SS.UserModel; using NPOI.XSSFUserModel; using (FileStream fs = File.OpenRead("题目模板.xlsx")) { IWorkbook workbook = new XSSFWorkbook(fs); ISheet sheet = workbook.GetSheetAt(0); for (int i = 1; i <= sheet.LastRowNum; i++) { IRow row = sheet.GetRow(i); if (row == null) continue; // 空白行直接跳过 string content = row.GetCell(0)?.ToString()?.Trim(); if (string.IsNullOrEmpty(content)) continue; // 题干为空的行跳过 // GetCell(1) 题型,GetCell(2)~GetCell(5) 选项A-D // GetCell(6) 正确答案,GetCell(7) 难度,GetCell(8) 分值 InsertQuestion(content, row.GetCell(1)?.ToString()?.Trim(), row.GetCell(2)?.ToString()?.Trim(), row.GetCell(3)?.ToString()?.Trim(), row.GetCell(4)?.ToString()?.Trim(), row.GetCell(5)?.ToString()?.Trim(), row.GetCell(6)?.ToString()?.Trim(), row.GetCell(7)?.ToString()?.Trim(), row.GetCell(8)?.ToString()?.Trim()); } }逻辑说明:这个循环从第二行开始读,第一行是表头。GetCell(0)?.ToString()的写法是必须的,xlsx 里空单元格返回 null,直接调用 ToString() 会报空引用异常。LastRowNum 是最后有内容行的下标,Row.GetCell 返回的单元格如果没内容也可能是 null,所以每一列都用带 ? 的链式调用。参数说明:列顺序是模板里约定的,一旦模板顺序变了,这里的下标全部要跟着调。
3.4 学生交卷事务:避免半截答案进库
学生交卷这个动作,表面上是把 DataGridView 或界面控件里的答案写进数据库,实际上必须保证“写答案”和“标记已交卷”这两件事同时成功。很多源码这里翻车,写到一半报错,学生既交不了卷,成绩也没存上。用事务包起来的写法是标准做法:
using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); SqlTransaction tx = conn.BeginTransaction(); try { foreach (var item in answers) { string sql = @"INSERT INTO answers(ExamID, QuestionID, StudentAnswer) VALUES(@eid, @qid, @ans)"; using (SqlCommand cmd = new SqlCommand(sql, conn, tx)) { cmd.Parameters.AddWithValue("@eid", examId); cmd.Parameters.AddWithValue("@qid", item.QuestionId); cmd.Parameters.AddWithValue("@ans", item.StudentAnswer ?? ""); cmd.ExecuteNonQuery(); } } string update = @"UPDATE exam_sessions SET IsSubmit=1, SubmitTime=GETDATE(), AutoScore=@score WHERE ExamID=@eid AND IsSubmit=0"; using (SqlCommand ucmd = new SqlCommand(update, conn, tx)) { ucmd.Parameters.AddWithValue("@score", autoScore); ucmd.Parameters.AddWithValue("@eid", examId); if (ucmd.ExecuteNonQuery() == 0) throw new Exception("试卷已提交,请勿重复操作"); } tx.Commit(); } catch { tx.Rollback(); throw; } }逻辑说明:先把所有答案插入 answers 表,再更新 exam_sessions 的提交状态。关键在 UPDATE 语句里带上了IsSubmit=0条件,如果影响行数为 0,说明这条考试记录已经提交过,直接抛异常回滚,从数据库层面拦住重复交卷。参数说明:answers 里的 StudentAnswer 用??把 null 转成空字符串,避免空值在批改比较时出问题。autoScore 是在事务外先算好的客观题分数,事务里只负责写入。
4. 常见问题与排查记录:四个踩过的坑
课程设计类源码大家都知道,能跑和能演示是两回事。下面几个坑是我实际用这套系统时踩过的,每个都按现象、原因、解决三步写清楚,遇到类似问题可以直接照方抓药。
4.1 批量导入 Excel 后中文全部变成问号
现象:通过“Excel 导入题库”功能导了 50 道题,界面上看到的中文全变成了“???”,英文和数字正常,数据库里也是问号。
原因:这套源码的导入模板如果是 xlsx,一般不会乱码,乱码多数出在 CSV 模板场景。CSV 文件用 Excel 打开后默认按 ANSI 编码保存,而源码里用 StreamReader 默认按 UTF-8 读取,两边编码不一致,中文就牺牲了。另外还有种情况是单元格里本身有全角空格或特殊字符,NPOI 读取时GetCell(0).ToString()把格式信息也带出来了,不过那个一般不会乱码。
解决:优先用 xlsx 模板而不是 CSV 模板;如果用 CSV,读取时要指定编码,常见做法是把new StreamReader(path)改成new StreamReader(path, Encoding.GetEncoding("GB2312"))。导入前先用记事本打开模板确认编码,比在代码里反复试验快得多。如果已经有乱码数据进库,写一条DELETE FROM questions WHERE Content LIKE N'%???%'清理掉,再重新导入。
4.2 学生明明答对了,自动批改却是 0 分
现象:单选题选了 A,标准答案也是 A,交卷后成绩单显示这道题 0 分。
原因:最常见的两个来源。一是标准答案或学生答案带首尾空格,Excel 模板里单元格不小心按了空格,存进数据库没被注意到;二是判断题的标准答案是“T”或“F”,而界面里的选项文本是“正确 / 错误”,学生端存进 answers 表的是“正确”,批改代码拿“T”和“正确”做字符串比较,永远不相等。
解决:先把批改代码里的比较统一成Trim().ToUpper(),这能解决八成问题。判断题的答案规范在 Excel 模板里就定好:数据库存“T/F”,界面选项的 Value 也绑成“T/F”,显示文本才用“正确 / 错误”,不要把两件事混在一个字段里。历史脏数据用 SQL 清洗:UPDATE questions SET RightAnswer = 'T' WHERE RightAnswer = '正确',再把学生答案表同步改掉。
4.3 交卷按钮点两次,出现两条考试记录
现象:学生作答完点了一下“交卷”,界面卡了半秒,又点了一下,结果成绩单里出现两条相同记录,分数还被算了两次。
原因:界面上没有做防重复提交,按钮点击事件里直接执行插入逻辑,第一次请求没返回时第二次点击已经进来,数据库端也没有唯一约束把关。这就是典型的前端控制不住、后端也不设防。
解决:两层保险。第一层是界面,按钮点击后立刻btnSubmit.Enabled = false; Application.DoEvents();,防止用户物理双击;第二层在数据库,answers 表加联合唯一索引UNIQUE(ExamID, QuestionID),同一道题只能有一条作答记录。最关键的是交卷事务里那条 UPDATE 语句要带IsSubmit=0条件,影响行数为 0 就说明已经交过卷,直接回滚并提示“请勿重复提交”。
4.4 演示前一天发现学生账号密码忘了
现象:要演示学生端,但记不住当时给学生设的密码,试了十几次都不对。
原因:密码是 MD5 存储的,不可逆,谁都没法从数据库反推明文。这就暴露了一个设计问题:系统里没有“重置密码”功能,管理员端只能看账号列表,改不了密码。
解决:不用重新写功能,直接跑一条 UPDATE 把指定学生的密码重置掉。注意 MD5 要和 C# 端算法一致,SQL Server 里这样写:
UPDATE users SET Password = CONVERT(varchar(32), HASHBYTES('MD5', '123456'), 2) WHERE UserName = 'student01';执行完再用 student01 / 123456 登录。从那以后我每次交付这套系统前,都会顺手把学生账号密码统一重置成 123456,并把这条 SQL 存在项目目录里一个叫 reset_password.sql 的文件中,省得演示现场翻手机找笔记。
5. 快速验收:三条 SQL 核一遍自动批改和成绩统计
系统跑起来后,别急着开始录 500 道题。先用三条 SQL 把自动批改、成绩统计和题库质量核一遍,确认核心逻辑没毛病,再往库里灌数据。这三条 SQL 也可以作为你理解这套源码数据流向的索引。
5.1 按考试场次统计已交卷人数、平均分、及格率
验收自动批改有没有生效,最直接的方式是看一场考试的交卷人数和平均分。如果一场考试有 30 个学生,已交卷人数是 30,但平均分是 0,那基本可以断定批改环节挂掉了。这条 SQL 适合跑在 exam_sessions 表上:
SELECT p.PaperName, COUNT(es.ExamID) AS 应考人数, SUM(CASE WHEN es.SubmitTime IS NOT NULL THEN 1 ELSE 0 END) AS 已交卷, CAST(AVG(es.AutoScore) AS DECIMAL(4,1)) AS 客观题平均分, SUM(CASE WHEN es.AutoScore >= 60 THEN 1 ELSE 0 END) AS 及格人数 FROM exam_sessions es JOIN papers p ON es.PaperID = p.PaperID WHERE p.PaperName LIKE N'%期中%' GROUP BY p.PaperName;逻辑说明:COUNT 统计所有发了卷的学生,SUM 加 CASE 统计已经交卷的人,AVG 算已交卷学生的客观题平均分。参数说明:及格线 60 是按百分制算的,如果你的试卷满分不是 100,这里要按比例调整。这套源码通常一张卷对应一场考试,如果一场考试有多张卷子,GROUP BY 的粒度就需要改成试卷 ID + 场次 ID,否则统计口径会混。
5.2 人工评分后重算最终成绩
简答题是人工评分的,教师在界面里给完 ManualScore 后,最终成绩不会自动变。很多版本需要点一个“重算”按钮,但有时候忘了点,成绩单导出的还是旧分数。与其依赖界面上那只按钮,不如直接跑 SQL 把最终成绩算一遍:
UPDATE es SET es.FinalScore = ISNULL(( SELECT SUM(q.Score) FROM answers a JOIN questions q ON a.QuestionID = q.QuestionID WHERE a.ExamID = es.ExamID AND q.Type <> 4 AND a.IsRight = 1 ), 0) + ISNULL(( SELECT SUM(a.ManualScore) FROM answers a WHERE a.ExamID = es.ExamID AND a.ManualScore IS NOT NULL ), 0) FROM exam_sessions es;逻辑说明:FinalScore 由两块组成,客观题部分找 IsRight=1 的作答,按问题分值求和;主观题部分把人工分加起来。ISNULL 的作用是当某一块没有记录时按 0 算,避免整个表达式变成空值。参数说明:如果你的源码里客观题分值不存 q.Score 而是直接冗余在 answers 表里,把 SUM 的参数换成 answers 表里的分值字段即可。
提示:执行这条 UPDATE 前最好先备份 exam_sessions 和 answers 两张表,或者加个 exe 事务包一下。人工评分有时候是批量改的,跑错了还能回滚。
5.3 查题库里有没有重复题和空题
抽题系统最大的隐患不是题量少,而是重复题和空题。重复题意味着同一张试卷里可能出现两道一模一样的题干,空题直接导致学生看到一个没有内容的选项。这两类数据用肉眼很难发现,SQL 一查就现形:
SELECT Content, COUNT(*) AS 重复次数 FROM questions GROUP BY Content HAVING COUNT(*) > 1;这条 SQL 专门找完全重复的题干。参数说明:如果两题的文字描述一样但选项顺序不同,GROUP BY Content 是查不出来的,需要再按 Content + OptionA + OptionB 组合查。空题用另一条:
SELECT QuestionID, Type, Content FROM questions WHERE Content IS NULL OR LEN(Content) = 0;逻辑说明:LEN 会忽略尾随空格,所以LEN(Content)=0能把纯空格题也查出来。查出问题数据后,删掉之前先确认这个题有没有被抽进 paper_details,如果已经进卷了,直接删题会把试卷中间抽出一个空位,正确做法是先删 paper_details 里的关联行,再删题库记录。
6. 给评委演示之前,先做一遍数据清理
课程设计答辩最怕的就是演示翻车。翻了题的重复数据、上次班学生考过的旧记录、忘了密码的账号,都会让现场变得很难看。所以每次演示前,我都会强制走一遍清理流程,把系统恢复到“考了一次试但还没人交卷”的干净状态。核心就是下面这段 SQL:
USE OnlineExam; GO DELETE FROM answers; DELETE FROM exam_sessions; DELETE FROM paper_details; GO DBCC CHECKIDENT('exam_sessions', RESEED, 0); DBCC CHECKIDENT('answers', RESEED, 0); GO UPDATE users SET Password = CONVERT(varchar(32), HASHBYTES('MD5', '123456'), 2) WHERE Role = 2; GO SELECT UserName, RealName, Role FROM users;逻辑说明:这个脚本把答题记录、考试场次、试卷明细全部清空,但保留题库和用户表,这样演示时你不需要重新录题,只需要新建一场考试让学生进入。删除顺序要从子表到主表:先 answers,再 exam_sessions,最后 paper_details,避免残留孤儿数据。DBCC CHECKIDENT 把自增 ID 重置回 0,演示时新产生的记录从 1 开始编号,界面上看起来整整齐齐。最后把学生账号密码统一重置成 123456,避免现场发现学生端登不进去。
这套源码的表大多用逻辑关联,没有强外键约束,所以 DELETE 可以按任意顺序执行。如果你的版本里加了外键或者级联删除,需要把顺序反过来:先删主表会导致外键冲突报错。另外,如果演示前教师刚导入了 500 道题,不要执行DELETE FROM questions,清理脚本特意没碰题库,就是不想让你好不容易准备的题目一夜回到解放前。
从那以后,我每次拿这套系统出去演示或者交给别人验收,都会强制走一遍清库、重置密码、查重复题三步流程。不是为了秀操作,而是为了不让自己在现场被脏数据搞得手忙脚乱。希望帮到你。
本文还有配套的精品资源,点击获取