简介一套基于C#与MySQL数据库的房屋租赁管理系统课程设计完整资料面向计算机相关专业在校生及需要快速搭建毕设或课设演示的开发者。系统覆盖登录与权限、房屋信息、租赁合同、账单财务、用户管理等常见模块并提供roomrentsystem.sql数据库脚本、设计报告文档及ER图、数据流图便于理解整体架构和业务流转。资源共71个文件主要包括C#源码文件.cs、窗体资源文件.resx、可执行程序.exe、Word设计文档.doc/.docx及SQL脚本等压缩包大小约12.83MB结构清晰下载后可对照源码与报告进行二次开发。已有303人浏览学习功能经过验证适合作为课程设计、大作业或初期项目立项参考对于有一定基础的学习者也可基于现有模块扩展预约、统计等功能。使用时建议将项目路径重命名为英文后再运行。1. 一套 C# 房屋租赁管理系统课设包先跑通再谈改代码从“Mysql数据库课设基于C#实现的房屋租赁管理系统源码数据库设计报告.zip”这类课设包里拿到的通常是一份 Visual Studio 解决方案、一个 .sql 脚本和一份 Word 设计报告。大多数人在这一步卡住的不是源码看不懂而是数据库导不进去、C# 连不上 MySQL、一跑起来全是红叉。这套课设的价值在于它同时牵扯 MySQL 建表、C# 分层写业务、WinForms 界面绑定、数据库设计报告四个环节正好覆盖一门数据库课设要交的全部材料。适合正在赶课设的学生也适合想用最短路径把 C# 与 MySQL 联调过一次的开发者。下面从数据库脚本的改造讲起到 C# 事务、界面绑定、报告组织和排错一条线把它变成你能在答辩现场讲明白的完整项目。2. 把数据库脚本跑成自己的东西6 张表、视图与存储过程怎么改打开 zip 里的 .sql 文件第一件事不是双击运行而是先看它建了哪几张表。常见的做法是把自己代入小区管理员视角有人租房子要记租客信息房子本身要有房源档案签约要有合同表每个月催租要有账单流水再加一张登录用的用户表。核心就是这几张表字段名和你下到的包可能不一样但结构基本逃不出这个范围。MySQL 8.0 的安装教程网上已经很全真正影响 C# 连接的就三个点服务有没有启动、端口是不是 3306、认证插件是什么。装好后用 Workbench 连一次本地连接能进去再跑脚本省得后面 C# 报错时分不清是代码问题还是环境问题。2.1 在 Workbench 里把 SQL 脚本完整跑一遍用 Workbench 打开 .sql 文件后不要直接鼠标选中全表复制那样容易漏掉开头的 DROP 和 USE 语句。正确做法是 File - Open SQL Script 打开整个文件然后按 CtrlShiftEnter 执行整个脚本。执行完成后左侧 SCHEMAS 右键刷新看到表结构就说明库建好了。如果包里的脚本结构和你的预期差得远或者建表语句里缺了外键、注释可以直接用下面这套重建。这份脚本把课设常用的五张核心表一次建好还带了演示数据能让 C# 程序启动后立刻有东西可查。CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rental; DROP TABLE IF EXISTS t_bill; DROP TABLE IF EXISTS t_contract; DROP TABLE IF EXISTS t_tenant; DROP TABLE IF EXISTS t_house; DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50) COMMENT 真实姓名 ) ENGINEInnoDB; CREATE TABLE t_house ( house_id INT PRIMARY KEY AUTO_INCREMENT, house_no VARCHAR(20) NOT NULL COMMENT 房号如 A-1-101, area DECIMAL(6,2) COMMENT 面积平方米, rent DECIMAL(8,2) NOT NULL COMMENT 月租金, house_status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1已出租 2维修, remark VARCHAR(200) ) ENGINEInnoDB; CREATE TABLE t_tenant ( tenant_id INT PRIMARY KEY AUTO_INCREMENT, tenant_name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL, phone VARCHAR(20) ) ENGINEInnoDB; CREATE TABLE t_contract ( contract_id INT PRIMARY KEY AUTO_INCREMENT, contract_no VARCHAR(30) NOT NULL UNIQUE COMMENT 合同编号, house_id INT NOT NULL, tenant_id INT NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, monthly_rent DECIMAL(8,2) NOT NULL COMMENT 签约月租金, deposit DECIMAL(8,2) NOT NULL COMMENT 押金, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (house_id) REFERENCES t_house(house_id), FOREIGN KEY (tenant_id) REFERENCES t_tenant(tenant_id) ) ENGINEInnoDB; CREATE TABLE t_bill ( bill_id INT PRIMARY KEY AUTO_INCREMENT, contract_id INT NOT NULL, bill_month CHAR(7) NOT NULL COMMENT 账期格式 2025-06, amount DECIMAL(8,2) NOT NULL COMMENT 应收金额, pay_status TINYINT DEFAULT 0 COMMENT 0未收 1已收, pay_time DATETIME DEFAULT NULL, FOREIGN KEY (contract_id) REFERENCES t_contract(contract_id) ) ENGINEInnoDB; INSERT INTO t_user (username, password, real_name) VALUES (admin, 123456, 管理员); INSERT INTO t_house (house_no, area, rent, house_status) VALUES (A-1-101, 62.50, 1800.00, 1), (A-1-102, 62.50, 1800.00, 0), (B-2-301, 85.00, 2600.00, 0), (C-1-501, 45.00, 1200.00, 2); INSERT INTO t_tenant (tenant_name, id_card, phone) VALUES (张三, 110101199001011234, 13800000001), (李四, 110101199202022345, 13800000002); INSERT INTO t_contract (contract_no, house_id, tenant_id, start_date, end_date, monthly_rent, deposit) VALUES (HT-2025-001, 1, 1, 2025-01-01, 2025-12-31, 1800.00, 3600.00); INSERT INTO t_bill (contract_id, bill_month, amount, pay_status, pay_time) VALUES (1, 2025-01, 1800.00, 1, 2025-01-03 10:00:00), (1, 2025-02, 1800.00, 1, 2025-02-02 09:30:00);这段脚本的关键点在于 DROP 顺序必须从子表往父表删t_bill 引用了 t_contractt_contract 引用了 t_house 和 t_tenant如果先删父表会报外键约束错误。演示数据特意把 A-1-101 设为已出租与后面的合同、账单数据形成一条完整链路方便验证 C# 里的关联查询。外键都用 RESTRICT 默认行为目的是让代码在删除时走业务校验而不是让数据库静默级联删除。2.2 建表里的 3 个关键设计写报告时直接抄第一个是存储引擎必须用 InnoDB。MyISAM 不支持事务和外键而这个系统的合同登记要同时写合同、账单、改房源状态三件事没有事务就会出现在中途失败时数据一半成功一半失败的情况。课设报告的需求分析里写一句“采用 InnoDB 引擎以支持事务与外键约束”这一分就拿到了。第二个是金额字段用 DECIMAL 而不是 FLOAT 或 DOUBLE。房租、押金这类金额数据用浮点类型存会在小数点后产生误差DECIMAL(8,2) 表示最多 8 位有效数字、2 位小数正好覆盖十万以内的月租金。账单表里冗余存储了 amount 字段这个冗余不是设计失误而是刻意保留“当时应收多少”的快照因为以后改了合同租金历史账单不应该跟着变。第三个是房源的 house_status 用 TINYINT 数字表示状态而不是存中文。0、1、2 三个值对应空闲、已出租、维修这样查询空置房源时直接WHERE house_status 0比字符串比较更快也避免同义不同字“空置”和“空闲”造成的数据混乱。界面显示时再做一次状态映射即可。2.3 视图和存储过程提前写好C# 里少拼 100 行 SQL视图的价值在于把复杂查询封装在数据库层。比如“空置房源列表”和“每月收租统计”这两类高频查询写成视图后 C# 里只需要一条SELECT * FROM v_house_vacancy不用每次都在代码里重写 JOIN 和 GROUP BY。CREATE VIEW v_house_vacancy AS SELECT house_no, area, rent, remark FROM t_house WHERE house_status 0 ORDER BY rent ASC; CREATE VIEW v_monthly_income AS SELECT bill_month, SUM(amount) AS total_amount, SUM(CASE WHEN pay_status 1 THEN 1 ELSE 0 END) AS paid_count, COUNT(*) AS total_count FROM t_bill GROUP BY bill_month;两个视图对应了课设里最常要求的功能点一个是“查询空置房源”另一个是“按月份统计租金收入”。ORDER BY rent ASC是 MySQL 排序操作低租金在前方便筛选性价比房源。视图不占额外存储只是保存了 SQL 定义执行时仍走底层表。存储过程可以演示“生成月度账单”的逻辑。这里用 DELIMITER 改变语句结束符因为过程体内部的分号会被 MySQL 误判为整段 SQL 的结束。DELIMITER // DROP PROCEDURE IF EXISTS sp_generate_bill// CREATE PROCEDURE sp_generate_bill( IN p_contract_id INT, IN p_bill_month CHAR(7) ) BEGIN DECLARE v_rent DECIMAL(8,2); DECLARE v_count INT; SELECT COUNT(*) INTO v_count FROM t_bill WHERE contract_id p_contract_id AND bill_month p_bill_month; IF v_count 0 THEN SELECT monthly_rent INTO v_rent FROM t_contract WHERE contract_id p_contract_id; INSERT INTO t_bill(contract_id, bill_month, amount) VALUES (p_contract_id, p_bill_month, v_rent); END IF; END// DELIMITER ;调用方式为CALL sp_generate_bill(1, 2025-03)。存储过程做了防重复生成同一合同同一个月只允许生成一张账单避免界面按钮被连点两下产生两条数据。IF v_count 0是典型的业务规则下沉答辩时能讲清楚这条逻辑比单纯贴代码更加分。3. 用 C# 把 SQL 变成界面功能DAL 封装、事务签约与 DataGridView 绑定数据库脚本跑通后接下来要处理的就是 zip 里的 C# 工程。常见做法是打开 .sln 先看项目结构正规一点的课设会分成 Model、DAL、BLL、UI 四层如果原包把所有代码都写在 Form1.cs 里那这套课设的质量就要打个折扣。分层的理由很直白界面代码和数据库代码混在一起时你改一个按钮事件都可能误伤查询逻辑而分层后每个类只干一件事排错范围被大大缩小。你还需要确认一件事工程引用的 MySQL 驱动是哪个版本。旧版 MySql.Data 连 MySQL 8.0 会报认证插件不支持这是最典型的坑后面第 5 章会细说。驱动版本对了再开始读代码能省一小时。3.1 Model 层先定义数据实体属性注释直接用于报告数据字典Model 层就是一张表一个类类的属性对应表的字段。下面以 House 为例注意每个属性上面的 XML 文档注释写报告的数据字典时可以直接从代码里复制整理不用重新敲。namespace HouseRental.Model { /// summary /// 房源实体对应表 t_house /// /summary public class House { /// summary房源ID主键/summary public int HouseId { get; set; } /// summary房号如 A-1-101/summary public string HouseNo { get; set; } /// summary面积平方米/summary public decimal Area { get; set; } /// summary月租金/summary public decimal Rent { get; set; } /// summary0空闲 1已出租 2维修/summary public int HouseStatus { get; set; } } }C# 的属性是带着 get/set 的字段封装和 Java 的 getter/setter 效果类似但写法更紧凑。HouseStatus在界面上要翻译成“空闲/已出租/维修”字符串这种事放在 UI 层做不要写进 Model保持实体类干净。3.2 DbHelper 封装连接与读写连接字符串别写死在代码里每个数据库操作都要“打开连接 - 创建命令 - 执行 - 关闭连接”这四步重复出现在所有窗口里。把它们收进一个静态类以后每加一个查询只需要写 SQL 本身。using System; using System.Data; using MySql.Data.MySqlClient; namespace HouseRental.DAL { public static class DbHelper { // 课设阶段写死方便调试正式作业请放到 App.config 的 connectionStrings private static readonly string ConnStr Server127.0.0.1;Port3306;Databasehouse_rental;Uidroot;Pwd123456;CharSetutf8mb4;; public static DataTable Query(string sql, params MySqlParameter[] paras) { using (MySqlConnection conn new MySqlConnection(ConnStr)) { conn.Open(); using (MySqlCommand cmd new MySqlCommand(sql, conn)) { if (paras ! null) cmd.Parameters.AddRange(paras); MySqlDataAdapter da new MySqlDataAdapter(cmd); DataTable dt new DataTable(); da.Fill(dt); return dt; } } } public static int Execute(string sql, params MySqlParameter[] paras) { using (MySqlConnection conn new MySqlConnection(ConnStr)) { conn.Open(); using (MySqlCommand cmd new MySqlCommand(sql, conn)) { if (paras ! null) cmd.Parameters.AddRange(paras); return cmd.ExecuteNonQuery(); } } } } }连接字符串里的 Server 用 127.0.0.1 而不是 localhost是因为在某些系统上 localhost 会被解析成 IPv6 的 ::1导致连不上。Port 默认 3306Pwd 填你安装 MySQL 时设的 root 密码。CharSet 必须和数据库字符集一致这句是关于“C# 连接 MySQL”最常见的乱码解药。using关键字保证连接和命令用完后自动释放课设里常见的“连接过多”“远程主机强迫关闭了连接”多半是没释放连接导致的。3.3 房屋查询LIKE 模糊搜索 参数化防 SQL 注入房源管理里最核心的查询是“按房号关键字和租金上限筛房源”。这一小节放在 BLL 层做一个业务方法输入关键字和租金上限输出 House 列表。using System.Collections.Generic; using System.Data; using MySql.Data.MySqlClient; using HouseRental.Model; namespace HouseRental.BLL { public class HouseManager { public ListHouse SearchHouses(string keyword, decimal maxRent) { string sql SELECT house_id, house_no, area, rent, house_status FROM t_house WHERE house_no LIKE kw ; if (maxRent 0) sql AND rent maxRent ; sql ORDER BY rent ASC; ListMySqlParameter paras new ListMySqlParameter(); paras.Add(new MySqlParameter(kw, % keyword %)); if (maxRent 0) paras.Add(new MySqlParameter(maxRent, maxRent)); DataTable dt DbHelper.Query(sql, paras.ToArray()); ListHouse list new ListHouse(); foreach (DataRow row in dt.Rows) { list.Add(new House { HouseId Convert.ToInt32(row[house_id]), HouseNo row[house_no].ToString(), Area Convert.ToDecimal(row[area]), Rent Convert.ToDecimal(row[rent]), HouseStatus Convert.ToInt32(row[house_status]) }); } return list; } } }这里的paras.ToArray()就是一个典型的“数组和集合有什么区别”场景DbHelper.Query的签名收的是params MySqlParameter[]参数数组而代码里需要动态追加条件所以用了ListMySqlParameter。List 是长度可变的集合可以 Add 添加元素数组长度固定不能直接追加。二者类型不同所以必须用ToArray()转成数组才能传参。这是 C# 课设里最容易漏掉的一行代码漏了编译直接报错。参数化用kw和maxRent占位注意赋值时 keyword 要自己加“%”而不是把“%”写进 SQL 字符串。如果写成“WHERE house_no LIKE %kw%”会语法报错因为 MySQL 不识别这种写法。maxRent 为 0 时表示不过滤租金就不追加该条件。排序用ORDER BY rent ASC让便宜的房源排前面视觉效果更友好。3.4 合同登记是一个事务合同、账单、房源状态一起提交合同登记这个动作涉及三处写库插入合同记录、生成初始账单、把房源状态改为已出租。任何一步失败其他步骤都得回滚否则会出现“合同建了但房子还是空闲”的脏数据。C# 里用 MySqlTransaction 来做这件事。public bool SignContract(Contract c, Liststring billMonths) { using (MySqlConnection conn new MySqlConnection(ConnStr)) { conn.Open(); MySqlTransaction tran conn.BeginTransaction(); try { // 1. 检查房源状态0 空闲才能签约 string checkSql SELECT house_status FROM t_house WHERE house_id houseId; MySqlCommand cmdCheck new MySqlCommand(checkSql, conn, tran); cmdCheck.Parameters.AddWithValue(houseId, c.HouseId); int status Convert.ToInt32(cmdCheck.ExecuteScalar()); if (status ! 0) { tran.Rollback(); return false; } // 2. 插入合同 string insertContract INSERT INTO t_contract (contract_no, house_id, tenant_id, start_date, end_date, monthly_rent, deposit) VALUES (no, houseId, tenantId, start, end, rent, deposit); SELECT LAST_INSERT_ID();; MySqlCommand cmd1 new MySqlCommand(insertContract, conn, tran); cmd1.Parameters.AddWithValue(no, c.ContractNo); cmd1.Parameters.AddWithValue(houseId, c.HouseId); cmd1.Parameters.AddWithValue(tenantId, c.TenantId); cmd1.Parameters.AddWithValue(start, c.StartDate); cmd1.Parameters.AddWithValue(end, c.EndDate); cmd1.Parameters.AddWithValue(rent, c.MonthlyRent); cmd1.Parameters.AddWithValue(deposit, c.Deposit); int contractId Convert.ToInt32(cmd1.ExecuteScalar()); // 3. 批量生成账单 foreach (string month in billMonths) { string billSql INSERT INTO t_bill(contract_id, bill_month, amount) VALUES (cid, month, rent); MySqlCommand cmd2 new MySqlCommand(billSql, conn, tran); cmd2.Parameters.AddWithValue(cid, contractId); cmd2.Parameters.AddWithValue(month, month); cmd2.Parameters.AddWithValue(rent, c.MonthlyRent); cmd2.ExecuteNonQuery(); } // 4. 修改房源状态为已出租 string updateHouse UPDATE t_house SET house_status 1 WHERE house_id houseId; MySqlCommand cmd3 new MySqlCommand(updateHouse, conn, tran); cmd3.Parameters.AddWithValue(houseId, c.HouseId); cmd3.ExecuteNonQuery(); tran.Commit(); return true; } catch (Exception ex) { tran.Rollback(); Console.WriteLine(ex.Message); return false; } } }事务对象 tran 必须通过new MySqlCommand(sql, conn, tran)传入每个命令漏传第三个参数的话这些命令会在独立事务里自动提交回滚就失效了。第 4 步用了 UPDATE 语句MySQL 的 UPDATE 语法是“UPDATE 表名 SET 字段名 新值 WHERE 条件”SET 后面多个字段用逗号分隔WHERE 一定带主键否则会批量更新全部记录。执行ExecuteScalar()返回首行首列刚好拿到 LAST_INSERT_ID。3.5 DataGridView 绑定数据别一层层手拼字符串查询结果要上界面最快的办法是直接用 DataTable 给 DataGridView 当数据源然后再调整列宽、列标题和显示顺序。DataTable dt DbHelper.Query(SELECT house_no, area, rent FROM v_house_vacancy); dgvHouses.AutoGenerateColumns true; dgvHouses.DataSource dt;用 DataTable 直绑比用 List 再配十几个 DataPropertyName 要省事而且 DataTable 绑定的数据源天然支持排序和筛选。不足之处是列名直接显示字段英文名需要手动把dgvHouses.Columns[house_no].HeaderText改成“房号”。给操作按钮加事件时会用到委托。下面这种 lambda 写法其实是委托的一种简洁形式相当于内联了一个方法课设里不用自定义 delegate 关键字直接注册事件。dgvHouses.CellClick (s, e) { if (e.RowIndex 0) return; string houseNo dgvHouses.Rows[e.RowIndex].Cells[house_no].Value.ToString(); MessageBox.Show(选中房源 houseNo); };CellClick事件和普通按钮的Click不同它会在点击表头时也触发此时 RowIndex 为 -1所以要先做判断。Cells[house_no]按列名取值比按索引安全因为列顺序改变时代码不用改。4. 设计报告的三个硬通货ER 图、模块图和测试用例表zip 里的设计报告通常是一份手感和排版都不错的 Word 模板但直接交原模板很容易被认定为雷同。明智的做法是直接把模板当成目录骨架把你前面做过的表设计、代码分层、功能验证结果填进去尤其把三个硬通货章节做厚ER 图、系统模块图、测试用例表。这三样东西是最能体现“这是我做的”的证据。4.1 用 Workbench 反向生成 ER 图再微调关系线很多人画 ER 图是用 Word 里的文本框和箭头一个一个拼的画一晚上还歪歪扭扭。更省力的方式是用 MySQL Workbench 的 Database - Reverse Engineer选择 house_rental 库工具会自动扫描表的外键关系并生成 EER 图然后 File - Export 导出PNG字号和关系线都清晰。导出的图需要手动整理几处把位置相近的表摆在同一行避免关系线交叉确认每对关系的基数标识正确。比如 t_house 和 t_contract 是 1:Nt_tenant 和 t_contract 是 1:Nt_contract 和 t_bill 是 1:N每根关系线旁边标注“1”和“N”。主键列上会有小钥匙图标外键列上会有小菱形这些符号在答辩时被老师看到的概率很高要能指出来讲明含义。如果关系线没有自动生成说明建表 SQL 里压根没写 FOREIGN KEY此时需要手动执行 ALTER TABLE 把外键补上。补完之后 ER 图才真实可信。4.2 模块图与界面菜单一一对应系统功能模块图经常被写成一张大树第一层是系统名称第二层是登录、房源管理、租客管理、合同管理、账单管理第三层是每个模块下的具体操作。画这棵树不要凭空编打开你的 WinForms 窗体看菜单栏和 TabControl 里有几个页面模块图就画几根分支。这是最诚实的做法也方便你按图演示。每个模块对应的数据表要写清楚。房源管理操作 t_house租客管理操作 t_tenant合同管理操作 t_contract账单管理操作 t_bill 和视图 v_monthly_income。模块图里还能标注“本模块只读视图”之类的说明体现你对读写分离有意识。树形图的绘制可以用 Word 的 SmartArt 或 draw.io注意字体和连线颜色保持统一。4.3 测试用例表把踩过的坑写进去反而更真实测试章节是课设报告里水分最大的部分很多人的表格千篇一律写着“测试通过”。评委老师看多了这种话反而对“某个用例修复后通过”的记录更感兴趣因为那说明真跑过真改过。下面是一组可以直接参考的用例结构。用例编号测试功能操作步骤预期结果实际结果状态TC-01用户登录输入 admin / 123456进入主界面与预期一致通过TC-02用户登录输入 admin / 错误密码提示密码错误与预期一致通过TC-03房源查询关键字“A”租金上限 2000返回 A-1-101、A-1-102与预期一致通过TC-04合同登记对已出租房间再次签约提示“该房源已出租”代码校验生效修复后通过TC-05退租操作删除有账单的合同禁止删除提示先处理账单外键报错后改为业务提示修复后通过TC-04 对应 3.4 节事务代码里的house_status检查TC-05 对应外键约束。这两条写进报告再配一段“修复过程说明”比十个“通过”都有说服力。数据字典部分可以从 Workbench 的SHOW FULL COLUMNS FROM t_house结果整理成表格把注释列原文粘贴进去效率高且不会漏字段。5. 常见问题排查连接失败、中文乱码、外键删不掉的 5 个翻车现场课设做完不代表能顺利演示环境换一台电脑就可能全面翻车。下面 5 个问题是我在课设辅导里见过频率最高的每个都按“现象 — 原因 — 解决”的路子写照着排查能省下大量找茬时间。5.1 连接 MySQL 报 error 2002 或 1045先确认服务再核对账号现象C# 程序启动一查询就抛 MySqlException错误文本里出现Cant connect to local MySQL server through socket /tmp/mysql.sock或Access denied for user root。前一个在 Linux 服务器上跑常见后一个在 Windows 上跑常见。原因2002 是 MySQL 服务没起来或客户端走了 socket 文件路径而不是 TCP 端口1045 是用户名或密码错误。如果你在 Windows 上把连接字符串的 Server 写成 localhost驱动有时会尝试走 socket而 Win 版 MySQL 默认只监听 3306 端口。解决先用 Workbench 确认能连上数据库这能排除服务本身的问题。再把连接字符串改成Server127.0.0.1;Port3306;Uidroot;Pwd你的密码;强制走 TCP。密码错误就执行ALTER USER rootlocalhost IDENTIFIED BY 新密码;重设。MySQL 8.0 如果报Authentication method caching_sha2_password not supported是因为旧版 MySql.Data 驱动不认识新认证插件两个选择升级驱动包到 8.0或者把 root 的认证方式改回。改法是执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;。5.2 中文全部显示成问号或乱码字符集三层都要一致现象往 t_tenant 插入“张三”Workbench 里看是“???”或者 C# DataGridView 里显示锟斤拷。原因至少有一层字符集不对。数据库建库时用了 latin1、连接字符串没带 CharSetutf8mb4、或者 C# 的文本框内容本身被转换坏掉。三层里坏任何一层都会乱码。解决从下往上查。先确认库表字符集SHOW CREATE TABLE t_tenant看结尾有没有DEFAULT CHARSETutf8mb4没有就重建表或执行ALTER TABLE t_tenant CONVERT TO CHARACTER SET utf8mb4;。再确认连接字符串带了CharSetutf8mb4。最后在插入前打印输入内容排除 UI 层取值问题。注意 utf8mb4 和 utf8 不同utf8 在 MySQL 里最多存 3 字节部分生僻字和 Emoji 会存不进去。5.3 删除房源时外键报错先处理子表数据或用“退租”逻辑现象界面上点“删除房源”提示Cannot delete or update a parent row: a foreign key constraint fails。原因t_house 被 t_contract 引用合同还存在数据库禁止直接删父表记录。这是 InnoDB 的默认保护不是 bug。解决不要在界面提供物理删除功能改成“退租”操作。业务逻辑是先把该房源相关合同的账单催缴完毕、合同标记为已结束再把房源状态改回空闲。如果一定要物理删除正确的顺序是删 t_bill - 删 t_contract - 更新 t_house 状态或删除 t_house三个步骤同样放进事务里执行。还有一种做法是把外键改成ON DELETE CASCADE但这会让删除操作像多米诺骨牌一样连带删掉账单记录审计时找不到历史数据课设里不推荐。5.4 DataGridView 点了查询但界面不刷新每次重新赋 DataSource现象第一次查询有数据第二次换个关键词再点查询表格要么没变化要么旧行下面叠新行。原因dgvHouses.DataSource dt只生效一次第二次查询生成了新的 DataTable 但界面数据源还指着旧对象或者复用了同一个 DataTable 导致行数据累加。解决每次查询都 new 一个新的 DataTable再把它赋给 DataSource而不是修改旧表。赋值之后如果列顺序乱掉重新设置列标题即可。dgv.Refresh()只管重绘界面不重新取数据所以光调用它没用。另一个隐蔽问题是 DataGridView 开启了排序后用户点击了列头程序里重新赋值 DataSource 会重置排序状态体验上像“乱序”这属于正常现象不用管。5.5 改完合同租金历史账单金额没变快照设计别当成 bug现象把某份合同的月租金从 1800 改成 2200结果以前月份的账单金额仍是 1800新生成的账单却是 2200。原因账单在生成那一刻把租金复制进了 amount 字段属于“快照”设计。这样做是对的历史账单代表当时应收的钱不该跟着合同漂移。真正的 bug 是你没意识到这个设计在测试用例里期望所有账单一起变。解决如果老师要求体现“调价补差”正确做法是写一条 UPDATE 把指定账期之后未收款的账单按新租金重算但只允许处理未收账单已收账单不允许改。UPDATE t_bill b JOIN t_contract c ON b.contract_id c.contract_id SET b.amount c.monthly_rent WHERE b.pay_status 0 AND b.bill_month 2025-07;这条 SQL 用 JOIN 关联两张表SET赋值来自另一张表的字段是 MySQL UPDATE 语法里关联更新的标准写法。执行前先 SELECT 预览一下会被改哪些行再运行别刚写完就直接执行这种操作没有后悔药。6. 答辩前的最后一道工序一键重置演示数据与导出 CSV项目跑到能查能签能收租的程度之后离答辩还差一个细节演示现场的数据库状态不可控。你上午测试时留下的脏数据下午演示时就可能让某个查询结果看起来莫名其妙。我一般会在系统里留一个“重置演示数据”的入口本质就是重新执行建库脚本。演示前点一下所有数据恢复成最初的样子整场答辩都在干净数据上做。重置做法很简单建一个控制台项目或写一段 C# 代码调用 mysql 命令行执行 SQL 脚本但密码不能出现在源码里。课设阶段可以在窗体上加一个隐藏按钮用Process.Start(mysql, -u root -p123456 db.sql)但需要把工作目录指向 db.sql 所在路径。更稳妥的是把建库脚本按分号拆开逐条执行不过这要求脚本里不能有存储过程这类包含分号的对象所以建议直接调用命令行简洁可靠。导出数据同样是答辩刚需。老师看到表格里 30 条记录第一反应可能是“这多半是硬编码的数据”此时你把表格导出成 CSV 再打开给老师看说服力完全不同。导出 CSV 不需要引入第三方库用 C# 内置的 StringBuilder 就能完成。private void btnExportCsv_Click(object sender, EventArgs e) { SaveFileDialog dlg new SaveFileDialog() { Filter CSV 文件|*.csv, FileName 房源列表.csv }; if (dlg.ShowDialog() ! DialogResult.OK) return; var sb new System.Text.StringBuilder(); var cols dgvHouses.Columns; var rows dgvHouses.Rows; for (int i 0; i cols.Count; i) { sb.Append(cols[i].HeaderText); if (i cols.Count - 1) sb.Append(,); } sb.AppendLine(); foreach (DataGridViewRow row in rows) { if (row.IsNewRow) continue; for (int i 0; i cols.Count; i) { sb.Append(row.Cells[i].Value?.ToString()); if (i cols.Count - 1) sb.Append(,); } sb.AppendLine(); } System.IO.File.WriteAllText(dlg.FileName, sb.ToString(), System.Text.Encoding.UTF8); MessageBox.Show(导出成功); }这段代码用IsNewRow跳过 DataGridView 底部的空行用Value?.ToString()避免空值抛异常。逗号拼接时最后一项不加逗号避免 CSV 多出一列。写文件用 UTF-8 编码否则用 Excel 打开中文会乱码。这段方法的业务逻辑就是把可视化表格原样落盘没有查库所以无论当前界面显示的是查询结果还是全部数据导出的都是用户正在看的内容。我现在的习惯是拿到任何课设包第一步永远是先把 .sql 在干净环境里跑通再打开 .sln这个顺序帮我避开了大半“代码有问题”的误判。数据库是地基地基不牢后面所有排查都是在浪费时间。希望这套从建表到排错再到答辩准备的完整链路能帮到你也祝你课设顺利过审。本文还有配套的精品资源点击获取
