
简介这是一套基于C# WinForm与SQL Server开发的酒店管理系统完整源码包适合正在学习桌面应用开发或需要完成课程设计、毕业设计的学生及初级开发者。系统围绕客房管理、预订入住、账务结算等核心业务通过界面化操作替代手工登记提升酒店运营效率与数据准确性。压缩包共169个文件容量约2.1MB包含63个.cs源码文件、17个.resx及resources资源文件、9个.pdb调试信息、7个.dll运行库以及SQL数据库脚本、项目解决方案.sln和Excel表格等源码采用BLL、DAL分层结构便于理解业务逻辑与数据访问的分离方式。资源内还附带可执行的.exe程序及多张PNG/JPG界面截图可先运行预览再对照源码学习。目前已有743人学习过这份材料对想快速掌握C#三层架构、完成酒店类信息管理系统设计与实现的学习者来说是一份可直接借鉴的完整范例。1. 拿到这套 WinForm SQLServer 酒店管理系统先别急着双击 exe先回答一个最基础的问题这是什么。它是一套典型的桌面版酒店管理系统技术栈锁死在标题里——C# 写窗体WinForm 做界面SQLServer 存数据业务覆盖前台登录、房态图、预订开房、入住退房、结账查询。常见用途是毕业设计或课程设计也有很多刚转 .NET 的从业者拿它当第一个完整项目啃。它的价值不在和 Opera 这类商业 PMS 比功能而在于把窗体开发、ADO.NET 参数化 SQL、SQLServer 表设计串成一个能跑通的最小闭环。但很多人第一步就卡住解压、附加数据库、按 F5三秒后弹出 SQL 登录失败。下面按我自己跑这类项目的顺序拆先看表结构再部署数据库然后读关键代码最后把踩过的坑列出来。2. 拆表结构四张核心表建好业务流就通了一半拿到源码包先别急着附加数据库。我习惯先找 SQL 脚本看表结构因为表结构就是需求文档。这类酒店管理系统通常由四张核心表撑起来用户表、房型表、客房表、订单表有的版本会再拆一张消费记录表但主干就是这四张。看清楚它们的字段和状态取值整个业务边界就浮出来了。2.1 用户表角色字段为什么比独立权限表更实用用户表一般叫 T_User 或者 Sys_User字段逃不开这几个UserID、UserName、Password、RoleName。RoleName 就两个取值管理员和前台。为什么不用独立权限表因为这个规模下真用不到。拆出用户角色关联表、角色权限表、菜单表代码复杂度翻一倍答辩时还要解释半天而业务上只是“管理员能进设置页前台只能操作开房退房”的区别。登录时的做法通常是一条参数化查询SELECT UserID, UserName, RoleName FROM T_User WHERE UserName name AND Password pwd密码多半是明文存储教学项目可以接受别急着在生产环境照搬。查出来之后把 RoleName 存到全局变量或主窗体的属性里主窗体加载时根据角色控制菜单项可见性。这个设计简单粗暴但对这个体量完全够用你后面改起来也容易。2.2 房型与客房房号用 varchar 是故意的客房表的设计直接决定房态图好不好画。房型表和客房表一般拆开房型表管类型名称和门市价客房表管房号、所属房型、当前状态。为什么拆改价格只动房型表客房表不用跟着改这是最基本的范式。房号字段值得多说一句。很多新手会把它设成 int然后存 101、102结果遇到 R101、A201 这种带前缀的房号就傻眼。标准做法是 varchar因为房号本质是标识而不是数字。等做到报表统计时你会遇到 sqlserver 的字符串转数字问题ORDER BY RoomNO 按字符串排R2 会排到 R15 后面。正确写法是把字母后面的部分转成 intSELECT RoomNO FROM T_Room ORDER BY CAST(SUBSTRING(RoomNO, 2, LEN(RoomNO)) AS INT)客房状态一般用 TINYINT 存0 空房、1 入住、2 预订、3 维修。这个字段是房态图的颜色依据也是整张表改动最频繁的字段。2.3 订单表预订、入住、退房都在这一张表上流转订单表是整个系统的核心预订和入住通常共用一张表用 OrderStatus 区分状态0 预订、1 入住、2 已退房、3 已取消。预订插入一条订单房间状态置为 2入住时订单改成 1房间状态改成 1退房时算清费用订单改成 2房间状态归 0。这些跨表操作后面会讲用事务收口。常见的设计脚本长这样字段名以你手里的源码为准-- 酒店管理系统核心表常见设计字段名以实际源码为准 CREATE DATABASE HotelDB; GO USE HotelDB; GO -- 用户表 CREATE TABLE T_User ( UserID INT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(20) NOT NULL UNIQUE, Password NVARCHAR(50) NOT NULL, RoleName NVARCHAR(20) NOT NULL DEFAULT 前台 ); -- 房型表 CREATE TABLE T_RoomType ( RoomTypeID INT IDENTITY(1,1) PRIMARY KEY, TypeName NVARCHAR(20) NOT NULL, Price DECIMAL(10,2) NOT NULL ); -- 客房表 CREATE TABLE T_Room ( RoomID INT IDENTITY(1,1) PRIMARY KEY, RoomNO NVARCHAR(10) NOT NULL UNIQUE, RoomTypeID INT NOT NULL REFERENCES T_RoomType(RoomTypeID), Status TINYINT NOT NULL DEFAULT 0 -- 0空房 1入住 2预订 3维修 ); -- 订单表 CREATE TABLE T_Order ( OrderID INT IDENTITY(1,1) PRIMARY KEY, OrderNO NVARCHAR(30) NOT NULL, CustomerName NVARCHAR(20) NOT NULL, RoomID INT NOT NULL REFERENCES T_Room(RoomID), CheckInDate DATETIME NOT NULL, CheckOutDate DATETIME NOT NULL, OrderStatus TINYINT NOT NULL DEFAULT 0, -- 0预订 1入住 2退房 3取消 TotalAmount DECIMAL(10,2) NOT NULL DEFAULT 0 );几个字段的取舍说明RoomNO 加 UNIQUE 防止重复房号订单号 OrderNO 由程序在保存时生成一般取日期加序号不要用自增 ID 直接当订单号展示TotalAmount 在订单创建时写入当前房价这样以后房型调价不会影响历史订单Status 用 TINYINTC# 里取出后要 Convert.ToInt32 再比较别拿字符串去拼。四张表的关系用一张表总结表核心字段状态字段含义主要操作T_UserUserID, UserName, Password, RoleName角色区分权限登录校验T_RoomTypeRoomTypeID, TypeName, Price无定价与房型维护T_RoomRoomID, RoomNO, RoomTypeID, Status0空/1入住/2预订/3维修房态图、开房、退房T_OrderOrderID, OrderNO, CustomerName, RoomID, 日期, OrderStatus0预订/1入住/2退房/3取消预订、入住、结账看到这里你应该明白了房态图就是查 T_Room 的 Status 并给房间上色前台操作就是改这两张表的状态。业务流一清楚后面读代码就快得多。3. 本地跑通数据库SQLServer 附加与连接字符串排错表结构看懂了接下来把数据库跑起来。这一章是最容易劝退新手的地方绝大多数运行失败发生在数据库侧而不是 C# 代码。按安装、附加、改连接三步走每一步对应一类报错。3.1 安装 SQLServer 与 SSMS选对认证模式先确认你机器上有没有 SQLServer。如果没有装 SQLServer 2022 Developer 版就行官方免费功能足够跑这种量级的系统。安装时有两个选择直接决定后面是否顺利一是功能至少要勾选“数据库引擎”和“管理工具”管理工具就是 SSMS也就是 sqlserver 图形化工具二是到“服务器配置”页时认证模式务必选“混合模式”然后设置 sa 密码。步骤很简单运行安装程序选“自定义”到功能页勾选数据库引擎和 SSMS。到“服务器配置”页认证模式选“混合模式”填 sa 密码。安装完成后打开“SQL Server 配置管理器”确认实例服务正在运行TCP/IP 协议处于启用状态。翻车最多的地方是只装了 SSMS 没装数据库引擎连接时报“找不到服务器”或者图省事选了默认的 Windows 认证模式后面代码里写 User IDsa 永远登录不进去。这一步别搜“sqlserver 安装教程”看一整页真正要紧的就这几个选择。3.2 附加数据库用“附加”而不是“导入数据”打开 SSMS连上服务器左侧“数据库”节点右键 → 附加 → 添加选中解压出来的 .mdf 文件。如果同一目录缺了 .ldf 日志文件下方列表里会有一行日志显示“找不到”选中它点“移除”确认后 SQLServer 会尝试重建日志。附加完成后数据库节点下会出现对应的数据库名比如 HotelDB。这一步有三个常见的坑。第一解压路径不要带中文放 C 盘根目录或英文路径下最稳中文路径下附加偶发“无法打开物理文件”的报错。第二SQL Server 服务账户要对 .mdf 所在目录有读取权限如果放在 D 盘某个带权限限制的文件夹里附加时直接拒绝访问给目录加上“MSSQLSERVER”账户的读写权限就能解决。第三有些人看到附加失败后去点“导入数据”向导然后被“sqlserver 无法导入数据 数据无效”这种提示绕晕——附加数据库不是导入数据入口在“附加”菜单不在向导里。3.3 修改连接字符串App.config 里一处改完数据库起来了C# 程序怎么找到它靠连接字符串。这类源码通常把连接串放在 App.config 里格式大致这样?xml version1.0 encodingutf-8 ? configuration connectionStrings add nameHotelDB connectionStringData Source.;Initial CatalogHotelDB;User IDsa;Password你的密码;Connect Timeout5 providerNameSystem.Data.SqlClient / /connectionStrings /configuration核心参数就几个改完基本能通参数作用常见取值Data Source服务器地址. 代表本机默认实例命名实例写成 计算机名\SQLEXPRESSInitial Catalog要连的数据库名HotelDB必须和附加后的数据库名一致Integrated Security是否用当前 Windows 账号登录False 用账号密码True 用 Windows 认证User ID / PasswordSQLServer 账号sa / 安装时设的密码Connect Timeout连接超时秒数5 或 15网络差再调大C# 侧读取连接字符串的标准写法using System.Configuration; using System.Data.SqlClient; string connStr ConfigurationManager.ConnectionStrings[HotelDB].ConnectionString; using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); // 能走到这里说明连接字符串没问题 }连不上时先看报错文字再动手写“找不到服务器”去查 Data Source写“用户 sa 登录失败”去查认证模式和密码写“无法打开登录请求的数据库”去查附加操作和 Initial Catalog。乱调参数不如先定位这一层。4. WinForm 侧关键代码登录、房态图、查询这三处最值得读数据库通了回到 C# 工程。毕业设计源码里最值得读的三块代码登录校验、房态图生成、列表查询。这三块看懂了WinForm 的基本套路就通了一半。多数源码会拆成三层或至少两层窗体层只负责收集输入和显示结果SQL 放在独立的 DBHelper 或 DAL 类里按钮点击事件里不应该手写 SqlConnection。4.1 登录校验用参数化 SQL不要拼接字符串登录窗体的按钮事件常见写法是这样string connStr ConfigurationManager.ConnectionStrings[HotelDB].ConnectionString; string sql SELECT COUNT(*) FROM T_User WHERE UserNamename AND Passwordpwd; using (SqlConnection conn new SqlConnection(connStr)) { using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(name, txtUser.Text.Trim()); cmd.Parameters.AddWithValue(pwd, txtPwd.Text); // 教学项目常用明文生产要哈希 conn.Open(); int count (int)cmd.ExecuteScalar(); if (count 0) { // 登录成功读取角色并打开主窗体 } } }逻辑说明SqlParameter 用 name 占位符传参而不是 string.Format 拼 SQL否则用户输入一个单引号就能让查询报错甚至被注入using 包裹连接和命令跑完自动释放避免连接池被占满ExecuteScalar 只取第一行第一列登录这种“是否存在”的查询用 COUNT(*) 最合适很多初学者在这里先查 DataTable 再数行数绕远了。补充一个细节AddWithValue 对日期类型偶尔会推断错精度登录这种场景无所谓但后面做订单查询时遇到日期查不到数据优先考虑给参数指定 SqlDbType.DateTime。4.2 房态图FlowLayoutPanel 动态生成按钮比买图表控件实在房态图是这类系统的门面。搜 winform 界面美化或 winform 仪表盘控件开源会看到一堆第三方图表库但酒店系统里最稳的做法是 FlowLayoutPanel 装 Button按房间状态换背景色数据一次查出来点按钮再进操作窗体。核心代码private void LoadRoomMap() { string sql SELECT r.RoomID, r.RoomNO, r.Status, t.TypeName, t.Price FROM T_Room r JOIN T_RoomType t ON r.RoomTypeID t.RoomTypeID; DataTable dt SqlHelper.ExecuteQuery(sql); flowLayoutPanel.Controls.Clear(); foreach (DataRow row in dt.Rows) { Button btn new Button(); btn.Text ${row[RoomNO]}\n{row[TypeName]}\n{row[Price]}元; btn.Size new Size(110, 70); btn.Tag row[RoomID]; switch (Convert.ToInt32(row[Status])) { case 0: btn.BackColor Color.LightGreen; break; // 空房 case 1: btn.BackColor Color.OrangeRed; break; // 入住 case 2: btn.BackColor Color.Khaki; break; // 预订 case 3: btn.BackColor Color.Gray; break; // 维修 } btn.Click BtnRoom_Click; flowLayoutPanel.Controls.Add(btn); } }逻辑说明FlowLayoutPanel 会自动换行不用手算坐标Button.Tag 存 RoomID点击时从 Tag 取值再弹开房或退房窗体界面层不用缓存整套业务数据Status 取出后一定要 Convert.ToInt32 再判断否则查出来的值可能是字符串比较就会莫名失败——这种问题排查起来很玄学其实是类型没转对。另外房态图别去接第三方仪表盘控件动态 Button 就是最稳的方案答辩时还能讲清楚“动态生成控件”这个知识点。4.3 列表查询DataGridView 的复选列和日期区间查询订单列表和查询功能常见的需求是状态列把 0/1 显示成复选框日期区间要能正确查出来。C# 里查订单并绑定列表的写法private void LoadOrderList(DateTime begin, DateTime end) { string sql SELECT o.OrderNO, o.CustomerName, r.RoomNO, CASE WHEN o.OrderStatus 1 THEN 1 ELSE 0 END AS IsChecked, o.CheckInDate, o.CheckOutDate, o.TotalAmount FROM T_Order o JOIN T_Room r ON o.RoomID r.RoomID WHERE o.CheckInDate begin AND o.CheckInDate end ORDER BY o.CheckInDate DESC; DataTable dt SqlHelper.ExecuteQuery(sql); dataGridView1.DataSource dt; // 把 IsChecked 显示为复选框列而不是 0/1 文本 DataGridViewCheckBoxColumn col new DataGridViewCheckBoxColumn(); col.DataPropertyName IsChecked; col.HeaderText 在住; dataGridView1.Columns.Add(col); }两个关键点。第一日期边界用“左闭右开”的写法 begin 且 end传参时 begin 取 dateStart.Value.Dateend 取 dateEnd.Value.Date.AddDays(1)。DateTimePicker 默认带当前时间SQL 里写等于某一天基本查不到数据这个写法把当天完整包含进去也不会多算次日的数据。第二DataGridView 默认把 0/1 显示成文本要显示成勾选框得用 DataGridViewCheckBoxColumnDataPropertyName 必须等于 SQL 里的别名 IsChecked。如果你源码里没有这一列按这个思路补上就行。5. 避坑实战跑这套酒店管理系统必踩的 5 个坑与排查思路下面这 5 个坑是我跑这类项目时大概率会遇到的问题每条按现象、原因、解决的顺序写你可以直接对照排查。5.1 附加数据库报“无法打开物理文件”或附加后状态为“可疑”现象选中 .mdf 附加时直接弹“无法打开物理文件 xxx.mdf拒绝访问”或者附加成功了但数据库名称旁边带着“可疑”两个字。原因SQL Server 服务账户对 .mdf 所在目录没有 NTFS 读取权限常见把文件解压到带权限限制的盘符或中文路径。日志文件缺失也会让一致性校验不过数据库进入“可疑”状态。解决把整个文件夹移到没有中文、没有特殊权限限制的路径比如 C:\HotelDB右键目录 → 属性 → 安全 → 添加 MSSQLSERVER 账户并给予读写权限日志缺失时按附加窗口下方的列表移除日志行后重试。如果附加完已经是“可疑”状态教学项目最省事的办法是删除重新附加一次别花时间修日志。5.2 用户 sa 登录失败或“无法打开登录请求的数据库”现象程序跑起来任何操作都会弹“用户 sa 登录失败”或者“无法打开登录请求的数据库 HotelDB登录失败”。原因安装 SQLServer 时选了默认的 Windows 认证模式没开混合模式或者 sa 账号被禁用。后者“无法打开登录请求的数据库”通常是连接字符串里的 Initial Catalog 和附加后的数据库名对不上。解决用 Windows 认证方式登录 SSMS在服务器属性 → 安全性里把认证模式改成“SQL Server 和 Windows 身份验证模式”然后在安全性 → 登录名 → sa 属性里启用登录并重置密码最后重启 SQL Server 服务。如果报“无法打开数据库”回第 3.3 节核对 Initial Catalog 是不是写的 HotelDB。这个问题本质是认证和数据库注册的问题跟 C# 代码没关系。5.3 程序能启动但一登录就报“未将对象引用设置到对象的实例”现象F5 能跑到登录窗体输入账号密码点登录直接崩在 NullReferenceException。原因源码里登录成功后一般会用 Rows[0] 取用户信息如果查询没返回行直接取 Rows[0] 就空引用。查询没返回行的根因通常是表名或列名对不上——比如源码里查的表叫 T_User你附加的库里表叫 T_UserInfo或者根本没有数据。解决先在 SSMS 里手动执行那条登录 SQL确认返回了数据再确认连接字符串指向的库确实有数据。把代码里 catch 块中吞掉的真实异常放出来看别只弹一个“操作失败”否则永远不知道是哪条 SQL 出问题。这种空引用问题九成是上游数据或 SQL 的问题不是对象本身的问题。5.4 房态图上点按钮没反应或改了房态刷新后又变回去现象房态图按钮能点但不管怎么操作回到主界面颜色不变或者退房后房间还是显示入住中的颜色。原因这类系统的房态图通常在 Form_Load 或构造函数里加载一次操作子窗体直接改了数据库但主窗体没有在子窗体关闭后重新查询 T_Room。界面刷新的时机不对数据库里其实已经变了。解决把加载房态图的代码抽成一个公共方法 LoadRoomMap()在主窗体的 Activate 事件或子窗体关闭后重新调用。这是我在刷新问题上交过的血泪经验WinForm 的 Form_Load 只跑一次想让界面跟着数据走必须把刷新挂在更合适的生命周期事件上而不是在构造函数里指望它自动重跑。5.5 查“今天”的订单查不到数据或区间查询少一天现象选日期范围比如 2024-06-01 到 2024-06-05结果缺了最后一天的数据只选当天时一条都查不出来。原因DateTimePicker.Value 自带时间部分比如 2024-06-05 08:30SQL 里用 比较日期当然匹配不到区间用了 和 6 月 5 日 0 点以后的数据会被“次日才能查到”的逻辑漏掉。解决查询条件统一写成 begin AND end传参时 begin 取 .Dateend 取 .Date.AddDays(1)。这是 SQLServer 日期边界最稳妥的“左闭右开”写法熟悉以后处理所有日期区间查询都不会少一天。顺带一提这和 sqlserver 字符串转数字的问题本质一样都是类型边界没处理对只是日期更隐蔽。6. 验收与升级功能核对表和把开房收成事务的好习惯把系统跑通不等于做完。我习惯按下面这张核对表过一遍证明“能用”而不是“能打开”。对毕业设计来说答辩时演示的流畅度往往比功能多更关键。模块验收动作预期结果登录分别用管理员和前台账号登录角色不同主窗体可见菜单不同房态图打开主窗体房间按钮按状态着色双击可进入操作开房选空房开房设置退房时间房态图该房变色订单列表出现一条记录退房对在住房间退房输入消费金额房费自动计算房间恢复空房色查询按日期区间查订单区间内订单全部出现不差一天如果你还想在答辩或简历上多一个亮点有一个成本很低的做法把开房流程里“插入订单”和“修改房间状态”两步包进一个事务。很多源码只是两个 SqlCommand 顺序执行中间任何一步失败数据就不一致。用事务收口既稳又能讲清楚using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); using (SqlTransaction tran conn.BeginTransaction()) { try { using (SqlCommand cmd new SqlCommand()) { cmd.Connection conn; cmd.Transaction tran; cmd.CommandText INSERT INTO T_Order(OrderNO, CustomerName, RoomID, CheckInDate, CheckOutDate, OrderStatus, TotalAmount) VALUES(orderNo, customerName, roomID, checkIn, checkOut, 1, amount); cmd.Parameters.AddWithValue(orderNo, orderNo); cmd.Parameters.AddWithValue(customerName, customerName); cmd.Parameters.AddWithValue(roomID, roomID); cmd.Parameters.AddWithValue(checkIn, checkIn); cmd.Parameters.AddWithValue(checkOut, checkOut); cmd.Parameters.AddWithValue(amount, amount); cmd.ExecuteNonQuery(); cmd.CommandText UPDATE T_Room SET Status 1 WHERE RoomID roomID; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue(roomID, roomID); cmd.ExecuteNonQuery(); } tran.Commit(); } catch (Exception) { tran.Rollback(); throw; } } }这个事务代码有两个要点SqlCommand 必须同时挂同一个连接和同一个事务对象否则 Commit 时事务里没有命令数据照样不写第二条命令复用同一个 SqlCommand 时必须先 Clear 掉第一条的参数再 AddWithValue否则同名参数会重复添加报错。更稳妥的做法是给日期参数指定 SqlDbType.DateTime避免 AddWithValue 推断错误。我个人的习惯是拿到任何这类项目先看 SQL 再看窗体事件绝不先双击 exe。看表结构能看出业务边界看事件能看出三层是否分开。如果时间紧别急着给界面加皮肤把“登录 → 开房 → 退房 → 查询”这条主链路走顺比什么都强。希望帮到你。本文还有配套的精品资源点击获取