
简介这是一份面向C#初学者的宾馆管理系统完整项目适合作为软件工程、通信工程等专业的课程设计或毕业设计参考。项目涵盖用户界面、数据库连接、业务逻辑层和数据访问层演示了Windows Forms控件使用、ADO.NET与SQL Server交互以及面向对象分层设计等关键技能。压缩包共121个文件包含44个.cs源码文件、15个resx与15个resources资源文件、12个DLL依赖库、3个EXE可执行程序另有SQL数据库脚本、DOC实验报告及项目配置与编译缓存文件整体大小9.84MB目录结构清晰便于按模块学习。已有210人学习下载。通过这套资源可以获取可直接运行或二次开发的宾馆管理系统源码、配套数据库脚本和完整实验报告既能对照练习C#窗体应用开发又能借鉴其分层架构与业务处理思路适合用于课程设计、项目复盘或快速搭建同类信息管理系统。1. 为什么一个 C# 宾馆管理系统成了数据库课程设计的“常青树”如果你搜过“C# 宾馆管理系统”大概率会看到一堆带“含数据库文件和实验报告”字样的压缩包。这个选题之所以泛滥不是因为它简单而是因为它刚好卡在课程设计的黄金难度区间C# WinForms 做界面SQL Server 或 Access 做数据层业务上覆盖了房间管理、客人登记、退房结账、预订和统计报表。换句话说它把“数据库增删改查”和“桌面应用开发”两门课的核心考点全部串了起来又不至于复杂到一个人做不完。但我要先泼一盆冷水网上能下载到的绝大多数“宾馆管理系统”源码不是跑不起来就是跑起来之后一改就崩。常见的翻车点包括数据库文件附加失败、连接字符串写死成本机路径、DataGridView 绑定数据后无法联动更新、实验报告里的 E-R 图跟代码里的表结构对不上。所以这篇笔记不打算让你再去碰运气下载而是按“自己动手复现一个能交差、能答辩、能二次修改”的版本来讲。我会把表结构怎么设计、WinForms 界面怎么绑定数据、报告里该画哪些图、以及最容易让老师追问后露馅的细节逐一拆开。适合谁看正在做课程设计或毕业设计的本科/专科生想快速捡起 C# SQL Server 的初学者以及需要给下属或学生布置“小型管理系统”任务的工程师。看完你可以直接照着建库、写代码、跑通流程也能理解每一个按钮背后到底发生了什么——这才是答辩时最值钱的部分。2. 先设计数据库宾馆管理系统的表结构、主键策略与关系约束2.1 六张核心表从房间到账单的完整数据链一个能正常运转的宾馆管理系统至少要覆盖“房间—客人—预订—入住—退房—账单”这条链路。很多网上下载的版本只有三四张表导致入住记录和账单记录混在一张表里退房的时候改状态还得牵着一堆字段跑逻辑一复杂就出错。我一般建议拆成六张表它们各自职责单一联合查询时也很直观。第一张是Rooms房间表字段包括RoomID主键、RoomNo房号、RoomType单人间/标准间/套房、Price门市价、Status空闲/入住/打扫/维修。注意RoomID用自增整数但RoomNo要设唯一约束因为前台报房号时永远输入的是RoomNo不是自增ID。第二张是Guests客人表字段有GuestID主键、GuestName、IDCard、Phone。IDCard身份证号必须加唯一索引这是以后统计“回头客”、按证件查找入住记录的常用入口不加索引后期查询会明显变慢。第三张是Reservations预订表记录ReservationID、GuestID外键、RoomID外键、CheckInDate、CheckOutDate、ReserveDate、Status。这里有个初学者容易踩的坑预订表里的RoomID一定不能是“某类房间”必须绑定到具体的房间号否则预订“标准间A”和“标准间B”在系统里无法区分。第四张是CheckIns入住表字段为CheckInID、GuestID、RoomID、CheckInTime、ExpectedCheckOutDate、Deposit押金。这张表的意义在于分离“预订但未到”和“已经拿钥匙入住”两个状态别混在一张表里加一个字段去硬切换。第五张是CheckOuts退房记录表记录CheckOutID、CheckInID外键、ActualCheckOutTime、TotalCost、PaymentMethod。它本质上是入住表的历史归档退房后数据不能从CheckIns里删否则月底统计营收时你会对着空气查数据。第六张是Consumptions消费明细表字段为ConsumptionID、CheckInID、ItemName、Amount、ConsumptionTime。房费以外的商品、加床、洗衣、赔偿都往这里放退房时把房费跟这张表的汇总相加就是账单总额。2.2 SQL Server 建库脚本约束、外键与默认值一次到位在 SQL Server Management Studio 里新建数据库HotelDB然后把下面的脚本按顺序执行。我故意把外键约束写得很明确这样实验报告里画 E-R 图的时候可以直接照着关系名写“1 : N”和“N : M”不会跟代码里的约束对不上。CREATE DATABASE HotelDB; GO USE HotelDB; GO CREATE TABLE Rooms ( RoomID INT IDENTITY(1,1) PRIMARY KEY, RoomNo NVARCHAR(10) NOT NULL UNIQUE, RoomType NVARCHAR(20) NOT NULL, Price DECIMAL(10,2) NOT NULL DEFAULT 0, Status NVARCHAR(10) NOT NULL DEFAULT N空闲 ); CREATE TABLE Guests ( GuestID INT IDENTITY(1,1) PRIMARY KEY, GuestName NVARCHAR(20) NOT NULL, IDCard NVARCHAR(18) NOT NULL UNIQUE, Phone NVARCHAR(11) NOT NULL ); CREATE TABLE Reservations ( ReservationID INT IDENTITY(1,1) PRIMARY KEY, GuestID INT NOT NULL, RoomID INT NOT NULL, CheckInDate DATE NOT NULL, CheckOutDate DATE NOT NULL, ReserveDate DATETIME NOT NULL DEFAULT GETDATE(), Status NVARCHAR(10) NOT NULL DEFAULT N有效, CONSTRAINT FK_Reservations_Guests FOREIGN KEY (GuestID) REFERENCES Guests(GuestID), CONSTRAINT FK_Reservations_Rooms FOREIGN KEY (RoomID) REFERENCES Rooms(RoomID) ); CREATE TABLE CheckIns ( CheckInID INT IDENTITY(1,1) PRIMARY KEY, GuestID INT NOT NULL, RoomID INT NOT NULL, CheckInTime DATETIME NOT NULL DEFAULT GETDATE(), ExpectedCheckOutDate DATE NOT NULL, Deposit DECIMAL(10,2) NOT NULL DEFAULT 0, Status NVARCHAR(10) NOT NULL DEFAULT N在住, CONSTRAINT FK_CheckIns_Guests FOREIGN KEY (GuestID) REFERENCES Guests(GuestID), CONSTRAINT FK_CheckIns_Rooms FOREIGN KEY (RoomID) REFERENCES Rooms(RoomID) ); CREATE TABLE CheckOuts ( CheckOutID INT IDENTITY(1,1) PRIMARY KEY, CheckInID INT NOT NULL UNIQUE, ActualCheckOutTime DATETIME NOT NULL DEFAULT GETDATE(), TotalCost DECIMAL(10,2) NOT NULL, PaymentMethod NVARCHAR(20) NOT NULL, CONSTRAINT FK_CheckOuts_CheckIns FOREIGN KEY (CheckInID) REFERENCES CheckIns(CheckInID) ); CREATE TABLE Consumptions ( ConsumptionID INT IDENTITY(1,1) PRIMARY KEY, CheckInID INT NOT NULL, ItemName NVARCHAR(50) NOT NULL, Amount DECIMAL(10,2) NOT NULL, ConsumptionTime DATETIME NOT NULL DEFAULT GETDATE(), CONSTRAINT FK_Consumptions_CheckIns FOREIGN KEY (CheckInID) REFERENCES CheckIns(CheckInID) );脚本的逻辑说明IDENTITY(1,1)是自增主键避免手工分配 ID 造成冲突NVARCHAR用来存中文避免VARCHAR在繁体或生僻字上出现乱码DEFAULT GETDATE()让预订时间和入住时间自动取服务器当前时间后台代码里少写一行赋值。CheckOuts.CheckInID加UNIQUE是因为一次入住只能对应一次退房记录这是很多开源版本没想到的约束。参数说明如果你用的是 LocalDB 或 Express 版本把脚本里的NVARCHAR(10)等长度按实际需求调整没有影响但主键字段类型不要改INT就够用。房费字段用DECIMAL(10,2)支持最大 99999999.99 元对小型宾馆来说绰绰有余。Status字段用中文字符串直观但不够规范如果要做得更专业可以用TINYINT存 0/1/2再用枚举去映射。3. 用 ADO.NET 封装数据访问层连接字符串、增删改查与参数化查询3.1 为什么选 ADO.NET 而不是 EF课程设计的“可控性”比“开发速度”更重要现在很多教程一上来就推 Entity FrameworkEF说不用写 SQL直接操作实体对象。但对“小型宾馆管理系统”这个场景我并不推荐。原因有三条第一课程设计的评分点往往包含“SQL 语句写得对不对、事务有没有用、视图或者存储过程有没有体现”用 EF 会自动生成 SQL老师一问“你的 Inner Join 怎么写”你会当场卡壳第二EF 的连接配置和迁移机制对新手来说是一个黑匣子NUget 包一多项目反而更容易跑不起来第三WinForms 搭配 ADO.NET 的DataTable可以直接作为DataGridView的DataSource代码量一点都不多而且每一行逻辑都摊在明面上。所以这套方案里我选用SqlConnection、SqlCommand、SqlDataAdapter和DataTable这套经典组合。它的核心思路是写一个DBHelper类把打开连接、执行查询、返回 DataTable 这些重复操作封装成方法界面层不直接接触连接对象后续换数据库比如从 SQL Server 换到 Access时只改一个类。3.2 DBHelper 封装连接字符串、ExecuteQuery 与 ExecuteNonQuery先建一个DBHelper.cs文件代码如下。注意连接字符串里的Data Source要改成你自己的机器名或实例名AttachDbFilename这种方式可以让你把.mdf数据库文件放在项目目录下提交作业时连库带代码一起打包老师打开就能跑。using System; using System.Data; using System.Data.SqlClient; namespace HotelManagement.Utils { public class DBHelper { private static readonly string connectionString Data Source(LocalDB)\MSSQLLocalDB;AttachDbFilename|DataDirectory|\HotelDB.mdf;Integrated SecurityTrue; public static DataTable ExecuteQuery(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(connectionString)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); SqlDataAdapter adapter new SqlDataAdapter(cmd); DataTable dt new DataTable(); adapter.Fill(dt); return dt; } } } public static int ExecuteNonQuery(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(connectionString)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteNonQuery(); } } } public static object ExecuteScalar(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(connectionString)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteScalar(); } } } } }逻辑说明ExecuteQuery用于执行 SELECT返回填充好的DataTable直接可以绑定给DataGridViewExecuteNonQuery用于 INSERT / UPDATE / DELETE返回受影响行数用来判断操作成功与否ExecuteScalar用于执行COUNT(*)、SUM(金额)这类返回单值的统计查询。三个方法都用了using块确保连接和命令对象用完自动释放避免前台频繁操作后连接池耗尽。params SqlParameter[]是为了让调用方可以传任意个数的参数后面查询时不需要手动拼 SQL。参数化说明所有 SQL 都用参数名占位再通过SqlParameter传值。这样做有两个实际好处一是防止 SQL 注入比如客人姓名里输入; DROP TABLE Guests;--时不会真的删表二是日期、金额这类类型值不会因为 ToString 的格式差异而出错。永远不要用字符串拼接去组装 SQL这是 C# 上位机开发和数据库应用里最不该省的事。3.3 房态查询与入住登记两段可直接抄的业务代码先看房态查询。前台需要按“空闲 / 入住 / 打扫”筛选房间所以我把 SQL 写成了带可选条件的查询。public DataTable GetRoomsByStatus(string status) { string sql SELECT RoomNo AS 房号, RoomType AS 类型, Price AS 价格, Status AS 状态 FROM Rooms WHERE (Status N全部 OR Status Status); return DBHelper.ExecuteQuery(sql, new SqlParameter(Status, status)); }逻辑说明Status N全部是一个技巧当前台下拉框选择“全部”时这个条件恒为真相当于查询所有房间选择具体状态时再走第二个条件。这样用一个存储过程式的 SQL 就能覆盖两种场景不用在 C# 里写 if 分支去切换 SQL 字符串。注意N全部里的前缀N是 SQL Server 识别 Unicode 字符串的标记不加的话中文比较偶尔会出现匹配不上的情况。然后是入住登记。这里涉及事务既要往CheckIns表插入一条记录又要修改Rooms表的状态为“入住”两步必须同时成功或同时失败。public bool CheckIn(int guestId, int roomId, DateTime expectedDate, decimal deposit) { string sqlCheckIn INSERT INTO CheckIns (GuestID, RoomID, ExpectedCheckOutDate, Deposit) VALUES (GuestID, RoomID, ExpectedDate, Deposit); string sqlUpdateRoom UPDATE Rooms SET Status N入住 WHERE RoomID RoomID; using (SqlConnection conn new SqlConnection(DBHelper.ConnectionString)) { conn.Open(); SqlTransaction transaction conn.BeginTransaction(); try { using (SqlCommand cmd new SqlCommand(sqlCheckIn, conn, transaction)) { cmd.Parameters.AddWithValue(GuestID, guestId); cmd.Parameters.AddWithValue(RoomID, roomId); cmd.Parameters.AddWithValue(ExpectedDate, expectedDate); cmd.Parameters.AddWithValue(Deposit, deposit); cmd.ExecuteNonQuery(); } using (SqlCommand cmd new SqlCommand(sqlUpdateRoom, conn, transaction)) { cmd.Parameters.AddWithValue(RoomID, roomId); cmd.ExecuteNonQuery(); } transaction.Commit(); return true; } catch { transaction.Rollback(); return false; } } }这个方法的边界说明guestId和roomId必须是数据库里真实存在的外键值所以在调用这个方法前前端应该已经从Guests和Rooms表里选好了记录而不是让用户手工输入 ID。AddWithValue虽然方便但有一个坑当传入null时它无法推断出正确数据库类型所以可空字段优先用DBNull.Value显式赋值。这里deposit如果允许为 0则不需要担心。事务的BeginTransaction()必须在Open()之后调用否则会抛“连接未就绪”异常这是一个经常出现的低级错误。4. 把界面做出来WinForms 布局、DataGridView 绑定与联动查询4.1 系统功能分区从登录窗口到主窗体的菜单结构宾馆管理系统的界面不需要花哨但功能区要清晰。我建议主窗体用MenuStrip做顶层导航分成“前台操作”“客房管理”“预订管理”“统计报表”“系统管理”五个菜单。前台操作下挂“入住登记”“退房结账”“换房操作”客房管理下挂“房间状态”“房价设置”统计报表下挂“入住率报表”“营收统计”。这样的结构跟实验报告里的“系统功能结构图”完全对应画图的时候直接照着菜单画即可。登录窗口不是必需项但加上会显著提升完整度。用一个LoginForm做简单校验用户名和密码存到Users表字段UserID,UserName,PasswordHash校验时不要明文比对用SHA256对输入密码做哈希再跟数据库里的哈希值比较。如果嫌麻烦至少也要用 MD5哪怕老师知道 MD5 可碰撞也比明文强这能让报告里“安全设计”一节有话可写。4.2 DataGridView 绑定 DataTable刷新数据与选中行取值主窗体加载时把Rooms表通过DataGridView展示出来。关键代码逻辑很简单但有两个实战细节必须掌握。private void LoadRoomData() { DataTable dt DBHelper.ExecuteQuery(SELECT RoomID, RoomNo, RoomType, Price, Status FROM Rooms); dgvRooms.DataSource dt; dgvRooms.Columns[RoomID].Visible false; dgvRooms.Columns[RoomNo].HeaderText 房号; dgvRooms.Columns[RoomType].HeaderText 房型; dgvRooms.Columns[Price].HeaderText 门市价; dgvRooms.Columns[Status].HeaderText 状态; } private void dgvRooms_SelectionChanged(object sender, EventArgs e) { if (dgvRooms.CurrentRow null) return; string roomNo dgvRooms.CurrentRow.Cells[RoomNo].Value.ToString(); string status dgvRooms.CurrentRow.Cells[Status].Value.ToString(); lblRoomInfo.Text $房号{roomNo}状态{status}; }逻辑说明DataSource dt是整表绑定后续调用LoadRoomData()重新查一次库就能刷新界面实现“增删改后视图同步更新”。Columns[RoomID].Visible false是为了隐藏自增主键它只作为代码里的定位依据不展示给前台。SelectionChanged事件在鼠标点击切换行时触发通过CurrentRow.Cells[列名].Value取出当前行的任意字段这是做“选中一间房再去办入住”的必备操作。参数说明dgvRooms的AutoSizeColumnsMode建议设为Fill这样列宽能自动撑满窗口SelectionMode设为FullRowSelect用户点任意单元格都会选中整行减少误操作。另外DataGridView默认允许用户直接在单元格里编辑如果你不希望前台改房号要把列或整个网格的ReadOnly设为true否则改了界面值但数据库没变会造成前后台数据不一致的假象。4.3 退房结账的联动房间、入住单和消费记录三表汇总退房窗口是整套系统里最容易出 bug 的地方。正确的流程是前台选中一条正在入住的状态记录系统自动根据CheckInID查出该客人的所有消费明细然后计算房费入住天数乘以房间单价加上消费总额减去押金得到应付金额。点击“结账”按钮后插入CheckOuts记录把CheckIns.Status改为“已退房”再把Rooms.Status改为“空闲”。private decimal CalcTotalCost(int checkInId, int roomId, DateTime actualCheckOut) { DataTable room DBHelper.ExecuteQuery(SELECT Price FROM Rooms WHERE RoomIDrid, new SqlParameter(rid, roomId)); DataTable checkIn DBHelper.ExecuteQuery(SELECT CheckInTime, ExpectedCheckOutDate, Deposit FROM CheckIns WHERE CheckInIDcid, new SqlParameter(cid, checkInId)); DataTable consumptions DBHelper.ExecuteQuery(SELECT SUM(Amount) AS TotalConsume FROM Consumptions WHERE CheckInIDcid, new SqlParameter(cid, checkInId)); decimal roomPrice Convert.ToDecimal(room.Rows[0][Price]); int days (actualCheckOut.Date - Convert.ToDateTime(checkIn.Rows[0][CheckInTime]).Date).Days; if (days 0) days 1; decimal roomCost roomPrice * days; decimal consumeCost consumptions.Rows[0][TotalConsume] DBNull.Value ? 0 : Convert.ToDecimal(consumptions.Rows[0][TotalConsume]); decimal deposit Convert.ToDecimal(checkIn.Rows[0][Deposit]); return roomCost consumeCost - deposit; }这一段的价值在于边界处理。days的计算使用DateTime.Date去掉了时间部分避免“凌晨 1 点入住、早上 8 点退房”被算成 0 天这是一个非常实际的踩坑点。SUM(Amount)在没有明细记录时返回NULL直接Convert.ToDecimal会崩所以必须判断DBNull.Value这是所有聚合函数查询的通用处理方式。房费的计费规则各家宾馆不一样有的是“下午 2 点前退房不算当天房费”你可以在报告里说明当前实现是“按自然日计算”再把规则做成可选参数。5. 必修避坑数据库附加失败、连接字符串失效、DataGridView 刷新与并发报错5.1 现象AttachDbFilename路径一换电脑就报“文件正由另一进程使用”很多同学在自己机器上跑得好好的把整个项目拷到 U 盘里换台电脑打开系统就报 “Unable to attach database” 或 “The database file is in use”。原因是|DataDirectory|这个关键字在代码运行时会解析到项目的bin\Debug目录而自带的.mdf文件每次生成时会被 SQL Server 服务进程锁定。如果你用任务管理器杀掉sqlservr.exe再复制文件反而会让数据库文件处于不稳定状态。解决不要用AttachDbFilename指向bin下的.mdf改成直接连接到 SQL Server 实例里已附加的数据库。也就是先用 SSMS 把HotelDB.mdf附加到本地实例然后把连接字符串写成Data Source.;Initial CatalogHotelDB;Integrated SecurityTrue。这样项目里不需要带.mdf老师打开时只要先附加一次数据库即可。如果你一定要携带数据库文件至少把Copy to Output Directory设为Copy if newer并且每次关闭 VS 后再复制文件。5.2 现象DataGridView 点刷新按钮后数据确实变了但界面显示的还是旧数据原因是DataGridView.DataSource被赋值为一个新的DataTable后如果同一个DataTable对象的Rows被间接修改过控件不会自动感知。更隐蔽的情况是你调用了dt.Rows.Clear()但没有重新赋值DataSource。解决刷新时直接执行两行代码dgvRooms.DataSource null; dgvRooms.DataSource dt;强制控件重建绑定源。另一种做法是使用BindingSource组件先bindingSource.DataSource dt再把dgvRooms.DataSource bindingSource之后只需要bindingSource.ResetBindings(false)就能刷新这种方案的性能更好推荐熟悉BindingSource的人使用。如果你只是改了单元格的值还需要调用dgvRooms.EndEdit()把界面上的修改提交到数据源否则切行后修改会丢失。5.3 现象两个前台窗口同时操作同一房间后提交的人把先提交的人覆盖了这是一个典型的并发更新问题。两个人同时看上一间房A 先点了入住B 的界面上还显示“空闲”B 再点入住时系统又插入了一条新的入住记录房间被重复分配。解决在房间状态更新时加入乐观并发控制也就是 SQL 里带上版本条件。比如把更新语句写成UPDATE Rooms SET StatusN入住 WHERE RoomIDRoomID AND StatusN空闲如果ExecuteNonQuery返回 0说明该房间已经不是空闲状态此时提示“房间刚被其他窗口办理入住请刷新重试”。同理退房时也要校验CheckIns.Status是否为在住。这是一种不需要数据库行锁和事务隔离级别的轻量方案课程设计里提到“数据库并发控制”时可以直接拿这段做例子。5.4 现象实验报告里的 E-R 图和实际建表脚本对不上很多网上下载的源程序报告画了“房间—客人”的多对多关系但代码里根本没有中间表或者报告里的字段叫Room_Price实际数据库里叫Price答辩时老师照着报告提问就会露馅。这是最容易补救的坑。解决报告写完必做一次“脚本回滚验证”。打开 SSMS新建一个空数据库把建表脚本从头执行一遍然后用SELECT * FROM Rooms等语句核对表名、字段名、约束名。E-R 图里的每一个实体、每一条连线、每一个联系类型都要能在INFORMATION_SCHEMA.TABLES和INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS里查到。如果你在报告里写了“支持多房型预订”那么Consumptions和CheckIns的关系务必在图上标清楚。宁可少画一条关系也别画一条代码里不存在的关系。5.5 现象程序在自己的机器上能跑但老师双击.exe报“初始化失败”或“数据库连接失败”原因是运行环境不同。你用了(LocalDB)\MSSQLLocalDB老师机器上没有装 LocalDB或者装了 SQL Server 2012 Express但实例名是SQLEXPRESS。解决在DBHelper里不要写死唯一连接字符串改成先探测可用实例。常见做法是在程序启动时读取App.config里的连接配置然后在注释里写明需要安装 SQL Server Express 或 LocalDB。如果老师的环境只有 Access那你的方案从一开始就要换成OleDbConnection.accdb文件这也是很多下载版为什么选 Access 的原因——它不需要数据库实例文件拷过去就能用。说实话Access 在课程设计里更稳妥但 C# 连接 Access 时 SQL 语法会有差异例如分页查询用TOP而不是OFFSET FETCH这一点如果你换了数据源务必逐条改写测试。6. 实验报告怎么写才不被追问卡住E-R 图、数据流图与关键代码的答辩话术报告是这门课程设计的另一半分数而且往往比代码占比更高。老师时间有限不可能把你几百行代码读完他们通常只做三件事翻目录看结构完整性、看 E-R 图和表结构是否自洽、挑一段“你觉得最复杂的功能”让你现场讲。因此报告的落笔重点不是堆字数而是让每一个图都能追溯到代码。E-R 图我建议画实体时标注出主码比如“房间房间号 PK, 房型, 价格, 状态”联系上标注 1 : N。如果画了预订实体和入住实体两个独立实体请不要省略它们之间的外键连线。数据流图按“前台登记入住—修改房间状态—生成消费记录—办理退房—生成账单”这条主线画图的层级不要超过三层否则看着就像复制粘贴来的。关键代码部分不要贴整个DBHelper而是挑一段带事务的入住登记和带聚合函数的退房结算在代码上方写“设计思路”在代码下方写“边界处理与异常说明”。比如事务那段你要能说出“为什么Commit之前检查两行受影响行数”——防止用户把同一个房间重复入住。这些细节比“系统健壮、界面友好”这种空话有用一百倍。报告中还可以加一张“测试用例表”列三到五个典型场景正常入住退房、重复预订同一房间、退房时存在消费明细、统计报表为空。每个用例写清输入、预期输出、实际输出老师看到这张表基本就不会再追着你问功能边界了。最后说一句我自己的习惯每次做完这种课程设计我都要把数据库脚本从头执行一遍再把程序的所有按钮点一遍最后把报告里的 E-R 图跟INFORMATION_SCHEMA里的约束比对一次。这套流程能帮你拦截掉 80% 的翻车比临时抱佛脚背话术有用得多。希望帮到你。本文还有配套的精品资源点击获取