首页 / 资讯中心 / 文章详情

ASP.NET MVC + EF + Bootstrap 教务系统实战:排课冲突检测与选课并发控制

ASP.NET MVC + EF + Bootstrap 教务系统实战:排课冲突检测与选课并发控制 ★ FEATURED ARTICLE
简介这是一套基于ASP.NET MVC与EF框架构建的教务信息管理系统完整源码面向计算机专业学生、课程设计开发者及需要学习企业级Web开发的初中级程序员。系统采用Bootstrap前端样式界面美观大方并引入HighCharts统计图展示数据适合用作毕业设计、课程实训或技术练手项目。压缩包共619个文件约41.22MB其中包含168个dll动态链接库、115个xml配置文件、66个cshtml视图页面、45个C#源码文件以及36个js脚本、12个css样式表等完整覆盖了从后端逻辑到前端展示的各类资源。系统划分管理员、教师、学生三种角色功能涵盖学生管理、教师管理、班级管理、课程管理与成绩管理权限清晰、模块完善。目前已有619人学习下载读者可借此掌握MVC分层架构、EF数据持久化、Bootstrap响应式布局及统计图表集成等实用技能快速搭建可运行的教务管理平台。1. 从一张排课冲突表说起这套教务系统到底要解决什么每年开学前两周教务办公室最怕的不是数据量大而是改一处、崩三处。一个班级的课程时间动了教师课表、教室占用、学生选课记录全得跟着重算靠 Excel 手工维护冲突检测基本靠肉眼。这套基于 ASP.NET MVC EF Bootstrap 的教务信息管理系统瞄准的就是这类牵一发动全身的场景把学生、教师、课程、班级、教室、排课、成绩、统计报表收进一个统一的数据模型里用 ORM 管住关联关系用前端框架把操作门槛压到最低。它适合两类人——需要交一份完整 Web 课设/毕设的学生以及要给中小型教学机构搭一套内部管理后台的开发者。整套方案不追求大而全追求的是能跑通、能演示、能讲清设计取舍。2. 需求拆解与技术选型为什么是 MVC EF Bootstrap 这套组合2.1 先把角色和用例理清楚教务系统的需求看着杂其实按角色切一刀就清楚了。系统里通常有四类角色管理员、教务人员、教师、学生。管理员管账号和基础数据教务人员管排课、班级、课程计划教师看自己的课表和录入成绩学生查课表、查成绩、选课。把用例落到功能模块上大致是这么几块模块核心功能主要使用角色用户与权限登录、角色分配、密码管理管理员基础数据学生/教师/班级/教室/课程维护管理员、教务排课管理课程安排、时间冲突检测、课表生成教务选课管理选课、退课、容量控制学生成绩管理成绩录入、修改、查询教师、学生统计报表成绩分布、选课人数、课程统计图教务、管理员这张表不是摆设它直接决定了后面数据库要建几张主表、控制器要分几个区域Area。我一般建议学生先把这张表画出来再动手写代码否则做到一半发现选课和排课共用了一张表却职责不清返工成本很高。2.2 技术选型每一层为什么这么选ASP.NET MVC负责的是请求分发和页面组织。选它而不是 Web Forms核心原因是教务系统里列表 表单 局部刷新的场景特别多MVC 的 Controller/Action 模型让每个操作都有明确的入口配合路由能做出干净的 URL比如/Course/Schedule?classId3调试和后期维护都直观。Entity Framework负责数据访问。教务系统最大的痛点是表之间的关联多——一个学生属于一个班级一个班级有多门课一门课有多个选课记录。用 EF 的 Code First可以用导航属性把这些关系直接表达在 C# 类里查询时用 LINQ 就能跨表取数不用手写一堆 JOIN。代价是要理解延迟加载和 N1 查询的坑这个后面避坑章节会专门讲。Bootstrap负责界面。教务系统的用户不都是技术人界面乱一点操作就出错。Bootstrap 的栅格和组件库能让表格、表单、模态框快速成型配合它的响应式特性教务人员在平板上也能用。统计图这块通常用 ECharts 或 Chart.js 在前端渲染后端只负责把聚合好的数据以 JSON 返回。选前端渲染而不是后端生成图片是因为交互性更好——鼠标悬停看数值、点击图例筛选这些体验对教务分析很实用。2.3 项目分层结构一个能讲清楚的分层是这样的EduAdmin.Web 表现层Controllers、Views、静态资源 EduAdmin.Services 业务层排课冲突检测、选课逻辑、成绩计算 EduAdmin.Data 数据层DbContext、实体类、迁移 EduAdmin.Models 视图模型与 DTO分层不是为了好看是为了让排课冲突检测这种核心逻辑能单独测试。如果把它写在 Controller 里改一次就得重新点一遍页面验证效率极低。3. 数据库与实体设计用 Code First 把关联关系钉死3.1 核心实体与关系教务系统的实体不算多但关系要理清。核心实体有Student、Teacher、Course、Class班级、Classroom、Schedule排课记录、Enrollment选课记录、Score成绩。关系上一个 Class 有多个 Student一个 Teacher 教多门 Course一个 Course 对应多条 Schedule一个 Student 对应多条 Enrollment每条 Enrollment 关联一门 Course并可能带一条 Score。用 Code First 表达出来大概是这样public class Student { public int Id { get; set; } public string Name { get; set; } public string StudentNo { get; set; } // 学号唯一 public int ClassId { get; set; } public Class Class { get; set; } // 导航属性所属班级 public ICollectionEnrollment Enrollments { get; set; } } public class Course { public int Id { get; set; } public string Name { get; set; } public int TeacherId { get; set; } public Teacher Teacher { get; set; } public int Credit { get; set; } // 学分 public ICollectionSchedule Schedules { get; set; } } public class Schedule { public int Id { get; set; } public int CourseId { get; set; } public Course Course { get; set; } public int ClassroomId { get; set; } public Classroom Classroom { get; set; } public int DayOfWeek { get; set; } // 1-7 public int StartPeriod { get; set; } // 第几节开始 public int EndPeriod { get; set; } }这里的关键设计点是排课冲突检测依赖 Schedule 表上的 DayOfWeek StartPeriod EndPeriod 三个字段。教室冲突看 ClassroomId 是否相同且时间段重叠教师冲突看 Course.TeacherId 是否相同且时间段重叠。把这三个字段单独拎出来而不是塞一个时间字符串就是为了让冲突检测能用纯数值比较完成不用解析字符串。3.2 DbContext 与迁移public class EduContext : DbContext { public EduContext(DbContextOptionsEduContext options) : base(options) { } public DbSetStudent Students { get; set; } public DbSetTeacher Teachers { get; set; } public DbSetCourse Courses { get; set; } public DbSetSchedule Schedules { get; set; } public DbSetEnrollment Enrollments { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 学号唯一索引防止重复录入 modelBuilder.EntityStudent() .HasIndex(s s.StudentNo) .IsUnique(); // 选课记录一个学生同一门课只能选一次 modelBuilder.EntityEnrollment() .HasIndex(e new { e.StudentId, e.CourseId }) .IsUnique(); } }OnModelCreating里加唯一索引是血泪经验。教务系统最常见的数据事故就是同一个学生被录了两次或同一门课选了两遍靠业务代码判断容易漏直接在数据库层加约束最省心。迁移用Add-Migration Init和Update-Database两条命令就能建库改字段时再生成新迁移版本可追溯。提示Code First 开发阶段可以开Database.EnsureCreated()快速起库但一旦进入需要保留数据的阶段必须切到迁移模式否则改一次模型数据就没了。4. 核心模块实现排课冲突检测、选课与统计图4.1 排课冲突检测一个方法挡住三类冲突排课是教务系统里逻辑最重的一块。新增或修改一条排课记录时要同时检查三种冲突教室被占、教师时间撞车、班级时间撞车。把它们写进一个 Service 方法public class ScheduleService { private readonly EduContext _ctx; public ScheduleService(EduContext ctx) _ctx ctx; // 返回冲突描述null 表示无冲突 public string CheckConflict(Schedule newSchedule) { // 时间段重叠判断新排课的起止节次与已有记录有交集 var overlap _ctx.Schedules.Where(s s.Id ! newSchedule.Id s.DayOfWeek newSchedule.DayOfWeek s.StartPeriod newSchedule.EndPeriod s.EndPeriod newSchedule.StartPeriod); // 1. 教室冲突 if (overlap.Any(s s.ClassroomId newSchedule.ClassroomId)) return 该教室在此时间段已被占用; // 2. 教师冲突通过 Course 关联到 TeacherId var teacherId _ctx.Courses .Where(c c.Id newSchedule.CourseId) .Select(c c.TeacherId).First(); if (overlap.Any(s s.Course.TeacherId teacherId)) return 该教师在此时间段已有课程; // 3. 班级冲突课程所属班级 var classId _ctx.Courses .Where(c c.Id newSchedule.CourseId) .Select(c c.ClassId).FirstOrDefault(); if (classId ! 0 overlap.Any(s s.Course.ClassId classId)) return 该班级在此时间段已有课程; return null; } }时间段重叠的判断条件是s.StartPeriod newSchedule.EndPeriod s.EndPeriod newSchedule.StartPeriod这是区间相交的标准写法比逐个节次循环高效得多。三类冲突共用同一个overlap查询结果避免重复查库。返回字符串而不是抛异常是为了让 Controller 能直接把提示显示到页面上用户体验更顺。4.2 选课模块容量控制与并发选课要防的是超额。一门课设了容量上限多个学生同时点选课时如果先查人数再插入中间有时间窗口可能超选。稳妥的做法是在插入前用事务 重新计数public bool Enroll(int studentId, int courseId) { using var tx _ctx.Database.BeginTransaction(); var course _ctx.Courses.Find(courseId); var current _ctx.Enrollments.Count(e e.CourseId courseId); if (current course.Capacity) return false; // 已满 _ctx.Enrollments.Add(new Enrollment { StudentId studentId, CourseId courseId, EnrollTime DateTime.Now }); _ctx.SaveChanges(); tx.Commit(); return true; }BeginTransaction保证计数和插入在同一个事务里配合数据库的唯一索引兜底基本能挡住重复选课和超额。课程容量Capacity字段要在 Course 实体里预留很多同学做课设时忘了这个字段到选课环节才发现没法限制。4.3 统计图后端聚合、前端渲染统计图这块后端只做数据聚合返回 JSON前端用 ECharts 画。比如各分数段人数分布[HttpGet] public IActionResult ScoreDistribution(int courseId) { var data _ctx.Enrollments .Where(e e.CourseId courseId e.Score ! null) .GroupBy(e e.Score 90 ? 优秀 : e.Score 80 ? 良好 : e.Score 60 ? 及格 : 不及格) .Select(g new { Segment g.Key, Count g.Count() }) .ToList(); return Json(data); }前端拿到[{Segment:优秀, Count:12}, ...]后映射成 ECharts 的series.data即可。把分组逻辑放在数据库端LINQ 会翻译成 SQL 的 CASE WHEN比全查出来在内存里分组要快尤其是成绩记录上千条时差别明显。注意ECharts 的容器 div 必须显式设置高度否则图表不显示。这是新手最常踩的坑页面看着什么都没画出来其实只是高度为 0。5. 避坑与常见问题排查这几处最容易翻车5.1 EF 延迟加载导致的 N1 查询现象课表页面加载特别慢日志里发现同一张表被查了几十次。原因在循环里访问导航属性每次访问触发一次数据库查询典型的 N1。解决查询时用.Include()一次性把关联数据带出来比如_ctx.Schedules.Include(s s.Course).Include(s s.Classroom)。或者在 DbContext 配置里关掉延迟加载强制显式加载问题会暴露得更早。5.2 排课冲突检测漏掉自己现象修改一条已有排课时系统提示该教室已被占用但占用的就是它自己。原因冲突查询没排除当前记录。解决查询条件里加s.Id ! newSchedule.Id把自身排除掉。这个坑在新增时不会出现只有编辑时才暴露测试时一定要覆盖编辑场景。5.3 中文乱码与编码问题现象学生姓名、课程名存进数据库变成问号。原因数据库字段排序规则不是 UTF-8或连接字符串没指定字符集。解决建库时字段用nvarchar而不是varchar连接字符串确认字符集配置。EF 默认映射 string 到 nvarchar但如果手动改过迁移脚本容易改错。5.4 权限只做了登录没做授权现象学生登录后手动改 URL 能进管理员页面。原因只在登录时判断了角色没有在每个 Action 上做授权。解决用[Authorize(Roles Admin)]特性标注控制器或 Action配合角色声明。别指望前端隐藏菜单就能挡住URL 是公开的。5.5 统计图数据为空时页面报错现象某门课还没录成绩点统计图页面直接白屏。原因前端拿到空数组后没做判空ECharts 配置里访问了不存在的字段。解决后端返回空数组而不是 null前端渲染前判断if (!data.length)显示暂无数据占位。空数据是常态不是异常。6. 验收与答辩怎么证明这套系统真的能用验收不是把页面点一遍就完事得有一套能复现的检查流程。我一般会按下面这张表逐项过验收项操作步骤通过标准登录与权限用四种角色分别登录手动改 URL 访问越权页面越权请求被拦截跳转登录或提示无权限基础数据 CRUD新增/修改/删除学生、课程各一条数据正确落库列表实时刷新排课冲突故意排一条与已有记录教室、教师、班级冲突的课三种冲突都能准确提示且不误报自身选课容量把某课容量设为 2用 3 个账号选课第 3 次提示已满数据库无超额记录成绩统计录入若干成绩后打开统计图分数段人数与手工核对一致空数据不报错并发选课两个浏览器同时选最后一门课名额只有一条成功无重复记录答辩时最容易被问到的三个问题提前准备好答案为什么用 EF 而不是手写 SQL答关联查询多ORM 降低维护成本复杂统计仍可回退到原生 SQL排课冲突怎么保证不漏答三类冲突统一走一个 Service 方法配合数据库唯一约束兜底系统能支撑多大数据量答给出分页、索引、Include 优化的具体做法而不是空说性能好。演示环节有个技巧别按顺序点按故事线点。比如新学期开学教务先建课程、再排课、系统检测出冲突、调整后学生开始选课、期末录成绩、最后看统计图一条线走下来评委能直观感受到系统是连贯的而不是一堆孤立页面。从那以后我每次交这类系统都会在答辩前一天自己按验收表完整走一遍把每个通过标准当成一道必答题而不是等评委现场帮我发现。希望这套结构和清单能帮你把课设或方案稳稳落地。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站