
简介面向C#课程设计与毕业设计的酒店管理系统源码项目采用C# Winform与SQL Server实现以酒店业务为场景涵盖信息维护、数据管理等核心逻辑适合Winform初学者阅读也可作为管理系统大作业或毕业设计的完整参考。压缩包共617个文件约96.58MB包含39个C#源码文件、66个db数据库文件、23个dll组件以及大量gif/jpg界面素材、resources与resx资源文件工程内附sln解决方案、config配置和可执行exe可直接运行体验也能在Visual Studio中打开进行二次开发。目前已有173人学习下载。项目结构完整从窗体设计、控件布局到数据访问层均有对应源代码可作为大学“管理系统”大作业或毕业设计的起步模板自带数据库文件与配套配置免去重新造库和配置连接的繁琐流程便于集中精力理解核心业务逻辑和界面交互实现。gif与jpg图片资源既可用于界面展示也能作为换肤或按钮图标素材配合完整工程更易进行个性化修改对快速完成课程设计答辩演示很有帮助。1. 拿到这份酒店管理系统的压缩包先搞清楚它到底能干嘛这两年市面上能搜到的“基于C# Winform窗体的酒店管理系统.zip”绝大多数是高校课程设计和毕业设计的产物但也有相当一部分是真在营业的小宾馆、民宿里跑着的内部系统。它的技术底盘很朴素C# 桌面开发、Winform 窗体、SQL Server 或 Access 做数据存储功能围绕“前台能开单、退房能算钱、老板能看账”这三件事展开。你解压后会看到一堆 .cs、.designer.cs、.sln 和数据库文件这些东西拼起来就是一个完整可跑的酒店管理终端。适合谁想快速了解 Winform 项目怎么组织代码的初学者以及手头有小旅馆、公寓式酒店需要低成本上系统的人。我的建议是别把它当“神器”把它当“可改可用的半成品项目”来拆这篇就按这个思路带你走一遍。2. 从压缩包到能跑起来先摸清工程结构和运行环境2.1 从 .sln 读起先搞清它是单文件工程还是三层结构解压后第一件事不是急着双击 .exe而是拿 Visual Studio 打开 .sln 文件先看解决方案里的项目列表。常见的目录结构分两种一种是所有窗体文件和数据库访问代码挤在同一个项目下另一种是分了 Model、DAL、BLL、UI 四个项目文件夹。我这些年见过的大多数酒店管理系统压缩包目录树长这样HotelManageSystem/ │ HotelManageSystem.sln │ HotelManageSystem.suo ├─ HotelManageSystem/ │ ├─ App.config │ ├─ Program.cs │ ├─ LoginForm.cs │ ├─ LoginForm.Designer.cs │ ├─ MainForm.cs │ ├─ MainForm.Designer.cs │ ├─ RoomManageForm.cs │ ├─ DataAccess/ │ │ ├─ SqlHelper.cs │ │ └─ DbHelper.cs │ ├─ Models/ │ │ ├─ RoomInfo.cs │ │ ├─ GuestInfo.cs │ │ └─ OrderInfo.cs │ └─ Resources/ │ └─ 一些图片和图标资源 └─ DataBase/ ├─ hotel.sql # 建表脚本 └─ db_hotel.mdf # 数据库附加文件第一次看到这样的结构你要先确认三件事Program.cs 里的启动窗体是哪个这决定了整个程序最先加载谁App.config 里有没有数据库连接串DataBase 目录下是 .sql 脚本还是 .mdf 实体文件。很多新手一上来直接按 F5结果报“数据库连接失败”问题就出在根本没看这个目录结构数据库没挂上去。2.2 环境配置Visual Studio 版本和 .NET Framework 目标Winform 项目的环境坑是玄学重灾区尤其当你拿到的压缩包是四五年前的老项目。打开项目属性看目标框架是 .NET Framework 4.5 还是 4.7.2甚至是 .NET Core 3.1 或 .NET 6 的 Winform。不同版本对应能打开的 Visual Studio 不一样VS2019 能打开 4.7.2 的VS2022 对老框架项目兼容性也还好但如果你机器上是 VS2017 去开 4.7.2 的项目会直接提示“不支持”。我一般的做法是先装一个 VS2022 社区版打开项目后如果提示需要重定向目标框架优先改成你自己机器上有的版本。改完框架后重新生成一次解决方案看错误列表里有没有缺引用。老压缩包里常见的问题是缺少某个 DLL 引用比如用了第三方 UI 库、水晶报表组件这类东西 NuGet 不会自动帮你装回来你需要去项目引用的“提示路径”里核对。还有一个小细节很多压缩包里的 App.config 里保存的是开发机上的绝对路径数据库连接串比如 “Data SourceDESKTOP-XXXX”。你换了一台机器这个 DataSource 肯定连不上。别急着改代码先把 SQL Server 跑起来确认服务器实例名是什么再到配置里改。3. 账套与登录数据库挂载、连接串和登录态的设计3.1 连接数据库的两种姿势配 App.config 还是直接拼字符串打开这类压缩包里面的数据库操作层十有八九是 SqlHelper 或 DbHelper 这一类类库。判断这套系统写得好不好先看连接串写在哪。规范的做法是把连接串放在 App.config 的 connectionStrings 节点里程序启动时用 ConfigurationManager 去读。凑合的做法是直接把连接串写在代码里换数据库就得重新编译整个项目。如果你拿到的系统是 Access 数据库版本连接串还得换成 ACE OLEDB 引擎。我只能说这类系统里 C# 与 Access 搭配的场景非常多因为小旅馆老板不可能去装 SQL Server一个 .mdb 文件拷走就是整个账套。两种连接串我都放出来!-- App.config 的 connectionStrings 节点 -- connectionStrings !-- SQL Server 版本 -- add nameHotelDb connectionStringData Source.\SQLEXPRESS;Initial CatalogHotelDB;Integrated SecurityTrue; providerNameSystem.Data.SqlClient / !-- Access 版本 -- add nameHotelDbAccess connectionStringProviderMicrosoft.ACE.OleDB.12.0;Data Source|DataDirectory|\Hotel.accdb; providerNameSystem.Data.OleDb / /connectionStringsusing System.Configuration; using System.Data.SqlClient; public static class DbHelper { private static readonly string ConnStr ConfigurationManager.ConnectionStrings[HotelDb].ConnectionString; public static SqlConnection CreateConnection() { var conn new SqlConnection(ConnStr); conn.Open(); return conn; } }参数说明|DataDirectory| 是 Winform 里的特殊占位符默认指向 exe 同目录下的子文件夹你可以通过 AppDomain.CurrentDomain.SetData(DataDirectory, ...) 来重定向。很多压缩包里的 Access 数据库连不上就是这个占位符指向的路径不对。SQL Server 版则要注意 Integrated SecurityTrue 表示用 Windows 身份验证如果目标机器上 SQL 服务用的是混合模式你得改成 user idsa;passwordxxx。至于用 .mdf 文件还是先执行 .sql 脚本我建议优先用 .sql 脚本建库因为 .mdf 附加时经常遇到“版本比当前实例高”的报错。3.2 登录窗体别只做表面功夫密码加密和登录状态保存这类系统里登录窗体的写法基本千篇一律一个 txtUsername、一个 txtPassword一个 btnLogin 按钮点击后去数据库里查有没有这条用户名和密码。但这里有两个坑第一很多压缩包里的密码是明文存储这在小系统里能跑但如果你要把它交付给营业场所用一旦泄库所有账号都完蛋。第二很多项目登录成功后直接把主窗体 show 出来登录窗体 hide 掉这样切换用户时得重启程序。密码这块我采用的是“先 Hash 再比对”的做法而不是把明文密码拼进 SQL。SQL 查询也不要拼字符串用参数化查询。登录成功后的用户信息放在一个静态类里整个程序生命周期内都能访问using System.Security.Cryptography; using System.Text; using System.Data; using System.Data.SqlClient; public static class UserSession { public static string CurrentUser { get; private set; } public static string Role { get; private set; } public static bool ValidateLogin(string username, string password) { string pwdHash Sha256Hash(password); string sql SELECT UserName, Role FROM SysUser WHERE UserNameu AND PasswordHashp; using (var conn DbHelper.CreateConnection()) using (var cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(u, username); cmd.Parameters.AddWithValue(p, pwdHash); using (var reader cmd.ExecuteReader()) { if (reader.Read()) { CurrentUser reader[UserName].ToString(); Role reader[Role].ToString(); return true; } } } return false; } private static string Sha256Hash(string input) { using (var sha SHA256.Create()) { byte[] bytes sha.ComputeHash(Encoding.UTF8.GetBytes(input)); return Convert.ToBase64String(bytes); } } }逻辑说明把原始密码转成 SHA256 的 Base64 字符串存库登录时对输入做同样的 Hash 再比对这样即使数据库文件被别人拷走也看不到明文。参数化查询解决了拼接 SQL 注入的隐患。注意 DataTable 和 DataReader 的取舍——这个场景只需要读一行结果我用 SqlDataReader 省内存如果你要填充 DataGridView 做列表直接 SqlDataAdapter DataTable 更顺手。第三节做法里角色这个字段值得多说一句。酒店管理系统里角色通常分前台操作员和店长/管理员两种前者只能开单、退房、查房态后者才能看财务报表、改房价、做日结。Winform 里的实现方式一般是登录后拿到 Role在 MainForm 里控制某些菜单项的 Visible 属性。很多源码压缩包里的 Role 字段只是个摆设所有窗体登录后都能打开这对真实营业场景是致命的——收银员要是能改房价月底账目根本没法对。4. 房态总览与开单退房把 DataGridView 和 ListView 用出业务感4.1 房态管理用 DataGridView 上色代替复杂的自定义控件酒店管理系统的核心界面是房态图也就是一层楼房间的占用情况总览。不少源码喜欢用 Button 数组来画房态格子每个按钮代表一个房间空闲显示绿色、入住显示红色、脏房显示橙色。这种方案直观但扩展性差房间数量一变就得改代码。我更推荐用 DataGridView 来做房态矩阵一行数据一个房间通过 CellFormatting 事件来上色。private void RoomGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (e.RowIndex 0) return; DataGridViewRow row RoomGrid.Rows[e.RowIndex]; if (row.Cells[RoomState].Value null) return; string state row.Cells[RoomState].Value.ToString(); switch (state) { case 空净: row.DefaultCellStyle.BackColor Color.FromArgb(198, 239, 206); // 浅绿 break; case 入住: row.DefaultCellStyle.BackColor Color.FromArgb(255, 199, 206); // 浅红 break; case 脏房: row.DefaultCellStyle.BackColor Color.FromArgb(255, 235, 156); // 浅黄 break; case 维修: row.DefaultCellStyle.BackColor Color.Gainsboro; break; } }这段代码的关键是 e.RowIndex 可能为 -1那是列头行必须提前 return。Color.FromArgb 的三组 RGB 数值是我平时习惯用的浅色系打印出来也不刺眼。房间状态字符串从数据库里读出来一般存的是 0/1/2/3 这种数字枚举我写代码时习惯先转成中文状态再绑定数据源这样 CellFormatting 里不用猜数字含义。给每行再增加房间号、房间类型、门市价、当前入住人、入住时间、预离时间这些列一个房间的完整信息就全了。有一个坑是DataGridView 的列名和 SQL 返回的列名必须一一对应如果 SQL 里用了别名别忘了同步修改列的 DataPropertyName。4.2 开单与退房计算日期差、金额精度和房态事务开单退房是这类系统的命脉。先看开单流程选中一个“空净”房间弹出新订单窗体填写客人姓名、证件号、入住天数或预离日期系统自动算出预收押金和房费总额。压单、超时退房、钟点房加收这些逻辑另说先做最基础的。一个血泪经验是日期计算别自己手写天数用 DateTime 结构体自带的减法但要注意“头尾算不算当天”。比如客人 1 月 1 日入住、1 月 3 日退房住了几天按酒店行规是两晚。如果你代码里写的是 LeaveTime - ArriveTime 得到 TimeSpan那 Days 属性等于 2刚好对。但如果客人当天入住当天退TimeSpan.Days 是 0可钟点房应该收费这里就要判断了。private decimal CalcRoomFee(DateTime arrive, DateTime leave, decimal pricePerNight) { if (leave arrive) throw new ArgumentException(退房时间必须晚于入住时间); TimeSpan span leave.Date - arrive.Date; int nights span.Days; // 不足一天按一天算钟点房场景 if (nights 0) nights 1; decimal total nights * pricePerNight; // 延迟退房超过 12:00 加收半天超过 18:00 加收全天 if (leave.TimeOfDay.Hours 18) total pricePerNight * 0.5m; else if (leave.TimeOfDay.Hours 12) total pricePerNight * 0.5m; return decimal.Round(total, 2, MidpointRounding.AwayFromZero); }参数说明leave.Date - arrive.Date 会先去掉时间部分再做差避免“1 月 1 日 23:00 入住、1 月 3 日 01:00 退房”被算成不足两天。decimal.Round 的 MidpointRounding.AwayFromZero 是财务上常用的四舍五入方式默认的银行家舍入会把 2.005 变成 2.00这在房费计算里是要翻车的。押金和应收分开存实收金额 房费 杂费 - 押金这个公式要在退房确认的提示框里显示出来操作员核对无误后再落库。退房时要做两件事更新订单表的状态为“已结账”把房间状态改成“脏房”。这两步必须放在同一个数据库事务里否则会出现房间已退但订单挂着或者订单结了但房间还是入住状态的问题。用 TransactionScope 还是 SqlTransaction这类单体小系统用 SqlTransaction 就够了。4.3 宾客查询与 ListView别再用 TextBox 模糊查询糊弄人很多源码里的宾客查询就是一个 TextBox 加一个“查询”按钮TextBox 里输入关键字SQL 用 LIKE 查询结果显示在 ListView 里。这种做法能跑但体验极差客人只要输错一个字就查不到而且 ListView 的列宽不会自动适配。我常用的做法是用 LvColumnClick 事件做表头排序用 TextChanged 事件做即时过滤查询条件里同时匹配姓名、手机尾号、证件号后四位这样前台操作员搜“138****5678”或“王先生”都能找到订单。private void TxtKeyword_TextChanged(object sender, EventArgs e) { string kw TxtKeyword.Text.Trim(); if (kw.Length 0) { LoadGuestList(GetDefaultSql()); return; } string sql SELECT o.OrderID, g.GuestName, g.Phone, r.RoomNo, o.ArriveTime, o.LeaveTime FROM Orders o JOIN Guests g ON o.GuestID g.GuestID JOIN Rooms r ON o.RoomID r.RoomID WHERE o.Status 入住 AND (g.GuestName LIKE kw OR g.Phone LIKE kw OR g.IDCardTail tail); using (var cmd new SqlCommand(sql, DbHelper.CreateConnection())) { cmd.Parameters.AddWithValue(kw, % kw %); cmd.Parameters.AddWithValue(tail, kw.Length 4 ? kw.Substring(kw.Length - 4) : ); // 填充 DataTable 后绑定到 ListView } }这里的逻辑是手机号模糊匹配用 LIKE证件号后四位用精确匹配。kw.Substring 取最后四位时先判断长度避免字符串截取越界。Winform 里 ListView 绑定数据源没有 DataGridView 方便一般要手动遍历 DataTable 创建 ListViewItem还要设置其中某些列的 Tag 存 OrderID。ListView 的好处是支持“详细信息”视图、表头排序、选中整行高亮适合前台这种密密麻麻的数据量。如果你觉得 ListView 的列头样式太原始可以用 ObjectListView 这类第三方包装控件效果接近 Excel 表格。4.4 加载大数据量的进度反馈状态栏加进度条现在这个系统里也许只有几十间房数据量不大。但把入住历史数据导进来之后宾客查询、报表统计的响应就会变慢。这时候如果界面没有反馈前台操作员会以为程序卡死立即关掉重开这是实体店里最常见的翻车现场。状态栏加进度条是 Winform 的经典做法。先在 MainForm 底部放一个 StatusStrip里面加一个 ToolStripProgressBar然后写异步加载逻辑private async void BtnLoadHistory_Click(object sender, EventArgs e) { ToolStripProgressBar.Value 10; var result await Task.Run(() LoadOrdersFromDb()); ToolStripProgressBar.Value 90; OrderGrid.DataSource result; ToolStripProgressBar.Value 100; StatusLabel.Text 共加载订单 result.Rows.Count 条; }注意事项用 ToolStrip 控件时进度条做不了平滑动画如果需要加载过程流畅顺滑可以直接在后台线程里循环更新 0 到 100同时让 UI 线程每 200 毫秒刷新一次。实际压测的时候会发现进度条经常卡在 90% 不动那是因为数据绑定比数据库取数更耗时进度值要留到 DataSource 赋值完成之后再充满。Winform 的异步模型里await 之后的代码会自动回到 UI 线程所以不用额外 Invoke。前台人员在长导出时还能拖动窗体而不是假死这个体验差异在实体店特别明显。5. 避坑排查酒店管理系统从源码到上线的 5 个真实现场5.1 现象双击 exe 后一直转圈登录界面都出不来原因Program.cs 里先做了数据库连接测试而连接串里指向的 SQL Server 实例名是开发者的机器名本机根本没有这个实例。连接超时默认 15 秒这期间界面啥都不显示。解决先打开 App.config 检查 Data Source改成自己机器上的实例名。SQL Server 服务没启动的老老实实去服务管理器里启动。更省事的方法是在 DbHelper 的连接串里加 Connect Timeout5连接失败时快速抛异常同时在 Program.cs 里加全局异常捕获用 MessageBox 把错误信息抛给操作员而不是让它默默卡死。5.2 现象DataGridView 里改了房态点保存报“并发冲突”原因很多压缩包里的保存按钮用的是 SqlDataAdapter.Update它默认用乐观并发检查也就是 Update 语句的 WHERE 条件里带了所有原始列值。两个窗口同时操作同一行数据的时候第二个人的更新条件匹配不到原始值于是报并发冲突。解决如果你确认这套系统只是单人操作把 SqlDataAdapter 的 UpdateCommand 改成主键更新UPDATE Rooms SET RoomStatestate WHERE RoomIDid。如果你要支持多前台同时操作那就得把更新时间戳字段加进并发控制逻辑。我一般选择前者小宾馆不存在多人同时改同一个房间主键更新最简单可靠。5.3 现象Win10 机器上 Access 数据库报“未找到提供程序 Microsoft.ACE.OleDB.12.0”原因64 位系统下Winform 项目默认 x86 或 AnyCPU而 ACE 驱动版本位数与程序位数不匹配。绝大多数压缩包里面自带的 .accdb 文件是 32 位 Access 创建的Excel 里的 ODBC 驱动又装的是 64 位。解决两个方向任选把项目平台目标改成 x86同时安装 AccessDatabaseEngine_x64.exe 的 32 位版或者干脆换数据库。如果这套系统只是几十间房的量直接切到 SQL Server Express LocalDB 更省心LocalDB 不需要安装服务跟着连接串走。5.4 现象跨日凌晨退房房费计算结果差了一天原因代码里把退房日期 - 入住日期当成 TimeSpan然后直接取 Days 属性忘了考虑时间部分。客人 1 号晚上 20:00 入住3 号早上 8:00 退房TimeSpan 是 1.5 天Days 等于 1被算成 1 晚实际住 2 晚。改动一行leave.Date - arrive.Date让时间部分归零后再算天数问题消失。这也是我在 4.2 里强调的原因。5.5 现象高分屏或缩放 150% 的电脑上界面错位控件显示不全原因Winform 的默认 DPI 感知模式在字体缩放时不会自动重算布局。老项目里写死的固定宽度、固定字体大小在 2K 屏上被放大 1.5 倍窗体高度不够下面的按钮直接看不见。解决这是老 Winform 项目的通病了Program.cs 开头加一行[STAThread] static void Main() { // 让系统按实际 DPI 缩放而不是 Winform 默认的位图拉伸 Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 可选如果想完全按声明尺寸显示这行不要加 // Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Application.Run(new LoginForm()); }需要注意 SetHighDpiMode 要在 Application.Run 之前调用而且项目目标框架得支持。老 .NET Framework 4.5 没这个方法就需要手动在 app.manifest 里声明 dpiAware 配置。这个坑修复不难但排查过程很折磨人因为你在 1080P 显示器上看一切正常交付到店里的 2K 一体机上就翻车属于典型的“开发环境没问题、生产环境出大事”。6. 交付前的最后一公里发布配置与两个顺手可做的扩展6.1 发布ClickOnce 还是绿色免安装版压缩包用 Visual Studio“发布”功能可以打成 ClickOnce 安装包操作步骤是右键项目 → 发布 → 选择发布文件夹 → 勾选“从网站安装应用”。但 ClickOnce 有个老毛病默认要求签名证书很多开发者没有代码签名证书装的时候会弹“未知发布者”店里老板看到这个提示通常不敢继续。所以我更推荐绿色免安装方案用 Debug 或 Release 编译后把 exe、dll、配置文件、数据库文件全部拷到一个文件夹里写一个“启动服务.bat”先启动 SQL Server 服务再运行 exe。数据库文件放在 exe 同目录App.config 里的 DataDirectory 用相对路径这样整个文件夹拷到新电脑就能跑。数据库升级时替换一个文件就行这是小规模系统最实际的做法。6.2 扩展把退房结算做成可复用的结算方法最后一章我想给一个具体技巧把第四章里的退房计算逻辑从按钮事件里抽出来放进独立的结算类。这样以后加会员优惠、加佣金、加钟点房规则时不用动界面代码。做法是建一个 SettlementService 类把计算房费、押金抵扣、更新房态、写日志这个流程封装成方法返回结算单对象public class SettlementResult { public decimal RoomFee { get; set; } public decimal Deposit { get; set; } public decimal ExtraFee { get; set; } public decimal ShouldPay { get; set; } } public SettlementResult Checkout(int orderId) { // 读取订单、房价、押金、杂费 // 调用 CalcRoomFee 计算房费 // 计算应付 房费 杂费 - 押金 // 开启事务更新订单状态、房态、写日志 // 返回结算结果 }界面端的代码只负责从结算对象里取数值展示到 Label 上然后调用打印。拆成服务类之后不管是从 Winform 按钮触发还是以后接一个自助入住机都能复用这份逻辑。C# 里这种“把业务从界面剥出去”的写法比在按钮事件里堆上千行代码好维护太多了。这是我被前面几个酒店项目反复捶打之后总结的习惯逻辑先封装成类、界面只做展示和输入校验。刚开始你会觉得写服务类多绕一层没必要可一旦店主要求加“旅行社佣金按比例结算”这种需求你会庆幸自己当初留了这么个后门。做管理系统开发永远要给未来留改口的机会这也算是我这个方向的真正心得。这套思路不只适用于酒店管理换到任意一个 Winform 业务管理系统你都能少走不少弯路希望帮到你。本文还有配套的精品资源点击获取