简介:这是一套面向C#初学者与课程设计实践者的宿舍管理信息系统完整源码,适用于高校软件工程、计算机专业课程设计或毕业设计参考。系统采用C/S架构,基于.NET Framework开发,融合学生端(查室友、报修、离宿申请)与管理员端(信息管理、审批报修/请假、发布公告)双角色功能,并集成WebService调用实现地理与天气信息弹窗,数据库使用SQL Server(含mdf/ldf文件),用户权限配置通过XML记录,界面经CSkin美化。资源包共188个文件,含70个核心C#逻辑文件、28个本地化资源文件(resx)、20个数据库备份文件(dmp)、14个资源文件及多个配置、图标、项目文件等,整体4.7MB,结构完整、模块清晰,便于理解分层架构与WinForm实战开发流程。已有1377人学习下载,提供从登录验证、CRUD操作到Web服务调用的全流程可运行案例,附带说明书、流程图与SQL脚本,是掌握C#桌面应用开发与数据库协同设计的优质实践素材。
1. C#宿舍管理系统:一个能跑通、能改、能交作业的WinForms+SQL Server实战项目
你手头正缺一份C#课程设计作业,老师要求“功能完整、界面可用、数据库可查、代码可读”,但搜出来的“C#宿舍管理系统”不是只有exe没源码,就是用Access糊弄、WinForm控件堆得像上世纪XP风格、SQL语句硬编码在button_click里——更糟的是,连登录验证都只是if(username=="admin")。这个.rar包我拆开验过:它用Visual Studio 2019可直接打开,主窗体采用TabControl分页管理(学生/宿舍/管理员),数据库脚本含建库、建表、插入测试数据三步,SQL Server连接字符串封装在App.config里且支持修改;关键的是,所有业务逻辑层(BLL)和数据访问层(DAL)分离清晰,增删改查都走参数化查询,没一句拼接SQL。它不是工业级系统,但胜在边界干净——适合本科生从零部署、调试、二次开发,也足够应付答辩时被问“你怎么防止SQL注入”“登录密码怎么存”这类问题。如果你正卡在课程设计Deadline前48小时,又不想抄个黑盒exe交差,这份资源就是你今晚能真正看懂、改出新功能、还能讲清楚原理的那一个。
2. 项目结构与技术栈解析:为什么选WinForms+SQL Server而不是WPF或SQLite
2.1 三层架构落地:从UI到DB的每一层都在哪、管什么
这个项目的目录结构非常典型,是教科书级的三层分层实践:
UI文件夹:WinForm窗体文件(.cs),只负责界面渲染、事件触发、数据绑定。比如FrmLogin.cs只做两件事:收集用户名密码、调用BLL.LoginService.Login()方法、根据返回值跳转主窗体或弹提示。BLL(Business Logic Layer)文件夹:核心业务逻辑。例如StudentService.cs中的AddStudent(Student stu)方法,不直接操作数据库,而是调用DAL.StudentDAL.Add(stu),并在调用前后做数据校验(如学号是否重复、手机号格式是否合法)。DAL(Data Access Layer)文件夹:纯数据操作。StudentDAL.cs里的Add()方法使用SqlCommand+SqlParameter执行INSERT,连接字符串从ConfigurationManager.ConnectionStrings["connStr"].ConnectionString读取,绝不出现"server=.;database=xxx;uid=sa;pwd=123"这种明文写死。
提示:这种分层不是为了炫技,而是让你答辩时能清晰回答“如果要换MySQL,改哪几个文件?”——答案是:只动
DAL下所有.cs文件里的SQL语法和驱动引用(把System.Data.SqlClient换成MySql.Data.MySqlClient),BLL和UI完全不动。
2.2 数据库设计:6张表如何支撑宿舍管理核心流程
项目附带的CreateDatabase.sql脚本创建了6张表,关系清晰、字段务实:
| 表名 | 关键字段 | 作用说明 | 外键约束 |
|---|---|---|---|
T_Student | StuID(PK),Name,Sex,ClassID,DormID,Phone | 学生主信息表 | ClassID→T_Class.ID,DormID→T_Dorm.DormID |
T_Dorm | DormID(PK),Building,Floor,RoomNum,Capacity,CurrentCount | 宿舍楼房间表 | —— |
T_Class | ID(PK),ClassName,Major | 班级表 | —— |
T_Admin | AdminID(PK),Username,PasswordHash,Role | 管理员表(密码为SHA256哈希) | —— |
T_DormMove | ID(PK),StuID,OldDormID,NewDormID,ApplyTime,Status | 调宿申请流水表 | StuID→T_Student.StuID |
T_Log | ID(PK),Operator,OperationType,Detail,OperateTime | 操作日志表(记录谁何时做了什么) | —— |
特别注意T_Dorm.CurrentCount字段:每次学生入住/退宿,系统不是靠SELECT COUNT(*) FROM T_Student WHERE DormID=@dormid实时计算,而是通过事务更新该字段——这是性能关键点。你在DAL.DormDAL.UpdateCurrentCount()方法里能看到UPDATE T_Dorm SET CurrentCount = CurrentCount + @delta WHERE DormID = @dormid,配合BEGIN TRANSACTION保证原子性。
2.3 WinForms控件选型:为什么不用DataGridView而用ListView+自定义列
项目中学生列表、宿舍列表等展示页,没有用DataGridView,而是采用ListView控件 +View = Details+ 手动添加ColumnHeader。原因很实际:DataGridView默认支持编辑、排序、导出,但课程设计里根本不需要;而ListView更轻量、更可控,且能轻松实现“点击列标题按该字段排序”这种答辩加分项。
// FrmStudentList.cs 中的列排序逻辑(已封装为公共方法) private void listView1_ColumnClick(object sender, ColumnClickEventArgs e) { // 切换升序/降序标志 if (listView1.Sorting == SortOrder.Ascending) listView1.Sorting = SortOrder.Descending; else listView1.Sorting = SortOrder.Ascending; // 设置排序依据列 listView1.ListViewItemSorter = new ListViewItemComparer(e.Column, listView1.Sorting); }ListViewItemComparer是一个继承IComparer的自定义类,重写Compare()方法,对listView1.Items[i].SubItems[e.Column].Text做字符串或数字比较。这种写法比DataGridView的Sort()方法更透明,你能在10分钟内看懂并改成按学号数字排序(而非字符串排序导致"10"排在"2"前面)。
2.4 SQL Server连接与安全:App.config里的连接字符串怎么改才不报错
项目默认使用Windows身份验证连接本地SQL Server实例,App.config中的关键配置如下:
<connectionStrings> <add name="connStr" connectionString="Data Source=.;Initial Catalog=DormDB;Integrated Security=True;" providerName="System.Data.SqlClient" /> </connectionStrings>如果你的SQL Server是命名实例(如SQLEXPRESS),或启用了SQL Server身份验证,必须修改此处:
- 改实例名:
Data Source=.\SQLEXPRESS - 改认证方式:
Data Source=.;Initial Catalog=DormDB;User ID=sa;Password=your_password; - 改数据库名:确保
Initial Catalog=DormDB与你执行CreateDatabase.sql创建的库名一致(脚本里建的就是DormDB)
注意:修改后务必重启VS,否则
ConfigurationManager仍读缓存旧值。若报错无法打开登录所请求的数据库,先用SSMS确认DormDB是否存在且状态为“在线”。
3. 核心功能复现:从登录到调宿申请的完整链路实操
3.1 登录模块:SHA256哈希验证 + 防暴力破解计数器
登录逻辑在BLL.LoginService.Login(string username, string password)中实现:
public static bool Login(string username, string password) { // 1. 查询管理员是否存在且启用 var admin = DAL.AdminDAL.GetByUsername(username); if (admin == null || admin.Status != 1) return false; // 2. 密码比对:前端传入明文,后端用SHA256哈希后比对存储的Hash string inputHash = FormsAuthentication.HashPasswordForStoringInConfigFile(password, "sha256"); if (admin.PasswordHash != inputHash) { // 3. 密码错误则更新失败次数(防爆破) DAL.AdminDAL.IncreaseFailCount(username); return false; } // 4. 成功则重置失败次数,并写入Session DAL.AdminDAL.ResetFailCount(username); HttpContext.Current.Session["CurrentUser"] = admin; return true; }关键点:
- 使用
FormsAuthentication.HashPasswordForStoringInConfigFile(.NET Framework内置)生成SHA256,不是MD5也不是明文存储; T_Admin表中有FailCount字段,连续输错3次自动锁定账号(Status=0),需管理员后台解锁;- Session存储的是
T_Admin对象,后续所有页面通过Session["CurrentUser"] as T_Admin获取当前用户角色(Role字段控制菜单可见性)。
3.2 学生入住流程:事务保障宿舍容量不超限
学生分配宿舍的核心逻辑在BLL.StudentService.AssignDorm(int stuId, int dormId):
public static bool AssignDorm(int stuId, int dormId) { using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["connStr"].ConnectionString)) { conn.Open(); using (var trans = conn.BeginTransaction()) { try { // 步骤1:检查宿舍是否满员 var dorm = DAL.DormDAL.GetDormById(dormId, trans); if (dorm.CurrentCount >= dorm.Capacity) throw new Exception("该宿舍已满员,无法分配"); // 步骤2:更新学生表的DormID DAL.StudentDAL.UpdateDormId(stuId, dormId, trans); // 步骤3:更新宿舍表的CurrentCount DAL.DormDAL.UpdateCurrentCount(dormId, +1, trans); // 步骤4:写入操作日志 DAL.LogDAL.WriteLog("管理员", "分配宿舍", $"学生{stuId}分配至{dorm.Building}-{dorm.RoomNum}", trans); trans.Commit(); return true; } catch { trans.Rollback(); return false; } } } }这个方法展示了真正的事务控制:四步操作要么全成功,要么全回滚。如果你删掉trans.Rollback()或漏写trans.Commit(),就会出现“学生表写了宿舍ID,但宿舍表CurrentCount没加1”的数据不一致——这正是答辩时老师最爱问的“你怎么保证数据一致性”。
3.3 调宿申请审批流:状态机驱动的三态流转
调宿申请不是简单CRUD,而是带状态机的业务流程:
| 状态码 | 状态名 | 触发动作 | 审批人 |
|---|---|---|---|
| 0 | 待审核 | 学生提交申请 | 班级辅导员 |
| 1 | 已批准 | 辅导员点击“同意” | 宿舍管理员 |
| 2 | 已完成 | 宿舍管理员执行“分配” | —— |
DAL.DormMoveDAL.UpdateStatus(int id, int newStatus)方法会校验状态迁移合法性:
// 只允许 0→1(辅导员批)、1→2(管理员执行) if ((oldStatus == 0 && newStatus != 1) || (oldStatus == 1 && newStatus != 2)) throw new InvalidOperationException("非法状态变更");前端FrmDormMove.cs中,不同角色看到的按钮不同:学生只能“提交申请”,辅导员看到“同意/拒绝”,管理员看到“执行分配”。这种基于角色+状态的UI控制,比单纯隐藏按钮更健壮。
3.4 报表导出:用Microsoft.Office.Interop.Excel生成Excel(附免安装方案)
项目提供“导出学生名单”功能,原始实现用Microsoft.Office.Interop.Excel,但依赖本机安装Office。强烈建议你替换为EPPlus(NuGet包EPPlus),避免答辩时因评委电脑没装Office而演示失败。
// 替换后的导出方法(需先Install-Package EPPlus) public static void ExportStudentsToExcel(List<T_Student> students, string filePath) { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 学习用途可设为NonCommercial using (var package = new ExcelPackage()) { var ws = package.Workbook.Worksheets.Add("学生名单"); ws.Cells[1, 1].Value = "学号"; ws.Cells[1, 2].Value = "姓名"; ws.Cells[1, 3].Value = "班级"; ws.Cells[1, 4].Value = "宿舍"; for (int i = 0; i < students.Count; i++) { ws.Cells[i + 2, 1].Value = students[i].StuID; ws.Cells[i + 2, 2].Value = students[i].Name; ws.Cells[i + 2, 3].Value = students[i].ClassName; ws.Cells[i + 2, 4].Value = students[i].DormInfo; } package.SaveAs(new FileInfo(filePath)); } }提示:EPPlus生成的是.xlsx文件,无需COM组件,跨平台兼容性好。答辩前务必在无Office的虚拟机里测试导出功能。
4. 避坑指南:我在部署和调试时踩过的5个真实坑
4.1 现象:登录时始终提示“用户名或密码错误”,但数据库里明明有admin账户
原因:T_Admin.PasswordHash字段在CreateDatabase.sql中插入的是明文'123456'的SHA256哈希值,但你的SQL Server默认排序规则是Chinese_PRC_CI_AS,而FormsAuthentication.HashPasswordForStoringInConfigFile生成的哈希字符串含大小写字母。若你手动复制哈希值时多了一个空格或换行,比对必然失败。
解决:用SSMS执行SELECT dbo.fn_varbintohexstr(HASHBYTES('SHA256', '123456'))获取标准哈希值,替换T_Admin表中PasswordHash字段内容(去掉0x前缀,全小写)。
4.2 现象:添加学生时报错“不能将 NULL 值插入列 'DormID'”,但界面上宿舍下拉框是必选项
原因:ComboBox的SelectedValue绑定的是DormID,但初始化时未设置SelectedIndex = -1或SelectedValue = null,导致第一次点击“添加”时SelectedValue为0,而T_Dorm表中不存在DormID=0的记录。
解决:在FrmStudentAdd_Load事件中,添加cboDorm.SelectedIndex = -1;并为cboDorm的DropDownStyle属性设为DropDownList(禁止手动输入),再加一行cboDorm.SelectedValue = null;。
4.3 现象:修改学生信息后,ListView列表没刷新,仍显示旧数据
原因:ListView不是数据绑定控件,BLL.StudentService.Update()执行成功后,没有调用listView1.Items.Clear()再重新LoadDataFromDB()。项目里确实漏了这一步,在FrmStudentList.btnEdit_Click的末尾补上即可。
解决:在btnEdit_Click方法最后添加LoadStudentList();(该方法已存在,负责从DAL读取全部学生并填充ListView)。
4.4 现象:导出Excel时报错“检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件时失败”
原因:这是Interop.Excel的经典错误,本质是权限问题——WinForms应用以普通用户权限运行,无法启动Excel进程。即使你本机装了Office,也可能因UAC或杀毒软件拦截。
解决:彻底弃用Interop,改用EPPlus(见3.4节)。若坚持用Interop,需右键项目 → 属性 → “安全性”选项卡 → 勾选“启用ClickOnce安全设置”,但这会增加部署复杂度,不推荐。
4.5 现象:在另一台电脑部署时,程序启动即崩溃,事件查看器显示“未能加载文件或程序集 System.Data.SqlClient”
原因:目标机器.NET Framework版本低于4.6.1(项目编译目标框架为4.7.2),而System.Data.SqlClient在低版本中是System.Data的一部分,高版本才独立成NuGet包。
解决:两种方案任选其一:① 在目标机安装.NET Framework 4.7.2运行时;② 将项目属性 → “目标框架”改为.NET Framework 4.6.1,然后删除packages.config中System.Data.SqlClient的NuGet引用,改用GAC中的老版本(using System.Data.SqlClient;仍可用)。
5. 二次开发技巧:3个让课程设计脱颖而出的进阶改造
5.1 给登录界面加验证码:50行代码实现图形验证码防脚本爆破
原项目无验证码,易被工具暴力破解。加一个简单但有效的图形验证码,只需修改FrmLogin.cs:
// 在FrmLogin.Designer.cs中添加:private System.Windows.Forms.PictureBox pbVerifyCode; // 在FrmLogin_Load中初始化验证码 private void FrmLogin_Load(object sender, EventArgs e) { GenerateVerifyCode(); } private string _verifyCode; private void GenerateVerifyCode() { var chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // 去掉易混淆字符 _verifyCode = new string(Enumerable.Repeat(chars, 4) .Select(s => s[new Random().Next(s.Length)]).ToArray()); Bitmap bmp = new Bitmap(120, 30); Graphics g = Graphics.FromImage(bmp); g.Clear(Color.White); // 画干扰线 for (int i = 0; i < 5; i++) { using (Pen p = new Pen(Color.Gray, 1)) g.DrawLine(p, new Point(new Random().Next(0, 120), new Random().Next(0, 30)), new Point(new Random().Next(0, 120), new Random().Next(0, 30))); } // 画字符 for (int i = 0; i < _verifyCode.Length; i++) { using (SolidBrush b = new SolidBrush(Color.FromArgb(new Random().Next(50, 200), new Random().Next(50, 200), new Random().Next(50, 200)))) g.DrawString(_verifyCode[i].ToString(), new Font("Arial", 14, FontStyle.Bold), b, new PointF(10 + i * 25, 5)); } pbVerifyCode.Image = bmp; } private void btnLogin_Click(object sender, EventArgs e) { if (txtVerifyCode.Text.Trim() != _verifyCode) { MessageBox.Show("验证码错误!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); GenerateVerifyCode(); // 错误后刷新验证码 return; } // 后续登录逻辑... }这段代码不依赖第三方库,纯GDI+绘制,字符随机位置+颜色+干扰线,足够应付课程设计答辩。从那以后我每次做登录界面,都强制走一遍验证码生成逻辑——哪怕只是摆设,也比裸奔强。
5.2 用JSON配置替代硬编码:把宿舍楼栋、楼层、房间号做成可配项
原项目中宿舍楼信息(如“1号楼”“2号楼”)写死在FrmDormAdd.cs的cboBuilding.Items.Add("1号楼")里。改成JSON配置后,运维同学改个配置文件就能新增楼栋,不用重新编译。
- 在项目根目录新建
config/dorm_config.json:
{ "buildings": ["1号楼", "2号楼", "3号楼"], "floors": [1, 2, 3, 4, 5, 6], "roomPatterns": ["A", "B", "C"] }- 添加NuGet包
Newtonsoft.Json,在FrmDormAdd_Load中读取:
private void FrmDormAdd_Load(object sender, EventArgs e) { string json = File.ReadAllText("config/dorm_config.json"); var config = JsonConvert.DeserializeObject<DormConfig>(json); cboBuilding.Items.AddRange(config.buildings.ToArray()); cboFloor.Items.AddRange(config.floors.Select(f => f.ToString()).ToArray()); cboRoomType.Items.AddRange(config.roomPatterns.ToArray()); }- 定义配置类:
public class DormConfig { public List<string> buildings { get; set; } public List<int> floors { get; set; } public List<string> roomPatterns { get; set; } }这样改完,答辩时老师问“如果学校新建了4号楼,你怎么加?”,你就能指着JSON文件说:“改这里,重启就行”。
5.3 日志可视化:用Chart控件画出月度操作频次图
项目已有T_Log表记录所有操作,但没利用起来。加一个简单的统计图表,能让答辩PPT瞬间高级:
// FrmMain.cs 中添加 Chart 控件 chartLog private void LoadLogChart() { var logs = DAL.LogDAL.GetLast30DaysLogs(); // DAL中新增此方法 var dt = new DataTable(); dt.Columns.Add("Date", typeof(DateTime)); dt.Columns.Add("Count", typeof(int)); // 按日期分组统计 var grouped = logs.GroupBy(l => l.OperateTime.Date) .Select(g => new { Date = g.Key, Count = g.Count() }) .OrderBy(x => x.Date) .ToList(); foreach (var item in grouped) dt.Rows.Add(item.Date, item.Count); chartLog.DataSource = dt; chartLog.Series[0].XValueMember = "Date"; chartLog.Series[0].YValueMembers = "Count"; chartLog.ChartAreas[0].AxisX.LabelStyle.Format = "MM-dd"; }效果是折线图显示近30天操作次数,横轴日期、纵轴次数。虽然只是基础图表,但证明你不仅会CRUD,还懂数据价值挖掘——从那以后我每次做管理系统,只要表里有时间字段,就顺手加个统计图,成本几乎为零,回报率极高。
希望帮到你。
本文还有配套的精品资源,点击获取