C#酒店客房管理系统实战:从EF Core到WinForm闭环开发
发布时间:2026/10/1 18:50:12来源:尧图网络
简介本资源是一份面向高校计算机专业本科生的C#课程设计实践项目聚焦酒店客房管理这一典型业务场景帮助学习者掌握Windows桌面应用开发、SQL Server数据库操作与软件工程全流程实践。压缩包共70个文件含22个C#源码文件实现登录、房间查询、订房、退房等核心模块、8个.resx本地化资源、4个.config配置文件、1个.mdf1个.ldf数据库文件、1份.doc格式课程设计报告以及.sln解决方案和.csproj项目文件总大小454KB结构完整便于直接编译运行。已有296人学习下载资源提供可运行的完整系统源码、配套数据库及规范化的课程设计文档覆盖需求分析、界面设计、ADO.NET数据交互、事务处理与测试说明特别适合课程设计参考、毕业设计选题借鉴或C#SQL Server综合实训演练。1. 这不是又一个“学生作业模板”而是用 C# 把酒店客房管理真正跑通的最小闭环从数据库建模到 WinForm 界面交互再到可验证的业务逻辑落地你下载过几十个标着“课程设计-基于C#的酒店客房管理系统-源码数据库报告.zip”的压缩包解压后发现数据库只有三张表、WinForm 窗体堆满未绑定控件、登录页能进但一查房就报NullReferenceException、报告里写着“系统采用三层架构”——可代码里连DAL文件夹都没有。这不是教学疏漏是典型“交付即断联”的课程设计黑匣子。本文讲的是把这类压缩包从“能编译”推进到“能跑通真实业务流”的实操路径用 SQL Server LocalDB 搭起可复现的数据库底座用 Entity Framework Core 6 做轻量 ORM 映射不硬套 DDD用 WinForm 控件组合实现“入住→查房→退房→报表生成”四步闭环最后用一份带截图、含测试用例、标注了每个按钮触发逻辑的 Word 报告收尾。适合大三下学期刚学完 ADO.NET 和 WinForm 的同学——不需要懂 WPF 或 ASP.NET只要会写button1_Click和SqlConnection就能照着把这套系统变成自己简历里能现场演示的项目。2. 用 SQL Server LocalDB EF Core 6 搭建可复现的数据库底座避开“附加数据库失败”和“连接字符串玄学”课程设计压缩包里常见的.mdf文件90% 无法直接附加到你的 SQL Server Management Studio ——不是权限问题而是版本错配SQL Server 2019 生成的.mdf在 2016 上附加失败、路径硬编码C:\Users\Admin\Desktop\hotel.mdf、或缺少日志文件.ldf丢失。我们绕过这些坑用 LocalDB Code-First 方式重建数据库确保每台电脑都能一键启动。2.1 创建数据库上下文与实体类用 EF Core 6 定义核心业务实体先创建HotelDbContext.cs定义三个核心实体Room客房、Guest住客、Reservation预订。注意字段命名必须严格对应业务逻辑比如RoomStatus用枚举而非字符串避免后期查询歧义// Models/HotelDbContext.cs using Microsoft.EntityFrameworkCore; public class HotelDbContext : DbContext { public DbSetRoom Rooms { get; set; } public DbSetGuest Guests { get; set; } public DbSetReservation Reservations { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) options.UseSqlServer(Data Source(localdb)\MSSQLLocalDB;Initial CatalogHotelDB;Integrated Securitytrue;); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityRoom() .HasKey(r r.RoomId); modelBuilder.EntityGuest() .HasKey(g g.GuestId); modelBuilder.EntityReservation() .HasKey(r r.ReservationId); // 预订关联客房和住客 modelBuilder.EntityReservation() .HasOne(r r.Room) .WithMany() .HasForeignKey(r r.RoomId); modelBuilder.EntityReservation() .HasOne(r r.Guest) .WithMany() .HasForeignKey(r r.GuestId); } } public enum RoomStatus { Available, Occupied, Maintenance, Dirty } public enum PaymentMethod { Cash, CreditCard, WeChatPay, Alipay } public class Room { public int RoomId { get; set; } public string RoomNumber { get; set; } string.Empty; public string RoomType { get; set; } Standard; // Standard, Deluxe, Suite public decimal PricePerNight { get; set; } 300.0m; public RoomStatus Status { get; set; } RoomStatus.Available; public string Description { get; set; } string.Empty; } public class Guest { public int GuestId { get; set; } public string Name { get; set; } string.Empty; public string IDCardNumber { get; set; } string.Empty; // 身份证号唯一索引 public string PhoneNumber { get; set; } string.Empty; public string Email { get; set; } string.Empty; } public class Reservation { public int ReservationId { get; set; } public int RoomId { get; set; } public int GuestId { get; set; } public DateTime CheckInDate { get; set; } public DateTime? CheckOutDate { get; set; } // 可为空表示尚未退房 public int StayNights { get; set; } // 自动计算CheckOutDate - CheckInDate public decimal TotalAmount { get; set; } // PricePerNight * StayNights public PaymentMethod PaymentMethod { get; set; } PaymentMethod.Cash; public bool IsPaid { get; set; } false; public DateTime CreatedAt { get; set; } DateTime.Now; // 导航属性非数据库字段 public virtual Room Room { get; set; } null!; public virtual Guest Guest { get; set; } null!; }提示OnConfiguring中的连接字符串使用(localdb)\MSSQLLocalDB这是 Visual Studio 自带的轻量数据库引擎无需安装完整 SQL Server。Initial CatalogHotelDB表示数据库名EF Core 会在首次运行时自动创建该数据库及所有表结构。2.2 初始化数据库并填充基础数据用 SeedData 类注入测试用房型与状态在Program.cs或Startup.cs若用 .NET 6中调用Database.EnsureCreated()并执行种子数据避免手动建表// Program.cs using (var context new HotelDbContext()) { context.Database.EnsureCreated(); // 第一次运行时创建数据库和表 if (!context.Rooms.Any()) { var rooms new ListRoom { new Room { RoomNumber 101, RoomType Standard, PricePerNight 280.0m, Status RoomStatus.Available }, new Room { RoomNumber 102, RoomType Deluxe, PricePerNight 420.0m, Status RoomStatus.Available }, new Room { RoomNumber 201, RoomType Suite, PricePerNight 680.0m, Status RoomStatus.Available }, new Room { RoomNumber 202, RoomType Standard, PricePerNight 280.0m, Status RoomStatus.Maintenance } }; context.Rooms.AddRange(rooms); context.SaveChanges(); } if (!context.Guests.Any()) { var guests new ListGuest { new Guest { Name 张三, IDCardNumber 11010119900307271X, PhoneNumber 13800138000 }, new Guest { Name 李四, IDCardNumber 110101199205123456, PhoneNumber 13900139000 } }; context.Guests.AddRange(guests); context.SaveChanges(); } }关键参数说明EnsureCreated()是 EF Core 的轻量初始化方式适合课程设计场景生产环境应改用Migrations。IDCardNumber字段设为唯一索引需在OnModelCreating中追加modelBuilder.EntityGuest().HasIndex(g g.IDCardNumber).IsUnique();防止重复登记。StayNights和TotalAmount不设为数据库字段而是在业务层计算见第 4 章避免数据冗余和一致性风险。2.3 验证数据库是否真正可用用 LINQ 查询替代手写 SQL快速定位连接问题不要依赖 SSMS 查看表结构直接在窗体代码中写一段验证逻辑放在主窗体Load事件里private void MainForm_Load(object sender, EventArgs e) { try { using (var ctx new HotelDbContext()) { var roomCount ctx.Rooms.Count(); // 触发实际查询 var guestCount ctx.Guests.Count(); MessageBox.Show($数据库初始化成功{roomCount} 间客房{guestCount} 位住客); } } catch (Exception ex) { MessageBox.Show($数据库连接失败{ex.InnerException?.Message ?? ex.Message}); // 常见错误LocalDB 服务未启动 → 运行 cmd 输入 sqllocaldb start MSSQLLocalDB // 或连接字符串格式错误 → 检查括号和分号 } }这段代码强制执行一次查询比“看到.mdf文件存在”更可靠。如果弹出错误90% 是 LocalDB 未启动或连接字符串拼写错误如MSSQLLocalDB写成MSSQLLocalDB少了个L。3. WinForm 界面与业务逻辑绑定用 BindingSource 实现“查房→选房→入住”三步联动课程设计里最常翻车的环节DataGridView 绑定后数据不刷新、ComboBox 选中项无法同步到实体、双击行不能跳转编辑窗体。根源在于没用BindingSource做中间层而是直接dataGridView1.DataSource list——这会导致 UI 与数据脱节。3.1 构建主界面布局用 TableLayoutPanel 管理控件位置避免绝对定位失灵不要用Location.X/Y手动拖控件用TableLayoutPanel划分区域行列控件00-2标题 Label字体加粗居中10“客房列表” Label11-2DataGridViewDockFill20“住客信息” Label21TextBox姓名22TextBox身份证号30“操作区” Label31Button入住32Button退房这样适配不同屏幕分辨率且代码中可通过tableLayoutPanel1.GetControlFromPosition(1,1)动态获取控件。3.2 用 BindingSource 绑定客房数据实现选中行自动更新状态栏在MainForm.cs中声明成员变量private BindingSource roomBindingSource new BindingSource(); private HotelDbContext dbContext new HotelDbContext(); private void LoadRooms() { var rooms dbContext.Rooms.ToList(); roomBindingSource.DataSource rooms; dataGridView1.DataSource roomBindingSource; // 设置列标题和宽度 dataGridView1.Columns[RoomId].Visible false; dataGridView1.Columns[RoomNumber].HeaderText 房号; dataGridView1.Columns[RoomType].HeaderText 房型; dataGridView1.Columns[PricePerNight].HeaderText 单价元/晚; dataGridView1.Columns[Status].HeaderText 状态; dataGridView1.Columns[Description].HeaderText 描述; // 状态列显示中文枚举值转字符串 dataGridView1.Columns[Status].DefaultCellStyle.Format G; dataGridView1.Columns[Status].ValueType typeof(RoomStatus); }为什么必须用 BindingSource当用户点击某行时roomBindingSource.Current自动指向该Room对象修改roomBindingSource.Current属性如Status RoomStatus.Occupied再调用dbContext.SaveChanges()即可持久化若直接绑定ListRoom修改对象后需手动dataGridView1.Refresh()且无法保证数据一致性。3.3 实现“入住”按钮逻辑校验 关联 状态更新三步原子操作点击“入住”按钮时必须同时完成① 检查选中客房是否空闲② 检查住客身份证号是否已存在③ 创建预订记录并更新客房状态。任一环节失败整个操作回滚private void btnCheckIn_Click(object sender, EventArgs e) { if (roomBindingSource.Current null) { MessageBox.Show(请先选择一间客房); return; } var selectedRoom (Room)roomBindingSource.Current; if (selectedRoom.Status ! RoomStatus.Available) { MessageBox.Show($房间 {selectedRoom.RoomNumber} 当前状态为 {selectedRoom.Status}不可入住); return; } // 获取住客信息从界面上读取 string name txtGuestName.Text.Trim(); string idCard txtIDCard.Text.Trim(); if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(idCard)) { MessageBox.Show(请输入住客姓名和身份证号); return; } // 检查身份证号是否已存在 var existingGuest dbContext.Guests.FirstOrDefault(g g.IDCardNumber idCard); int guestId; if (existingGuest null) { // 新建住客 var newGuest new Guest { Name name, IDCardNumber idCard, PhoneNumber txtPhone.Text.Trim() }; dbContext.Guests.Add(newGuest); dbContext.SaveChanges(); guestId newGuest.GuestId; } else { guestId existingGuest.GuestId; } // 创建预订记录 var reservation new Reservation { RoomId selectedRoom.RoomId, GuestId guestId, CheckInDate DateTime.Today, StayNights 1, TotalAmount selectedRoom.PricePerNight, PaymentMethod PaymentMethod.Cash, IsPaid false }; dbContext.Reservations.Add(reservation); // 更新客房状态 selectedRoom.Status RoomStatus.Occupied; dbContext.SaveChanges(); MessageBox.Show($入住成功房间 {selectedRoom.RoomNumber} 已分配给 {name}); LoadRooms(); // 刷新列表 }关键细节dbContext.SaveChanges()在最后统一调用确保“新建住客 新建预订 更新客房状态”在一个事务内完成selectedRoom.Status RoomStatus.Occupied直接修改绑定对象BindingSource会自动同步到 DataGridView不在Reservation实体中存储RoomNumber或GuestName只存外键 ID —— 避免数据冗余和更新不一致。4. 避坑课程设计压缩包里高频翻车的 4 个致命问题与血泪修复方案课程设计压缩包最大的陷阱不是功能缺失而是表面能运行、实际业务逻辑断裂。以下是我在帮 17 届同学调试时总结的 4 个必踩坑每一条都附带现象、根因和可复制的修复命令。4.1 现象程序启动报错System.Data.SqlClient.SqlException: Cannot open database HotelDB requested by the login. The login failed.原因压缩包里的app.config连接字符串写的是Server.;DatabaseHotelDB;...但你的电脑没有名为HotelDB的数据库且 SQL Server 实例名不是.本地默认实例。更糟的是有些包直接把连接字符串硬编码在Form1.cs里连配置文件都没用。解决① 删除所有硬编码连接字符串统一用HotelDbContext.OnConfiguring()② 确保 LocalDB 已安装VS 2019 默认自带运行命令行sqllocaldb info # 若输出包含 MSSQLLocalDB则正常否则安装https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/sql-server-express-localdb sqllocaldb start MSSQLLocalDB4.2 现象DataGridView 显示数据但双击某行后弹出的编辑窗体里 TextBox 全是空的原因编辑窗体用new EditRoomForm(roomId)构造函数传参但在窗体Load事件里没查数据库而是直接txtRoomNumber.Text roomId.ToString()—— 这只是传了 ID没加载完整对象。解决编辑窗体构造函数改为接收Room对象并在Load中绑定public partial class EditRoomForm : Form { private Room _room; public EditRoomForm(Room room) { InitializeComponent(); _room room; } private void EditRoomForm_Load(object sender, EventArgs e) { txtRoomNumber.Text _room.RoomNumber; cmbRoomType.Text _room.RoomType; numPrice.Value (decimal)_room.PricePerNight; cmbStatus.Text _room.Status.ToString(); } }4.3 现象“退房”按钮点击后客房状态变成Available但数据库里Reservation.CheckOutDate仍是NULL原因业务逻辑写成room.Status RoomStatus.Available; dbContext.SaveChanges();漏掉了对Reservation记录的更新。课程设计包里常见“只改状态、不关订单”的半截逻辑。解决退房操作必须查出当前入住的预订记录var currentReservation dbContext.Reservations .FirstOrDefault(r r.RoomId selectedRoom.RoomId r.CheckOutDate null); if (currentReservation ! null) { currentReservation.CheckOutDate DateTime.Today; currentReservation.StayNights (DateTime.Today - currentReservation.CheckInDate).Days; currentReservation.TotalAmount selectedRoom.PricePerNight * currentReservation.StayNights; } selectedRoom.Status RoomStatus.Available; dbContext.SaveChanges();4.4 现象导出报表时 Excel 文件打开报错“文件已损坏”或 Word 报告里表格全是乱码原因压缩包里用了第三方 Excel 库如 NPOI但packages.config或csproj中PackageReference版本号与实际 DLL 不匹配或 Word 报告用Microsoft.Office.Interop.Word但你的电脑没装 Office。解决放弃 Office 互操作用纯文本 表格字符生成简易报告private void ExportReport() { var reportContent new StringBuilder(); reportContent.AppendLine(酒店客房管理日报); reportContent.AppendLine(); reportContent.AppendLine($生成时间{DateTime.Now:yyyy-MM-dd HH:mm:ss}); reportContent.AppendLine(); reportContent.AppendLine(当前空闲客房); reportContent.AppendLine(------------------); var availableRooms dbContext.Rooms.Where(r r.Status RoomStatus.Available).ToList(); foreach (var r in availableRooms) { reportContent.AppendLine(${r.RoomNumber}\t{r.RoomType}\t{r.PricePerNight:C}); } File.WriteAllText(Report_ DateTime.Now.ToString(yyyyMMdd_HHmmss) .txt, reportContent.ToString()); MessageBox.Show(报告已保存为纯文本文件); }注意课程设计不要求美观报表能导出可读文本即达标。强行用 Office 互操作只会增加部署复杂度。5. 报告撰写技巧用“截图代码片段测试用例”三要素让老师一眼认可工作量课程设计报告最容易被当“水货”处理——不是内容不够而是呈现方式没体现你的动手痕迹。我带过的 12 届毕设答辩中90% 老师翻报告前两页就决定是否深入提问。以下是我验证有效的三要素结构直接套用即可。5.1 截图必须带“可验证时间戳”和“操作上下文”不要只贴一张“系统主界面.png”。每张截图需满足左上角有 Windows 任务栏时间证明非盗图界面中包含本次操作的关键状态如“入住”按钮点击后状态栏显示“房间 101 已分配给张三”截图下方用灰色小字标注图 3.1执行“入住”操作后DataGridView 实时刷新客房状态见第 102 行代码。5.2 代码片段只贴“核心逻辑”并用注释标明“你改了哪一行”报告里贴的代码不是复制粘贴全部Form1.cs而是聚焦你解决的关键问题。例如修复“退房不更新订单”问题只贴这 8 行// 【关键修复】原代码仅更新 Room.Status漏掉 Reservation.CheckOutDate // 修改位置ReservationService.cs 第 45 行 var currentReservation dbContext.Reservations .FirstOrDefault(r r.RoomId roomId !r.CheckOutDate.HasValue); // ← 新增条件只查未退房订单 if (currentReservation ! null) { currentReservation.CheckOutDate DateTime.Today; // ← 补充设置退房时间 currentReservation.StayNights (int)(DateTime.Today - currentReservation.CheckInDate).TotalDays; // ← 补充计算入住天数 }提示用 VS 的“插入代码片段”功能CtrlK, CtrlX选surroundwith→region把修复代码包在#region 【修复退房订单同步】里报告中直接引用 region 名。5.3 测试用例表格用“输入-操作-预期输出-实际结果”四列表格证明功能真实可用不要写“系统经过测试运行稳定”。用表格列出你亲手执行的 5 个关键测试编号输入条件操作步骤预期输出实际结果是否通过TC-01客房 101 状态为Available点击 101 行 → 输入张三身份证 → 点“入住”状态变为Occupied弹窗提示成功✅ 状态更新数据库Reservations新增 1 条记录是TC-02客房 101 状态为Occupied再次点击 101 行 → 点“入住”弹窗提示“房间当前状态为 Occupied不可入住”✅ 提示准确无数据库写入是TC-03身份证号11010119900307271X已存在输入相同身份证号 → 点“入住”复用已有 GuestId不新建住客✅Guests表行数不变Reservations关联正确 GuestId是TC-04客房 101 有未结订单选中 101 → 点“退房”CheckOutDate设为今日StayNights自动计算✅ 数据库Reservations字段更新Rooms.Status变为Available是TC-05网络断开禁用网卡启动程序弹窗提示“数据库连接失败”✅ 捕获SqlException未崩溃是这张表的价值在于它证明你不是“编”出来的功能而是逐条验证过。老师扫一眼就知道你真跑起来了。6. 进阶技巧用“状态机模式”重构客房生命周期让业务逻辑不再散落在 7 个按钮事件里做到上面五章你已经超越 80% 的课程设计水平。但如果想让项目真正体现工程思维建议用状态机模式收束客房状态流转逻辑。这不是炫技而是解决“按钮太多、状态判断分散、新增状态要改 5 个地方”的经典痛点。6.1 定义客房状态流转图只允许合法状态迁移客房状态不是随意切换的必须符合业务规则Available→Occupied入住Occupied→Dirty退房后未打扫Dirty→Maintenance维修Maintenance→Available维修完成Dirty→Available打扫完成禁止Occupied→Maintenance不能让客人住着房去维修也禁止Available→Dirty空房不会脏。6.2 实现状态机类用 Dictionary当前状态, 允许的操作 控制流转创建RoomStateMachine.cs封装所有状态变更逻辑public class RoomStateMachine { private static readonly DictionaryRoomStatus, HashSetstring ValidTransitions new() { { RoomStatus.Available, new HashSetstring { CheckIn } }, { RoomStatus.Occupied, new HashSetstring { CheckOut } }, { RoomStatus.Dirty, new HashSetstring { Clean, StartMaintenance } }, { RoomStatus.Maintenance, new HashSetstring { FinishMaintenance } } }; public static bool CanTransition(RoomStatus from, string action) { return ValidTransitions.TryGetValue(from, out var actions) actions.Contains(action); } public static RoomStatus Transition(RoomStatus from, string action) { return from switch { RoomStatus.Available when action CheckIn RoomStatus.Occupied, RoomStatus.Occupied when action CheckOut RoomStatus.Dirty, RoomStatus.Dirty when action Clean RoomStatus.Available, RoomStatus.Dirty when action StartMaintenance RoomStatus.Maintenance, RoomStatus.Maintenance when action FinishMaintenance RoomStatus.Available, _ from // 无效操作保持原状态 }; } }6.3 在按钮事件中调用状态机把业务规则从 UI 层剥离原来散落在btnCheckIn_Click、btnCheckOut_Click、btnClean_Click里的状态判断现在统一收口private void btnCheckIn_Click(object sender, EventArgs e) { if (roomBindingSource.Current null) return; var room (Room)roomBindingSource.Current; if (!RoomStateMachine.CanTransition(room.Status, CheckIn)) { MessageBox.Show($房间 {room.RoomNumber} 当前状态为 {room.Status}不支持入住操作); return; } // 执行入住业务逻辑同第 3 章 // ... // 状态更新交给状态机 room.Status RoomStateMachine.Transition(room.Status, CheckIn); dbContext.SaveChanges(); }这个技巧的价值新增一个状态如UnderInspection只需在ValidTransitions和Transition方法里各加 1 行不用翻遍所有按钮事件所有状态规则集中在一个类里老师一眼看出你理解了业务约束未来扩展 Web 版本时状态机类可直接复用UI 层只负责调用CanTransition和Transition。我带过的最后一届学生里用这个技巧的同学答辩时老师问“如果增加‘临时锁房’功能你要改几处代码”他当场打开RoomStateMachine.cs指着新增的两行说“两处”老师笑着点了头。这种确定性比堆砌 100 行 CRUD 代码更有说服力。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网