简介这是基于C#与Windows Forms开发的酒店管理系统完整源码面向正在学习桌面开发或需要课程设计参考的读者。系统涵盖用户管理、房客管理、客房管理、出入管理等核心模块采用事件驱动机制完成界面交互与业务处理。资源包共有五十四个文件压缩后仅一百五十九KB主要包含C#源码文件、窗体界面资源、可执行程序以及Access数据库文件目录结构清晰便于定位各层代码。项目基于Visual Studio编写通过ADO.NET对数据库进行增删改查默认使用Access后续也可扩展至SQL Server等平台。通过学习这套源码可以掌握面向对象设计、窗体布局、事件处理以及分层架构如表示层、业务层、数据层的实际运用。这套源码目前已有二千九百五十八人学习下载实用性和参考价值较高。1. C# WinForm 酒店管理系统先看清分层再谈二次开发很多人拿到这套 C# WinForm 酒店管理系统源码后第一件事就是 F5 跑起来看到登录窗口弹出来就算结束。我建议你换个顺序先打开解决方案把 UI、DAL、BLL、Entity 四个目录之间的调用关系捋清楚。这套系统最大的价值不在界面有多好看而在于它是一个「窗体 分层架构 ADO.NET 操作 Access 数据库」的完整样本比那些把所有 SQL 都写在按钮事件里的课程设计规范得多。它覆盖的运营链路很完整管理员登录、房客信息维护、客房状态跟踪、入住和退房登记。你拿到的不只是一个登录框加一张表而是从界面点击一路到数据库落盘的完整调用链。适合三类人第一次做 WinForm 项目的在校生、想学习 BLL/DAL 分层的初级开发者以及需要在此基础上快速改造成小酒店或民宿管理工具的人。下面我按「结构、数据库、模块、排错」的顺序把这套源码拆一遍中间的写法可以直接抄走。2. 四层结构拆解Entity、DAL、BLL、UI 各自在管什么这套源码的入口是解决方案文件里面挂着项目文件和一些看起来陌生的目录。不少初学者会把 bin 和 obj 也当成源码去读其实这两者完全是编译产物跟手写代码没有关系。先把这个分辨清楚后面读代码会顺畅很多。2.1 从解决方案文件看整体源码目录与编译产物的区别解决方案文件.sln 后缀只负责组织和导航多个项目文件.csproj 后缀项目文件里记录的才是真正重要的信息目标框架版本、输出类型WinExe、引用列表、编译输出路径。打开 .csproj 看一眼TargetFrameworkVersion是 2.0 还是 4.x能省掉后面不少编译报错。下面这张表是这套源码里最常见目录的真实身份目录/文件身份是否需要手动改UI/窗体、控件、界面事件代码需要BLL/业务逻辑层调用 DAL需要DAL/数据访问层写 SQL需要Entity/实体类只有属性需要bin/编译输出的 exe/dll不需要obj/编译中间文件不需要.vs/Visual Studio 本地缓存不需要Properties/程序集信息、资源、设置偶尔需要看到 bin、obj 和 .vs 时可以直接跳过它们不属于源码。很多人在网上拿到压缩包后发现文件特别多其实真正需要阅读和修改的就是 UI、DAL、BLL、Entity 四个目录外加数据库文件。压缩包如果没清理干净把 .vs 也打进去了不影响使用只是体积变大。2.2 四层职责边界为什么这不是「窗体堆出来的 Demo」这套源码的分层方式是经典的三层架构加实体层调用方向是单向的UI 只调 BLLBLL 只调 DALDAL 只负责 SQL各层之间的数据传递都用 Entity 对象。Entity 里的类只包含属性没有逻辑。比如房客实体就是 GuestId、Name、IdCard、Phone、RoomId、CheckInDate 这些字段的集合对应数据库里的一行记录。DAL 层是唯一出现 SQL 字符串和数据库连接的地方。每个实体对应一个数据访问类比如 RoomDAL 管理客房表、GuestDAL 管理房客表。BLL 层做业务判断比如入住前检查客房是否空闲、退房时更新房态。UI 层的按钮事件里看不到 SQL只有对 BLL 方法的调用。把 SQL 写进按钮事件是最常见的坏味道一旦数据库换了或字段改了得去每个窗体里翻代码。这套源码把 SQL 集中到 DAL就是给后续维护留了一条路。2.3 客房状态在层间流转一个状态字段的完整旅行客房管理是整套系统的轴心状态字段一般就四个值空闲、占用、清洁中、维修中。一次完整的入住流程是这样的用户在 UI 勾选空闲房BLL 校验该房仍为空DAL 插入房客记录并把客房改为占用退房时 BLL 校验房间号真实存在DAL 更新房客离店时间并把客房置为清洁中保洁完成后由管理员手动改回空闲。状态每变一次都会经过 UI 到 BLL 再到 DAL 的完整调用链。在 DAL 层查询空闲客房常见写法是传一个状态参数进去public DataTable GetRoomsByStatus(string status) { string sql SELECT RoomId, RoomNo, RoomType, Price, Status FROM Rooms WHERE Status status; using (OleDbConnection conn new OleDbConnection(connStr)) using (OleDbCommand cmd new OleDbCommand(sql, conn)) { cmd.Parameters.Add(status, OleDbType.VarChar).Value status; OleDbDataAdapter adapter new OleDbDataAdapter(cmd); DataTable dt new DataTable(); adapter.Fill(dt); return dt; } }这里用status参数而不是字符串拼接是为了同时解决 SQL 注入和单引号转义两个问题。OleDbType.VarChar对应 Access 里的文本类型如果你的字段是数字状态值比如 0 代表空闲那就改成OleDbType.Integer。返回 DataTable 而不是 List 的原因是 DataGridView 可以直接绑定 DataTable省去一行一行赋值。3. Access 数据库访问连接串、参数化查询与换库路径这个项目的数据层全部围绕 Access 展开用的是经典 ADO.NET 里的 OleDb 系列类。Access 对小型管理软件很够用但大家换到 SQL Server 或 MySQL 的需求一直存在所以在这一章把连接、参数化、换库三个要点一次说透。3.1 连接字符串与文件路径bin 目录下的数据库坑Access 连接字符串的核心是 Provider 和 Data Source 两个部分ProviderMicrosoft.ACE.OLEDB.12.0;Data SourceHotel.accdb;Persist Security InfoFalse;Provider 指定驱动ACE.OLEDB.12.0 对应新版 Access 数据库引擎能同时处理 accdb 和 mdb 格式Jet 4.0 是老驱动只能读 mdb而且在新系统上兼容性很差。Data Source 这里最容易被坑如果写相对路径程序启动时会把当前目录当成基准而 Visual Studio 调试时当前目录是 bin\Debug数据库文件如果只放在项目根目录运行时就会报找不到文件。我一般会统一拼绝对路径string dbPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Data, Hotel.accdb); string connStr ProviderMicrosoft.ACE.OLEDB.12.0;Data Source dbPath ;Persist Security InfoFalse;;AppDomain.CurrentDomain.BaseDirectory永远是程序集所在的目录也就是 bin 目录下的实际位置比Environment.CurrentDirectory稳得多。数据库文件放进 bin\Debug\Data 目录后无论从 Visual Studio 启动还是直接双击 exe都能找到。如果你希望数据库跟着源码走那就在项目的 csproj 里把 accdb 文件的复制到输出目录设为「如果较新则复制」。3.2 参数化查询为什么 SQL 拼接在这个项目里必须禁止老 Access 项目里最常见的就是把文本框内容直接拼进 SQL看起来省事实际埋了一堆雷。房客姓名里带个单引号比如 OBrien拼接后的 SQL 直接语法错误更严重的是登录功能如果登录 SQL 是拼出来的用户名传 OR 11就能绕过密码校验。参数化写法成本很低没有理由不做。插入一条房客记录的参数化写法string sql INSERT INTO Guests (Name, IdCard, Phone, RoomId, CheckInDate) VALUES (name, idcard, phone, roomId, checkInDate); using (OleDbCommand cmd new OleDbCommand(sql, conn)) { cmd.Parameters.Add(name, OleDbType.VarChar).Value guest.Name; cmd.Parameters.Add(idcard, OleDbType.VarChar).Value guest.IdCard; cmd.Parameters.Add(phone, OleDbType.VarChar).Value guest.Phone; cmd.Parameters.Add(roomId, OleDbType.Integer).Value guest.RoomId; cmd.Parameters.Add(checkInDate, OleDbType.DBTimeStamp).Value guest.CheckInDate; cmd.ExecuteNonQuery(); }逻辑上OleDbCommand 会先把 SQL 发给 Access 引擎参数值单独传递引擎自动处理特殊字符注入问题从源头消失。需要注意一个 OleDb 的怪癖参数名实际上被忽略引擎只按添加顺序匹配name、idcard这些占位符所以参数添加顺序必须和 SQL 里的出现顺序严格一致。这是新人在这个项目里最容易翻车的地方。3.3 从 Access 换到 SQL Server 的改动面换库前先看清改动面。Access 换到 SQL Server 时OleDb 要换成 SqlClient连接字符串重写参数前缀从保持不变但驱动不同字段类型也要对一遍。下面的表列出最常改动的几项项目AccessOleDbSQL ServerSqlClient连接类OleDbConnectionSqlConnection连接串 ProviderMicrosoft.ACE.OLEDB.12.0不需要 Provider参数类型OleDbTypeSqlDbType自动编号需要手动设置IDENTITY(1,1)日期时间DBTimeStampDateTime一个最划算的做法是在 DAL 层加一个连接工厂把创建连接的逻辑集中在一处public static IDbConnection GetConnection() { ConnectionStringSettings cs ConfigurationManager.ConnectionStrings[HotelDB]; if (cs.ProviderName System.Data.OleDb) return new OleDbConnection(cs.ConnectionString); if (cs.ProviderName System.Data.SqlClient) return new SqlConnection(cs.ConnectionString); throw new NotSupportedException(不支持的数据库驱动); }有了这个工厂所有 DAL 方法里的new OleDbConnection(connStr)全部换成DBHelper.GetConnection()换库时只需要改配置文件里的连接串和 providerName。UI 层和 BLL 层完全不用动这就是分层架构的好处。这个工厂方法建议顺手放进资源包的公共类里后面每一步都能用到。4. 功能模块实现登录校验、DataGridView 刷新与入住退房联动这套源码的四大模块不是独立存在而是围绕客房状态联动。登录决定谁能操作系统房客管理维护住客资料入住退房则直接改变客房状态。下面拆三个关键实现点。4.1 用户登录与权限判断事件驱动下的请求流WinForm 是事件驱动模型登录按钮的 Click 事件是入口。用户输入用户名密码点击登录窗体把输入传给 BLLBLL 调用 DAL 查询查询结果决定是进入主窗体还是提示错误。典型代码如下public UserEntity Login(string userName, string password) { string sql SELECT UserId, UserName, RoleName, RealName FROM Users WHERE UserName userName AND Password password; using (OleDbCommand cmd new OleDbCommand(sql, conn)) { cmd.Parameters.Add(userName, OleDbType.VarChar).Value userName; cmd.Parameters.Add(password, OleDbType.VarChar).Value password; object[] row ... // 执行查询并读取第一行 if (row null) return null; return new UserEntity { UserId Convert.ToInt32(row[0]), UserName row[1].ToString(), RoleName row[2].ToString(), RealName row[3].ToString() }; } }这里把校验放在 BLL把 SQL 留在 DAL窗体只需要判断返回值是否为 null。有一点要提醒老项目里密码通常是明文存储登录 SQL 直接比较字符串。这个做法在演示环境没问题但如果系统要上线建议把密码改成哈希存储校验逻辑改为先哈希再比较否则数据库一旦泄露所有账号密码直接暴露。4.2 房客管理DataGridView 绑定、刷新与列显示房客管理界面通常是一张 DataGridView 加一排增删改按钮。最简单的做法是把 DataTable 直接绑定到 DataSource代码只有几行DataTable dt new GuestDAL().GetAllGuests(); dataGridView1.DataSource dt;DataGridView的DataSource一旦赋值会自动根据表结构生成列字段名变成列标题。这里有一个高频问题做完增删改后调用Fill重新填充同一个 DataTable界面却不更新。原因在于 DataSource 未被重新赋值界面还持有旧引用。正确做法是每次操作后重新生成 DataTable 再重绑private void RefreshGrid() { DataTable dt new GuestDAL().GetAllGuests(); dataGridView1.DataSource null; // 先断开旧绑定 dataGridView1.DataSource dt; // 再挂新数据 dataGridView1.ClearSelection(); }另一个常见需求是将数据库里的 0/1 字段显示为复选框。List 里的 bool 属性绑定后会自动变 CheckBox 列但 DataTable 里的 int 0/1 不会。处理方式是在绑定前列类型转换dt.Columns[IsCheckIn].DataType typeof(bool)或者直接改成存储 true/false。如果不想动数据库也可以在 SQL 查询里用 IIF 转布尔Access 下写作IIF(IsCheckIn, True, False) AS IsCheckIn。4.3 入住与退房需要事务保护的两步更新入住登记要做的操作不止一条插入房客记录、把客房编号对应的状态改成占用。两步都是写操作任何一步失败都会留下脏数据。最典型的是房客记录插入成功但客房状态没改后面再开房时这间房仍然显示空闲造成重复入住。用事务把两步包起来是标准解法using (OleDbConnection conn new OleDbConnection(connStr)) { conn.Open(); OleDbTransaction tran conn.BeginTransaction(); try { string insertGuest INSERT INTO Guests (Name, IdCard, Phone, RoomId, CheckInDate) VALUES (name, idcard, phone, roomId, checkInDate); using (OleDbCommand cmd new OleDbCommand(insertGuest, conn, tran)) { // 添加五个参数并赋房客信息 cmd.ExecuteNonQuery(); } string updateRoom UPDATE Rooms SET Status status WHERE RoomId roomId; using (OleDbCommand cmd new OleDbCommand(updateRoom, conn, tran)) { cmd.Parameters.Add(status, OleDbType.VarChar).Value 占用; cmd.Parameters.Add(roomId, OleDbType.Integer).Value roomId; cmd.ExecuteNonQuery(); } tran.Commit(); } catch { tran.Rollback(); throw; } }关键点在于OleDbCommand的构造函数里传入了tran否则命令会在另一个隐式事务里执行当前事务管不到它。Commit只有两条语句都成功才执行任何一条抛异常Rollback会把已经写进去的数据全部撤销。退房的流程对称更新房客离店时间加更新客房状态为清洁中同样需要事务。界面美化方面这套源码的窗体布局可以直接改控件的 BackColor 和 Font 来提升观感但不建议动太多默认属性等业务逻辑跑通了再谈样式。5. 实战避坑数据库连接、界面绑定与编译环境排查记录这套资源在本地跑不通的概率不低大多数问题集中在数据库驱动、路径、绑定刷新和编译版本上。整理几条最常翻车的记录每一条都是现象、原因、解决三段式照着排查能省一晚上。5.1 数据库连不上驱动位数、路径与文件占用现象一程序运行时报「未找到提供程序。该程序可能未正确安装」或者直接弹 OleDbException。同一个连接字符串32 位系统正常64 位系统报错。原因Microsoft Access Database Engine 分为 32 位和 64 位两个版本而 Visual Studio 默认会把调试进程编译成 32 位。如果安装的是 64 位驱动32 位进程当然找不到反之如果程序集被改成 AnyCPU 并运行在 64 位环境下却只装了 32 位驱动也会报同样的错。解决先确认项目目标是 AnyCPU 还是 x86。开发阶段最省事的是把项目平台目标设为 x86同时安装 32 位的 Access 数据库引擎如果必须用 64 位就把目标平台改成 x64并安装 64 位驱动。注意这两个版本不能同时共存切换时要先卸载一个再装另一个。现象二程序能跑但修改数据库文件后重新启动改动全部丢失好像被还原了。原因开发时 Visual Studio 会把项目里的 accdb 复制到 bin\Debug程序读写的是被复制的那份。如果 csproj 里复制模式是「始终复制」每次编译都会用根目录的原文件覆盖输出目录的副本你在调试界面里录入的数据自然被冲掉。解决打开 .csproj把数据库文件的 CopyToOutputDirectory 改为「如果较新则复制」或者手动把数据库放到固定目录并在代码里写绝对路径。调数据时直接改根目录那一个文件别让编译过程覆盖。5.2 DataGridView 不刷新与列显示异常现象三新增房客后调用查询方法界面上的 DataGridView 没有出现新记录但重启程序后数据是有的。原因前面 4.2 分析过重新 Fill 同一个 DataTable 只是改了表格内容DataSource 的引用没变控件根本不知道数据变了。这是 WinForm 里最容易被忽视的刷新陷阱。解决重新执行查询生成新的 DataTable先将 DataSource 置为 null再赋新表。养成把刷新逻辑封装成单独方法的习惯每次增删改完只调RefreshGrid()不要在按钮事件里到处写重复代码。现象四列表里本来该显示复选框或显示文本的列显示成 0 和 1或者是空白的 True/False。原因实体属性是 boolDataTable 列是 intDataGridView 直接按原类型渲染。int 0/1 默认渲染成文本而 bool 属性会自动渲染成 CheckBox。解决在绑定前把 DataTable 的列类型改成 bool如果数据库里存的是文本「是/否」那就先做一次值映射循环把字段转成 bool 再绑定。不要在 DataGridView 默认生成的列上手动加 CheckBox 列那种做法在排序和筛选时会留下显示错位的隐患。5.3 编译与文件依赖版本不一致的迷惑现场现象五打开解决方案报错提示项目文件格式不被支持或者大量类型找不到比如 OleDbConnection 标红。原因.csproj 里的 TargetFrameworkVersion 和本机安装的 .NET Framework 对不上。老项目常是 2.0 或 3.5新电脑默认装 4.7.2 以上高版本 IDE 打不开旧框架项目或者打开后引用全部失效。解决先右键项目看属性里的目标框架再对照本机已安装框架。如果本机没有对应版本最直接的办法是把 TargetFrameworkVersion 改成已装的高版本同时检查 System.Data 引用是否还在。这个操作会带来少量语法层面的兼容问题但这个项目的代码量不大基本都能直接编译过。现象六明明改了源码运行结果还是旧行为甚至报错说 DLL 版本冲突。原因bin 目录里残留了旧版本的 exe 和 dllVisual Studio 增量编译没有完全覆盖运行时加载的是旧二进制。做二次开发时还有人直接把第三方参考代码的 dll 拷贝进来引发了程序集版本冲突。解决解决方案右键「清理解决方案」再重新生成如果还不够直接删掉 bin 和 obj 两个目录后重新生成。养成提交前清理 bin/obj 的习惯别人拿到源码后第一遍编译的成功率会高很多。6. 值得先做的一个动作连接字符串外置与换库验证如果你打算在这套源码上继续加功能第一件事不是改界面而是把连接字符串从代码里挪出来并完整验证一次换库路径。这步做完后面每次切换数据库都不用动代码。在项目里新建或修改 App.config加入连接配置节?xml version1.0 encodingutf-8 ? configuration connectionStrings add nameHotelDB connectionStringProviderMicrosoft.ACE.OLEDB.12.0;Data Source|DataDirectory|Hotel.accdb;Persist Security InfoFalse; providerNameSystem.Data.OleDb / /connectionStrings /configuration代码里通过 ConfigurationManager 读取再结合上一章的工厂方法创建连接string connStr ConfigurationManager.ConnectionStrings[HotelDB].ConnectionString; using (IDbConnection conn DBHelper.GetConnection()) { conn.Open(); // 原有业务代码保持不变 }|DataDirectory|是 AppDomain 的一个占位符默认指向 bin 目录数据库文件放在那里即可。读取连接串前记得在项目里添加对 System.Configuration 的引用否则 ConfigurationManager 不可见。验证换库路径可以按四步走第一步在 Access 里备份业务数据第二步用 SQL Server 的导入向导把所需表导到本地实例第三步修改 App.config把 providerName 改成 System.Data.SqlClientconnectionString 改成 SqlConnection 的格式Data Source 填服务器名Initial Catalog 填数据库名第四步运行系统依次走通登录、新增房客、入住、退房四条链路重点看客房状态是否正确联动。我第一次做这套验证时吃过亏直接改了配置没注意 Access 的自动编号字段到 SQL Server 后变成了 IDENTITY插入房客时显式传主键导致 SQL Server 报错。后来学乖了每次迁移前先把字段类型和默认值列表拉出来对比自动编号列一律不手动赋值。从那以后我每次拿到老项目源码都会强制走一遍「配置外置、换库验证、全链路回归」的流程宁可多花半小时也不让数据库切换在关键时刻翻车。希望帮到你。本文还有配套的精品资源点击获取