
简介这是一份软件工程/数据库方向的课程设计参考文档主题为宾馆房间管理系统围绕SQL Server 2000与C#.NET展示了从零完成数据库应用系统设计的完整路径。文档从课程设计目的与要求出发依次讲解需求分析、数据流图、数据字典、概念结构设计、关系模式转换、逻辑与物理设计、建库建表、约束设置与初始数据填充并给出了客房类型管理、客房信息管理、入住登记、客户查询、结账等模块的C#.NET程序实现思路整体结构规范、步骤完整适合作为课程设计报告范例。资源包大小293KB共含1个doc文档目录清晰、内容精炼便于快速阅读和对照写作。已有139人学习浏览尤其适合正在做宾馆管理或信息管理系统课设的学生可帮助理解如何把业务需求逐步转化为可运行的数据库表结构与应用程序模块。1. 数据库课程设计怎么做一份宾馆房间管理系统报告的拆解逻辑做数据库课程设计的人十个里有八个是先去网上找现成的管理系统报告但这东西最容易翻车的地方反而不在代码而在需求分析写着写着就成了抄。我拆的这份《SQL数据库课程设计宾馆房间管理系统》是一份把软件工程专业课程设计四阶段走全了的报告需求分析、概念设计E-R图、逻辑设计关系模式、物理设计建表与索引最后落到 C#.NET 程序调用。它解决的问题是数据库课程设计到底交什么才不被老师批“不规范”适合软件工程和计算机相关专业正在做课设、或者想补全系统设计规范性的学习者。下载下来以后强烈建议先对照后面第 5 章那几个前后不一致的地方过一遍再决定哪些内容直接采用。2. 需求分析先立住七个功能模块和数据字典的三层拆法课程设计的正文从第 2 章开始才是硬功夫但决定报告质量的是需求分析。原文在这一阶段给出的材料挺全功能需求列表、业务流程图、数据流程图、数据字典。这四个东西的关系可以这样理解——功能列表回答“系统要做什么”流程图回答“数据在谁手上流转”数据字典回答“每一块数据内部长什么样”。把它们串起来后面表结构的雏形已经出来了。2.1 七个功能模块圈定系统边界原文把系统拆成七个功能点用户登录、客房类型管理、客房信息管理、客房查询、客户入住登记、客户查询、客户结算。这几个功能不是平级的先做依赖梳理能少走很多弯路——用户登录是一切的前提系统里应该先有一张用户表客房类型是基础数据没有类型定义客房信息无从谈起客房信息管理依赖类型数据而客户入住登记会反过来修改客房的实际人数状态客户结算是终点结完账要更新客房状态和结算信息。这七项功能全部落到数据库层面需要的核心实体就是客房类型、客房、客户、入住记录、用户。注意这里没有单独的“查询”实体因为查询是数据的读取操作不产生新表只产生视图或 SELECT 语句。这个判断会在后面反复出现。功能模块涉及数据实体落库形态客房类型管理客房类型RoomType 表客房信息管理客房Room 表客户入住登记客户、客房、入住记录Client、Room、ClientRecord 表客户查询客户Client 表或视图客房查询客房Room 表或视图客户结算入住记录更新 ClientRecord 表用户登录用户User 表原文在课程设计环境里写得很清楚操作系统 Windows XP/2000数据库 SQL Server 2000开发语言 C#.NET。这三个约束决定了后面所有技术选型——SQL Server 2000 不支持某些新语法C# 用的是 WinForms环境老但不影响课程设计这一层教学目的。需要提醒的是如果你现在电脑上装的是 SQL Server 2008 以上版本原文里图形界面操作的路径会略有不同但表结构和 SQL 脚本基本可以平移。2.2 数据字典把字段定义、数据结构、数据存储三层拆开数据字典是需求分析里最容易凑字数的部分但也是唯一能直接推导出表结构的部分。原文把数据字典拆成了数据项、数据结构、数据存储、数据流四层这个拆法是对的——课程设计的表结构应该能一一对应回去。看几个关键数据项就能明白房间编号字符型长度 4取值范围 a000-x999含义是为每间客房设置一个代号额定人数整型取值范围 2 到 20这里等于把每个房间能住几个人框死了客房位置可变字符 20取值范围是东南西北这里其实是楼层或区位。数据项定义完数据结构就是把若干数据项组合成业务对象。原文列了三个核心结构客房类型类型名称面积床数人数价钱电视电话空调卫生间、客房房间号类型名楼层人数床数实际人数备注、客户客户编号姓名性别籍贯。这三个结构已经可以直接映射到三张表了。数据存储部分则定义了客户信息和客房信息关键字分别是客户编号、房间号码——这个关键字定义预示着后面这两张表的主键是谁。我在核对数据字典时发现一个有意思的细节原文数据项里给了“床数”的取值范围是 2但客房类型结构里既有床数也有额定人数实际建表时床数应该是 SmallInt 而不该限制成 2。“备注”字段的取值范围竟然也写的 a000-x999这明显是复制房间编号的取值范围时漏改了。这种小错误在课程设计报告里非常普遍但它恰恰说明数据字典不是写完就完而是要和后面的物理表结构逐字段核对。2.3 数据流图先看数据从哪来、到哪去再定外键数据流图在课程设计报告里通常画完就没人看了但实际上它是外键关系的来源。原文里列了两条典型的数据流客房查询数据结构是房间号码客房类型名称来源是 P1.5去向是客房查询模块客户结算信息数据结构是客户号码客房号码入住日期结算日期结算价钱。这两条流透露出两个重要的表关系客房查询这条流说明 Room 表里需要有一个字段带出客房类型名称也就是 Room.RoomTypeName 要作为外键指向客房类型表客户结算信息这条流说明有一张记录表同时持有客户号码和客房号码也就是多对多关系需要的那张中间表。所以数据流图画得不规范没关系只要字段出现在流里最后表里就一定有这个列。用户登录这条流值得单独说登录信息 D1 只包含用户名和密码来源是系统用户去向是 P1.1 登录模块。它的数据字典极其简单但它是整个系统的门卫——没有这张用户表后面所有模块都能被随意访问。有的同学会把用户表和客户表合并这是需求分析阶段就埋下的逻辑错误用户是操作系统的管理员或前台人员客户是住店的旅客两者完全不同。3. 概念设计与逻辑设计E-R 图到关系模式的转换规则需求分析把“业务上有什么数据”讲清楚了概念设计更进一层要回答“这些数据之间是什么关系”。原文这一阶段画了客房类型、客房、客户、客户入住、用户五个实体的 E-R 图并描述了它们的关系。E-R 图在课程设计里是必须出现的东西但比画图更重要的是把关系读对。3.1 实体关系先理清楚一对多、多对多分别怎么处理E-R 图的核心关系原文讲得很直白客房类型和客房是一对多一个类型下面可以挂多间房客户和客房是多对多一个客户可以先后入住多间房一间房也会被不同客户住过。除此之外客户和客户记录之间是一对多客户记录和客房类型之间的关系原文描述得有点绕它想表达的其实是客房类型通过客房这条链路与入住记录产生关联本质上仍然是客房这一层在承上启下。关系梳理得好不好直接决定后面表拆得对不对。对一对多关系处理方式是“一”方的主键放到“多”方的表里做外键也就是 Room 表里放 RoomTypeName。对多对多关系标准做法是拆出一张关系表原文里的客户记录表 ClientRecord 承担的就是这个角色它同时持有 ClientID 和 RoomID 两个外键字段每条记录代表一次真实入住。这里有一个容易被忽略的实体客户入住。原文概念设计把它单独列出来说明作者意识到了“客户”和“入住”是两件事——客户是长期存在的档案入住是一次性的行为。这和后面 ClientRecord 表的设计完全对得上。如果概念设计阶段没分清后面很容易把客户姓名、身份证号直接塞进入住记录表造成大量冗余。3.2 关系模式清单哪些是表、哪些不是表原文逻辑设计部分列出了七项关系模式客房种类、客房信息、客户入住、客户查询、客房查询、客户结算、用户。我对照需求分析发现一个问题客户查询和客房查询本质上是查询动作不是需要物理存储的表。把查询功能列成关系模式是很多课程设计报告的通病——它的表结构定义里也根本没有为这两项单独建表说明作者写逻辑设计时是把功能列表又抄了一遍。原文关系模式实质处理建议客房种类实体建表客房信息实体建表客户入住关系多对多建表作中间表客户查询查询需求建视图或 SELECT客房查询查询需求建视图或 SELECT客户结算操作结果在入住记录中增加字段用户实体建表正确的做法是只有携带数据的实体客房种类、客房、客户入住、用户和关系客户与客房之间的入住记录才转成表查询和结算这类操作需求转成视图、存储过程或者程序里的查询语句。原文里客户结算只给了“结算折扣、结算日期、结算总金额”它更像是结算操作产生的结果展示不需要独立建表放在视图或查询里处理更合理。这一章还有一个更大的问题逻辑设计说要有这些关系模式数据库实现章节却说“建立五张数据表”包括用户表、客户表、客房类型表、客房表、客户入住记录表。五张表和七项关系模式之间的差异恰恰来自那几个查询和结算项。只要理解了“查询不建表”这个原则这份报告里最大的矛盾就解释通了。3.3 主键与外键从业务约束倒推字段选择主键选择是概念设计落地的第一关。原文在 Room 表里用房间号 RoomID 做主键数据类型 Char(4)取值方式符合数据字典里 a000-x999 的客房编号规则——这个选择是对的。如果房间号本身带有字母和位置信息用 Int 自增反而别扭所以 Char(4) 比 Int 更贴合业务。ClientRecord 表的设计更关键用 ClientID RoomID 联合做主键。这代表一条入住记录由“哪个客户住了哪个房间”唯一确定是数据完整性从建表环节就开始保证的典型做法。同时 ClientID 和 RoomID 各自又是外键分别指向客户表和客房表。要注意的是如果同一个客户在同一间房入住多次联合主键就会失效严格一点应该加一个入住序号字段但课程设计阶段联合主键是足够的。外键字段的命名在这里也值得养成习惯外键列名尽量和主表主键保持一致比如 Room 表里的外键就叫 RoomTypeNameClientRecord 表里的外键就叫 ClientID、RoomID。这样看 SQL 连接条件时不需要猜答辩时被问“这个字段为什么叫这个名字”也能答得理直气壮。4. 物理设计与数据库实现建表脚本、索引和视图的落地逻辑设计给的是“表长什么样”物理设计才是真在建库。原文这一章包含索引设计、数据文件位置、建表操作和视图创建属于在 SQL Server 2000 上实际动手的部分。数据文件放在 C:\Program Files\Microsoft SQL Server\MSSQL\Data事务日志文件也放在同一位置这在当时是默认路径课程设计不需要特意改动但要知道数据和日志分离的意义——数据文件存的是表里的记录日志文件存的是每个事务的变更痕迹日志先落盘再写数据这是 SQL Server 保证崩溃可恢复的基础。4.1 索引策略主键索引、外键索引、普通索引各管什么原文的物理设计很克制没有盲目加索引Room 表的 RoomID 是主键建主键时自动生成唯一索引RoomType 的 RoomTypeName 同理ClientRecord 用 ClientID 和 RoomID 联合做主键之外又分别在 ClientID、RoomID 上各建一个普通索引客户入住记录表的外键 RoomID 也建了索引。这套做法的逻辑很清晰主键索引保证唯一性外键索引加速关联查询。实际执行连表查询时如果外键字段没有索引SQL Server 经常要全表扫描——课程设计数据量小不容易暴露但如果表里插了上千条数据客户记录关联客房信息时没索引会明显变慢。这就是慢 SQL 优化在上学阶段就能做的事先把外键索引补齐再去分析语句写法。还需要注意一点不要给每个字段都加索引。索引不是越多越好因为每次 INSERT 和 UPDATE 都要同步维护所有索引写操作的代价会成倍上升。课程设计里常见的情况是往表里灌测试数据时越来越慢十有八九就是索引加多了。4.2 一套能直接跑起来的建表脚本原文的建表是在 SQL Server 2000 的图形界面里右键新建的但课程设计报告里最好有一份 T-SQL 脚本答辩时也可以现场执行。按原文的核心五张表用户表、客户表、客房类型表、客房表、客户入住记录表我一般会写成下面这样CREATE DATABASE HotelManage; GO USE HotelManage; GO -- 客房类型表类型名称做主键配套设施用 bit 表示有/无 CREATE TABLE RoomType ( RoomTypeName varchar(20) NOT NULL PRIMARY KEY, Area smallint NULL, BedNum smallint NULL, PeopleNum smallint NULL, Price money NULL, Television bit NULL DEFAULT 0, Phone bit NULL DEFAULT 0, AirCondition bit NULL DEFAULT 0, Toilet bit NULL DEFAULT 0 ); GO -- 客房表RoomID 是 4 位字符主键RoomTypeName 外键指向客房类型 CREATE TABLE Room ( RoomID char(4) NOT NULL PRIMARY KEY, RoomTypeName varchar(20) NULL, RoomPosition varchar(10) NULL, PeopleNum smallint NULL, BedNum smallint NULL, FactPeopleNum smallint NULL, Remak varchar(20) NULL, CONSTRAINT FK_Room_RoomType FOREIGN KEY (RoomTypeName) REFERENCES RoomType(RoomTypeName) ); GO -- 客户表按数据字典结构补充 CREATE TABLE Client ( ClientID char(16) NOT NULL PRIMARY KEY, ClientName varchar(20) NULL, ClientSex varchar(2) NULL, ClientNative varchar(20) NULL ); GO -- 客户入住记录表ClientID RoomID 联合主键同时又是两条外键 CREATE TABLE ClientRecord ( ClientID char(16) NOT NULL, RoomID char(4) NOT NULL, ClientName varchar(20) NULL, InDate datetime NULL, CheckDate datetime NULL, TotalMoney money NULL, CONSTRAINT PK_ClientRecord PRIMARY KEY (ClientID, RoomID), CONSTRAINT FK_CR_Client FOREIGN KEY (ClientID) REFERENCES Client(ClientID), CONSTRAINT FK_CR_Room FOREIGN KEY (RoomID) REFERENCES Room(RoomID) ); GO -- 用户表User 是 SQL Server 保留字必须用方括号包起来 CREATE TABLE [User] ( UserName varchar(20) NOT NULL PRIMARY KEY, PassWord varchar(20) NULL, UserType varchar(10) NULL ); GO -- 外键字段上的普通索引避免连表查询全表扫描 CREATE INDEX IX_ClientRecord_ClientID ON ClientRecord(ClientID); CREATE INDEX IX_ClientRecord_RoomID ON ClientRecord(RoomID); GO脚本里有几个值得说的点一是 bit 类型不要允许 NULL配合 DEFAULT 0 用逻辑上对应“是否有电视/电话/空调”这类二值开关程序里读出来就是 true/false二是 [User] 这个表名User 是 SQL Server 的系统保留字不加方括号直接建表会报错课程设计里十有八九会在这一步翻车三是 ClientID 用 Char(16)存身份证号时如果末位是 X 会被补空格更稳妥的选择是 varchar(18)这一点第 5 章还会展开。另外原文数据库实现章节写的是“建立五张数据表”但物理设计部分又提到 BookIn、Client、ClientRecord、Custom、Room、RoomType、User 七张表前后对不上。处理办法是以 2.1.5 节里详细定义的表结构为准不要在自定义表名上纠结保证主外键关系一致就好。如果你用的是 SQL Server 2008 以上版本注意把 Varchar 换成 Nvarchar 以支持中文字符SQL Server 2000 时代的排序规则对中文的支持没有现代版本友好。4.3 视图 View1_ClientRecord把四张表 join 成一张大表原文提到创建视图 View1_ClientRecord选的是 Client 表、ClientRecord 表、Room 表、RoomType 表四张表目的是显示客户入住信息的所有相关的客房、客户、客房类型等信息。视图的本质是把复杂的连表查询封装成一个虚拟表界面层查询时只需要 SELECT * FROM View1_ClientRecord不需要关心表之间怎么 join。按原文的意图创建语句大致是CREATE VIEW View1_ClientRecord AS SELECT c.ClientID, c.ClientName, r.RoomID, r.RoomTypeName, cr.InDate, cr.CheckDate, cr.TotalMoney FROM Client c INNER JOIN ClientRecord cr ON c.ClientID cr.ClientID INNER JOIN Room r ON cr.RoomID r.RoomID INNER JOIN RoomType rt ON r.RoomTypeName rt.RoomTypeName; GO视图在课程设计里是加分项但答辩时很可能会被追问“和普通查询有什么区别”。要能说清楚视图不存储数据它只是保存了查询定义每次查询时重新执行它把对多张表的访问统一成对一张虚拟表的访问。别做嵌套视图套娃三层以内否则性能和排查都是问题。视图、存储过程、触发器三者里课程设计最稳妥的组合是“视图 一个存储过程”。视图管查询展示存储过程管结账这类多步操作触发器尽量别碰它会在 INSERT/UPDATE 时自动执行一旦写错数据错误是静默发生的答辩时很难自圆其说。5. 课程设计避坑指南从建库到答辩的五处坑这份报告完整归完整但里面有几个前后不一致的点恰恰是大多数课程设计翻车的重灾区。我把它们单独列出来每一条都是“现象 → 原因 → 解决”的排查思路写报告和改别人的报告都用得上。5.1 关系模式全被当成了物理表现象概念设计里的客户查询、客房查询被当成关系模式列出来后面描述数据库实现时也很容易顺手就把它们建成表。原因把“系统功能”和“数据实体”混为一谈。查询功能不产生数据只是数据的读取入口它不应该有自己的物理存储。解决在报告里明确区分——只有实体和关系才建表查询需求用视图、存储过程或简单的 SELECT 处理。答辩被问“客户查询表存什么数据”时回答不上来就是这个坑。5.2 类型长度和数据字典对不上现象身份证号定义成 Char(16)实际身份证号是 18 位尾号还可能是 XChar(16) 存不下还会截断或补空格房间号明明取值 a000-x999 是 4 位写成 Int 类型就彻底丢了字母信息。原因数据字典里的取值范围和长度定义没有贯穿到物理设计阶段。写数据字典是一拨人建表是另一拨人或者同一个人分两次写中间没对齐。解决建表前把每个字段的长度、类型、取值范围列一张对照表逐项核对。身份证字段用 varchar(18)房间号保持 char(4) 或改为 varchar(4) 更稳妥——注意 char 定长会在尾部补空格程序里读出来要 Trim。我自己处理这类问题时习惯在数据字典里每个字段旁直接标注对应的物理表字段名这样哪里对不上当场就能发现。5.3 主键选了会变的业务字段现象客房类型表用 RoomTypeName 做唯一主键哪天类型名字从“标准双人间”改成“豪华双人间”所有引用它的 Room 表外键和代码里带类型名的 SQL 全要跟着改。原因选主键时图省事直接用自然属性替代了代理主键。自然属性是有业务含义的就容易变而主键应该是稳定且与业务解耦的。解决RoomType 加一个自增 ID 列做代理主键RoomTypeName 保留唯一约束作为业务字段。对课程设计来说如果这个系统就是一次性演示原始设计也能勉强跑通但报告里如果能体现这个概念差距得分会完全不同。5.4 登录模块写成了字符串拼接现象登录判断直接用字符串拼 SQL输入用户名 1 OR 11 就可能绕过验证直接把整个用户表里的第一条记录当登录结果返回。原因课程设计阶段很多人没接触参数化查询为了省事直接拼串。在 SQL Server 2000 那个年代这种注入漏洞在学生的代码里几乎是标配。解决用 SqlParameter 参数化查询。这不仅是安全习惯也是答辩里数据库访问代码最容易被认可的地方。第 6 章给了可以直接抄的写法照着改就行。5.5 死锁与连接泄漏现象多个窗体轮流打开关闭隔一会儿就报连接超时或死锁客户结账时偶尔卡死只能重启程序。原因SqlConnection 没有及时释放更隐蔽的是显式开了事务却没在 finally 里提交或回滚连接池被占满后后续请求全部排队等待。解决C# 里用 using 语句包住连接和命令对象如果显式开了事务一定要在 finally 里做回滚或提交。课程设计数据量小死锁发生率低一旦出现多半都是连接没释放把连接池占满了。排查方法也简单把报错的连接字符串和代码中的 Open 时间点对应一下看哪些连接只 Open 不 Close。6. C#.NET 和 SQL Server 的连接登录模块与连接串的小技巧数据库建完最后一步是把程序接上去。原文的程序部分用 WinForms 实现了三层结构DataLevl 管数据访问、BussinessLevel 管业务、UILevel 管界面主窗体通过按钮调用各个子窗体这个分层本身就比单窗体直接写 SQL 正规得多。我实际操作时会额外坚持两个习惯连接串集中管理、参数化查询顺便处理一个小坑。using System.Data.SqlClient; // 连接串集中放在 DataLevl 的 Connection 类里不要散落在每个窗体 public static class Connection { public static readonly string ConnString Data Source(local);Initial CatalogHotelManage;User IDsa;Password123456; } // 登录判断参数化查询杜绝字符串拼接 public bool CheckLogin(string userName, string password) { string sql SELECT COUNT(1) FROM [User] WHERE UserNameuser AND PassWordpwd; using (SqlConnection conn new SqlConnection(Connection.ConnString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.Add(user, SqlDbType.VarChar, 20).Value userName; cmd.Parameters.Add(pwd, SqlDbType.VarChar, 20).Value password; conn.Open(); int count (int)cmd.ExecuteScalar(); return count 0; } }这段代码的好处有三个参数化查询让 SQL 语句里只认参数从根上避免了拼接字符串带来的注入问题using 语句保证连接用完后自动释放不会出现第 5 章说的连接泄漏连接串集中在静态类里换一台机器部署时只改一处。参数长度写 20 是因为 User 表的 UserName 字段定义就是 varchar(20)SqlParameter 的类型和长度要和表结构完全一致否则索引可能失效。最后一个小技巧RoomID 如果是 char(4) 类型程序里读取后记得调用 Trim() 再去参与字符串拼接或比较。char 是定长类型不足 4 位会自动补空格C# 端拿到的值往往是 a001 而不是 a001直接拿去构造 SQL 条件会把匹配条件变成带尾随空格的值查询结果莫名对不上。遇到这种问题先看字段类型是不是 char再决定要不要 Trim比调试半天 SQL 快得多。课程设计最后那一步永远是回头检查需求分析里的每个功能点是不是都有对应的表和代码数据字典里的每个字段是不是和物理表完全一致程序里的每条 SQL是不是都走参数化。从那以后我每写一个访问数据库的窗体都强制走完这三步再没在验收时因“代码不规范”被卡过。希望帮到你。本文还有配套的精品资源点击获取