简介:这是一份基于C#与MySQL开发的房屋租赁管理系统完整课程设计资源,面向计算机、软件工程及通信工程等专业学生,可作为课程设计或毕业设计参考。资源共69个文件,包含25个C#源码、SQL数据库脚本、可执行程序、数据库连接驱动安装包、课程设计报告文档,以及ER图、数据流图等设计图表,压缩包整体约12.81MB。项目覆盖租户信息管理、房源信息管理、租赁合同管理等核心业务模块,涉及ADO.NET数据访问、Windows窗体界面设计、SQL增删改查、面向对象编程等关键知识点,适合作为学习C#桌面应用开发的完整案例。通过学习可理解系统从需求分析、数据库设计到编码实现与文档撰写的完整流程,掌握C#与MySQL数据库交互方法。已有276人学习使用,可直接借鉴其数据库表结构、界面布局与代码组织方式,作为课设项目或毕业设计的基础框架。
1. 先别急着双击 sln:这份 C#+MySQL 房屋租赁系统压缩包的正确打开方式
大三做数据库课程设计时,最怕的不是写不出代码,而是下载的源码包解压后根本跑不起来。这份基于 C#+MySQL 的房屋租赁管理系统压缩包,里面是一整套成体系的课程设计:RoomRentSystem 解决方案、roomrentsystem.sql 数据库脚本、ER 图、数据流图、MySQL 连接驱动安装包和一份能直接改写的课程设计报告,连介绍.txt 都替你写好了项目说明。它能解决课设最核心的诉求——快速建起一套能演示、能答辩、能讲清楚原理的房源管理系统:管理员登录、房源维护、租户管理、合同签约和收租记录都齐全。适合正在做 C# 课设、写毕业设计想找个参考,或者单纯想补数据库实践的开发者。下面按我复现这套系统的顺序,从数据库到代码到排错一层层拆开。
2. 数据库设计先行:从 ER 图、数据流图到 roomrentsystem.sql 的还原步骤
拿到压缩包,我从来不是先双击 RoomRentSystem.sln,而是先花十分钟把数据库侧的三样东西过一遍:ER图.png、数据流图.png、roomrentsystem.sql。原因很简单:C# 窗体里每一个文本框、每一列表格,最终都对应到某张表的字段上。表结构和代码里的字段对不上,编译可能通过,一运行全是异常。数据库是这套系统的地基,地基歪了,上面盖多少窗体都得返工。
2.1 ER 图里的五张表:租户、房源、合同、缴费、管理员的关系
用系统自带图片查看器打开 ER 图,能看到一套典型的课设级表结构,核心是五张表:
| 表名 | 职责 | 关键字段 |
|---|---|---|
| admin | 管理员登录 | id, username, password |
| house | 房源信息 | house_id, house_no, area, rent, deposit, status, address |
| tenant | 租户信息 | tenant_id, tenant_name, id_card, phone |
| contract | 租赁合同 | contract_id, tenant_id, house_id, start_date, end_date, rent, deposit, sign_date |
| payment | 收租记录 | payment_id, contract_id, pay_date, amount, month, pay_type |
表之间的关系是这门课设真正的考点。租户和房源从业务上看是多对多:一个租户可以先后租不同房源,一套房源也会被不同租户租过。数据库里处理多对多的标准做法是引入中间表,这里的 contract 就扮演了这个角色——每张合同同时持有 tenant_id 和 house_id 两个外键,把多对多拆成两个一对多。在此基础上,合同和缴费记录又是一对多:一份合同对应多笔收租流水,这样租金按月催收时每笔都有独立记录,可以统计、可以追溯。
为什么要理解到这一层?因为答辩老师最常问的问题之一就是"为什么别把 tenant_id 直接放 house 表里"。直接放意味着某套房在任意时刻只能绑定一个租户,退租后历史归属彻底丢失,收租记录也散落在代码里无法联查。而拆出 contract 之后,house 表只需一个 status 字段(0 空置 / 1 已租)标记当前状态,完整租赁历史都沉淀在 contract 和 payment 里。能把这个逻辑讲明白,"关联表拆分"这个知识点你就拿到了。
实际复现时要注意:sql 文件里的表名未必和 ER 图完全一致,有的课设喜欢用 manager 代替 admin,用 room 代替 house。以 roomrentsystem.sql 里 CREATE TABLE 语句的实名为准。我拿到包的习惯是先用记事本打开 sql,搜索所有 "CREATE TABLE",把表名和字段名抄成一张对照表,再回头比对 ER 图。两边不一致的地方,就是后面改代码时最容易踩的雷——你照着 ER 图写 SQL,结果表根本不存在,报 ERROR 1146 Table doesn't exist,那时候再回头查就晚了。
2.2 数据流图对应的三个模块:登录验证、房源维护、合同收租
数据流图.png 画的是整套业务的信息流向,通常分成两个层次:顶层是"管理员→系统"的单条数据流,细化层拆出登录验证、房源维护、租户维护、合同签订、收租记录五个处理节点。这五个节点落在 RoomRentSystem 解决方案里,就是三组窗体和一整套数据库操作:
- 登录窗体(FrmLogin)+ 主窗体(FrmMain):负责身份验证和模块导航。数据流图里"管理员账号密码"这条流,对应代码里的 login 按钮事件。
- 房源管理窗体(FrmHouse)+ 租户管理窗体(FrmTenant):单表增删改查,数据访问层最薄,是练习 DataGridView 绑定的最佳样板。
- 合同管理窗体(FrmContract):核心业务节点,新建合同时要同时写 contract、payment 和更新 house.status,是唯一需要事务的地方。
数据流图的价值不在于画得多规范,而在于它标明了"信息往哪流"。比如图上从合同管理节点生出一条箭头指向收租记录节点,在代码里就是合同保存按钮里先插合同、再插首期收租、最后改房源状态的动作序列——这三步的先后顺序和事务边界,都是从数据流图定下的。
我复现课程设计时的习惯是:把数据流图的每个处理节点写成一两句注释,粘在对应窗体的代码文件头部。答辩演示时,先指着界面讲"这是登录验证节点",再切到代码讲"这是房源维护节点",全程有图有码,比空口背架构有说服力得多。
2.3 还原数据库:mysql 命令行导入 roomrentsystem.sql 的完整步骤
数据库脚本是整包的地基,导入方式我推荐命令行,因为答辩现场演示SOURCE比鼠标点 Navicat 更能体现基本功。
# 进入 MySQL 命令行(-u 指定用户,-p 回车后输入密码) mysql -u root -p-- 1. 创建数据库,显式指定 utf8mb4,为中文数据兜底 CREATE DATABASE IF NOT EXISTS roomrentsystem DEFAULT CHARACTER SET utf8mb4; -- 2. 切到目标库 USE roomrentsystem; -- 3. 导入脚本;路径按你实际解压的位置改,注意用正斜杠 SOURCE D:/RoomRentSystem/roomrentsystem.sql; -- 4. 验证导入结果 SHOW TABLES;这段流程的逻辑很简单:先建库、再选库、再灌数据、最后验证。关键在第 4 步——SHOW TABLES输出的表清单必须和 ER 图对得上。如果少了表,多半是导入过程中途报错中断;如果表都在,再用DESC house;抽查字段结构,确认主键、外键、字段名和代码期望的一致。
提示:导入前先打开 sql 文件看前几行。如果它自带
CREATE DATABASE和USE语句,前两步可以跳过,直接 SOURCE;如果直接就是CREATE TABLE开头,就必须手动建库选库,否则会报 ERROR 1046 No database selected。
导入过程中最常见的报错是 ERROR 1064(语法错误),通常原因有两个:一是 sql 文件用记事本编辑过,被 Windows 换行符和 BOM 头污染;二是脚本里含存储过程或触发器,里面有 DELIMITER 语句,命令行直接导入会卡在分号处。前者用记事本"另存为"转成 UTF-8 无 BOM 即可,后者要看清楚报错行号,把对应存储过程单独摘出来执行。出现问题时,mysql -u root -p roomrentsystem < D:/RoomRentSystem/roomrentsystem.sql这种重定向方式有时比 SOURCE 更稳妥,因为 SOURCE 是在客户端内执行的,重定向是 shell 层面把文件喂给服务器,错误信息更明确。
导入成功只是第一步,建议顺手执行一条业务链的查房列表查询:
SELECT house_id, house_no, status FROM house ORDER BY house_id;把返回的几条数据记下来,后面跑通系统时对照着看,界面上显示的数据和这里一致,就说明数据库层完全还原了。
3. 环境与连接串:mysql-connector-net 6.9.6 的安装、引用和最小连通测试
3.1 为什么包里配的是 6.9.6:老版 Connector 的适配边界
压缩包里那个 mysql-connector-net-6.9.6.msi,是 MySQL 官方为 ADO.NET 提供的数据库驱动安装包,发布于 2016 年前后。当时的主流组合是 MySQL 5.6/5.7 + .NET Framework 4.0/4.5 + Visual Studio 2013/2015,这份课设正是在那个环境下写的,所以 6.9.6 和它的代码是原配。
这个版本号不是随手选的,它直接决定了你能用什么版本的 MySQL。6.9.6 的驱动只认识 mysql_native_password 认证协议,而 MySQL 8.0 起默认认证插件换成了 caching_sha2_password,两者握手必失败。所以如果你按现在的习惯去 MySQL 官网下载页抓最新版 8.x,装完大概率连不上这份课设。到 MySQL 官网下载页选安装包时,我建议在 5.7.x 版本区停留一下:5.7 和 6.9.6 驱动同年代,导入 roomrentsystem.sql 出现的兼容性问题最少,答辩机器的环境也最容易复现。
另一个适配边界是 .NET 版本。MySql.Data 6.9.6 的托管 dll 面向 .NET Framework,编译目标是 4.0/4.5。如果你用 Visual Studio 2022 打开 sln,项目默认目标框架可能偏新,可能会在"添加引用"阶段就找不到驱动,或者在运行时抛 FileNotFoundException 找不到 MySql.Data。解决办法是右键项目 → 属性 → 目标框架,改成 .NET Framework 4.6.1 或更低的版本,让项目回到 6.9.6 的射程内。
如果实在想用 MySQL 8 + 新版驱动,也不是不行,但那就等于放弃包里自带的 msi,改成 NuGet 里最新的 MySql.Data 8.x,然后代码里几乎没有要改的地方——因为 ADO.NET 的 API 从 6.9 到 8.x 基本兼容。这个选择留给想练"升级依赖"的读者,课设保过优先的话,老老实实 5.7 + 6.9.6。
3.2 引用驱动的两种方式:MSI 安装后的 dll 引用与 NuGet 包
方式一:直接双击压缩包里的 mysql-connector-net-6.9.6.msi 安装。默认安装路径类似:
C:\Program Files (x86)\MySQL\MySQL Connector Net 6.9.6\Assemblies\v4.0\MySql.Data.dll装完打开 Visual Studio,右键项目 → 添加 → 引用 → 浏览,定位到上面的 dll 文件选中确认。编译不报错,说明引用成功。
方式二:不装 msi,在 Visual Studio 的"工具 → NuGet 包管理器 → 程序包管理器控制台"里执行:
Install-Package MySql.Data -Version 6.9.6或者在"管理解决方案的 NuGet 程序包"窗口里搜 MySql.Data,版本下拉选 6.9.6,点安装。NuGet 方式的好处是项目文件里会记录包引用,整个项目目录拷到别的机器,还原 NuGet 包就能拉回驱动,不用每台机器都装一遍 msi。
两种方式各有利弊:msi 版附带安装目录里的帮助文档和示例,适合想翻文档的;NuGet 版干净、可迁移,适合赶时间复现的。我个人推荐 NuGet——课程设计是要换机器的,答辩机器上能一键还原包比什么都重要。
装完先验证引用:代码顶部写using MySql.Data.MySqlClient;,不出现红色波浪线即成功。如果出现"命名空间不存在"的编译错误,优先检查目标框架,而不是怀疑驱动没装上——九成情况是项目目标框架太高。如果目标框架确实合适还报错,检查是否同时引用了 System.Data 和 MySql.Data 的版本冲突;6.9.6 只有一个 MySql.Data.dll,版本冲突场景并不多见,遇到了就删掉引用重加一次。
3.3 最小连通测试:一份连接串和 20 行验证代码
环境搭好先别急着跑整个系统——直接跑 sln 一旦报错,错误信息会混在几十个窗体初始化的噪声里,很难定位。正确做法是新建一个空白 WinForms 项目,放一个按钮,写最少的代码验证数据库连通。
using System; using System.Windows.Forms; using MySql.Data.MySqlClient; namespace ConnTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnTest_Click(object sender, EventArgs e) { // 连接串各参数含义见下方说明表 string connStr = "server=127.0.0.1;port=3306;user id=root;password=123456;" + "database=roomrentsystem;charset=utf8;pooling=true;SslMode=None;"; using (MySqlConnection conn = new MySqlConnection(connStr)) { try { conn.Open(); MessageBox.Show("连接成功,MySQL 版本:" + conn.ServerVersion); } catch (Exception ex) { MessageBox.Show("连接失败:" + ex.Message); } } } } }这段代码的逻辑:Open() 才是真正建立 TCP 连接并完成认证的时刻,new MySqlConnection 只是在内存里准备一个连接对象。ServerVersion 属性成功返回,说明从网络到认证到选库全部通过。using 块在方法结束时自动调用 Dispose 关闭连接,这是 C# 管理数据库连接的标准姿势,也符合连接池的归还机制——Close 不是销毁连接,而是把连接还给连接池。
连接串参数逐个说:
| 参数 | 值 | 作用与坑点 |
|---|---|---|
| server | 127.0.0.1 | 优先用 IP 而不是 localhost,避免 localhost 被解析成 IPv6 的 ::1 导致连不上 |
| port | 3306 | 必须和 MySQL 实际监听端口一致,装了多实例时常改成 3307/3308 |
| user id / password | root / 你的密码 | 与 MySQL 里的用户密码严格一致,密码含特殊字符时要留意转义 |
| database | roomrentsystem | 必须和导入后的库名一致,MySQL 大小写敏感策略因系统而异 |
| charset | utf8 | 保证中文读写不乱码,表用 utf8mb4 时这里也可以写 utf8mb4 |
| pooling | true | 对应 MySQL 数据库连接池,驱动默认开启,Close 归还连接 |
| SslMode | None | 老驱动连 MySQL 8 遇到 SSL 连接错误时显式关闭加密通道 |
最小连通测试通过之后,再去打开 RoomRentSystem.sln。如果登录时仍报连接错误,先用第 5 章的排查顺序走一遍;绝大多数情况下,问题出在 DBHelper.cs 里写死的密码和你本机 MySQL 实际密码不一致——把最小测试里验证通过的连接串原样替换过去,问题即消。
4. 核心代码拆解:登录、房源增删改查与合同事务的实现模板
数据库通了之后,就该读代码了。对课设来说,代码部分不需要你理解每一行,而是抓住三条主线:连接怎么封装、查询怎么参数化、多表写入怎么保证一致。这三条主线正好对应 C# 入门阶段最容易卡的三个点,也是 C# 课程设计报告里最值得写的技术点。
4.1 数据库连接封装:单例 DBHelper 与参数化执行入口
打开 RoomRentSystem 解决方案,第一个值得通读的文件是数据库连接类,一般叫 DBHelper.cs 或 SQLHelper.cs。课程设计里它通常是静态单例:
using System; using MySql.Data.MySqlClient; namespace RoomRentSystem { class DBHelper { // 连接串:正式使用时建议写到 App.config,这里先保持课设原样 private static string connStr = "server=127.0.0.1;port=3306;database=roomrentsystem;" + "user id=root;password=123456;charset=utf8;"; private static MySqlConnection conn = null; // 获取一个已打开的连接 public static MySqlConnection GetConnection() { if (conn == null) { conn = new MySqlConnection(connStr); } if (conn.State != System.Data.ConnectionState.Open) { conn.Open(); } return conn; } // 统一执行增删改,返回受影响行数 public static int ExecuteNonQuery(string sql, params MySqlParameter[] paras) { using (MySqlCommand cmd = new MySqlCommand(sql, GetConnection())) { if (paras != null && paras.Length > 0) cmd.Parameters.AddRange(paras); return cmd.ExecuteNonQuery(); } } } }逻辑说明:GetConnection() 保证整个程序只有一个 MySqlConnection 实例,避免每个窗体各开一个连接把连接池撑爆;ExecuteNonQuery() 把所有增删改操作收敛到一处,SQL 语句和参数从调用方传入,返回受影响行数用于判断操作成功与否。params 关键字允许调用方直接传 MySqlParameter 数组而不需要先 new 数组,这在课设代码里能少写很多样板。
答辩被问"你用了什么设计模式",说"数据库连接用了单例,界面数据操作统一走一个入口封装"就能过关。真正要留意的反而是 connStr 里的密码:这份课设大概率写死成 123456,你本机的 MySQL 密码如果不是这个,第一件事就是改它,而不是怀疑驱动没装对。
4.2 登录模块:参数化查询与用户名校验
登录窗体是整个系统第一个窗体,也是最容易被扣分的地方——因为很多课设直接拼接 SQL 字符串,被答辩老师一问 SQL 注入就哑口。这份项目如果用的是参数化查询,就照着下面这个模板理解:
private void btnLogin_Click(object sender, EventArgs e) { string username = txtUsername.Text.Trim(); string password = txtPassword.Text.Trim(); if (username == "" || password == "") { MessageBox.Show("用户名和密码不能为空"); return; } // 参数化查询:@u 和 @p 由驱动转义,不会破坏 SQL 结构 string sql = "SELECT COUNT(*) FROM admin WHERE username = @u AND password = @p"; MySqlParameter[] paras = { new MySqlParameter("@u", username), new MySqlParameter("@p", password) }; using (MySqlCommand cmd = new MySqlCommand(sql, DBHelper.GetConnection())) { cmd.Parameters.AddRange(paras); int count = Convert.ToInt32(cmd.ExecuteScalar()); if (count > 0) { // 登录成功,打开主窗体,隐藏登录窗体 FrmMain frm = new FrmMain(); frm.Show(); this.Hide(); } else { MessageBox.Show("用户名或密码错误"); } } }逻辑说明:ExecuteScalar 返回第一行第一列,配合 COUNT(*) 就能只拿一个整数判断登录是否成功,比 DataReader 读整行数据轻量。参数化查询的关键在于 @u 和 @p 是占位符,驱动会把用户名和密码当作纯数据转义,即使用户输入' OR 1=1 --也只会当作字符串匹配。
这里有个课设常犯的错:把密码明文存在表里。虽然这节课设就是这样,但答辩时如果老师问"密码怎么不加密",你可以答"课设重点是完成增删改查闭环,生产环境必须做哈希加盐"。诚实承认边界,比硬编一套加密逻辑却讲不清楚好得多。
4.3 房源管理:DataGridView 绑定、添加与删除
房源管理窗体是典型的"表单 + DataGridView"结构:上半部分是录入控件,下半部分表格展示全部房源。窗体加载时调用加载方法:
private void FrmHouse_Load(object sender, EventArgs e) { LoadHouseList(); } private void LoadHouseList() { string sql = "SELECT house_id, house_no AS 房号, area AS 面积, " + "rent AS 月租金, deposit AS 押金, " + "CASE WHEN status = 1 THEN '已租' ELSE '空置' END AS 状态, " + "address AS 地址 FROM house ORDER BY house_id DESC"; MySqlDataAdapter da = new MySqlDataAdapter(sql, DBHelper.GetConnection()); DataTable dt = new DataTable(); da.Fill(dt); dgvHouse.DataSource = dt; }逻辑说明:MySqlDataAdapter 的 Fill 会自己处理连接的打开和关闭,你把连接对象传进去,它填充完 DataTable 就释放,不需要手动 Open/Close。ORDER BY house_id DESC 让新加的房源排最前面,演示时效果最好——这也是 MySQL 排序在业务里最常见的用法:不是"怎么排",而是"新的记录要一眼能看到"。
添加房源的保存按钮,核心是插入语句和参数数组的组装:
private void btnAdd_Click(object sender, EventArgs e) { if (txtHouseNo.Text.Trim() == "") { MessageBox.Show("房号不能为空"); return; } // 金额用 decimal,避免 double 的精度误差 string sql = "INSERT INTO house (house_no, area, rent, deposit, status, address) " + "VALUES (@house_no, @area, @rent, @deposit, @status, @address)"; MySqlParameter[] paras = { new MySqlParameter("@house_no", txtHouseNo.Text.Trim()), new MySqlParameter("@area", Convert.ToDecimal(txtArea.Text.Trim())), new MySqlParameter("@rent", Convert.ToDecimal(txtRent.Text.Trim())), new MySqlParameter("@deposit", Convert.ToDecimal(txtDeposit.Text.Trim())), new MySqlParameter("@status", cmbStatus.SelectedIndex), // 0 空置 1 已租 new MySqlParameter("@address", txtAddress.Text.Trim()) }; int rows = DBHelper.ExecuteNonQuery(sql, paras); if (rows > 0) { MessageBox.Show("添加成功"); LoadHouseList(); // 刷新表格 ClearInput(); // 清空录入控件 } }注意 Convert.ToDecimal:房租、押金属于金额字段,必须用 decimal 而不是 double,否则 0.1+0.2 这种浮点误差会在累计统计时冒出来。这是 C# 入门阶段很多人忽略的细节,但课程设计报告里写一句"金额字段采用 decimal 类型以保证精度",比写一堆架构术语更实在。
删除按钮的代码要特别小心主键获取,这是第 5 章最后一个坑的前奏:
private void btnDelete_Click(object sender, EventArgs e) { if (dgvHouse.CurrentRow == null) return; // 必须通过主键列取值,不能用显示列的中文别名 int houseId = Convert.ToInt32(dgvHouse.CurrentRow.Cells["house_id"].Value); string sql = "DELETE FROM house WHERE house_id = @id"; MySqlParameter[] paras = { new MySqlParameter("@id", houseId) }; if (DBHelper.ExecuteNonQuery(sql, paras) > 0) { MessageBox.Show("删除成功"); LoadHouseList(); } }问题在于:SELECT 里把 house_id 也查出来了,但 DataGridView 默认会显示这一列,很丑。常见做法是把这个列设为隐藏列(在窗体设计器里把该列的 Visible 设为 false),但代码里 Cells["house_id"] 仍然能取值。如果你把列隐藏了却用中文别名取值,运行时就会抛 "Column 房号 cannot be found"——前提是你代码里写的是 Cells["房号"]。用哪个名字取决于你 SELECT 里的别名,这个对应关系就是坑的根源。租户管理窗体的增删改查和房源管理完全同构,照着这套模板改表名和字段名就能跑,不再重复贴代码。
4.4 合同签约事务:三步操作一起成功或一起回滚
合同管理是整个项目里最值得讲的部分,因为它是唯一涉及多表写入的业务点。点"签约"按钮时,系统要做三件事:插入 contract 合同记录、插入 payment 首期收租记录、把 house 表状态改成已租。这三步任何一步失败,前面成功的都必须撤销,否则会出现"合同建了但房源还是空置"的数据不一致。这就是事务的用武之地:
private void btnSign_Click(object sender, EventArgs e) { MySqlConnection conn = DBHelper.GetConnection(); MySqlTransaction trans = null; try { trans = conn.BeginTransaction(); // 开启事务 // 第一步:插入合同 string sqlContract = "INSERT INTO contract (tenant_id, house_id, start_date, end_date, rent, deposit, sign_date) " + "VALUES (@tenant_id, @house_id, @start_date, @end_date, @rent, @deposit, NOW())"; MySqlCommand cmd1 = new MySqlCommand(sqlContract, conn, trans); cmd1.Parameters.AddRange(new MySqlParameter[] { new MySqlParameter("@tenant_id", Convert.ToInt32(cmbTenant.SelectedValue)), new MySqlParameter("@house_id", Convert.ToInt32(cmbHouse.SelectedValue)), new MySqlParameter("@start_date", dtpStart.Value.ToString("yyyy-MM-dd")), new MySqlParameter("@end_date", dtpEnd.Value.ToString("yyyy-MM-dd")), new MySqlParameter("@rent", Convert.ToDecimal(txtRent.Text.Trim())), new MySqlParameter("@deposit", Convert.ToDecimal(txtDeposit.Text.Trim())) }); cmd1.ExecuteNonQuery(); int contractId = Convert.ToInt32(cmd1.LastInsertedId); // 拿到自增主键 // 第二步:插入首期收租记录 string sqlPay = "INSERT INTO payment (contract_id, month, amount, pay_date, pay_type) " + "VALUES (@contract_id, @month, @amount, NOW(), @pay_type)"; MySqlCommand cmd2 = new MySqlCommand(sqlPay, conn, trans); cmd2.Parameters.AddRange(new MySqlParameter[] { new MySqlParameter("@contract_id", contractId), new MySqlParameter("@month", dtpStart.Value.ToString("yyyy-MM")), new MySqlParameter("@amount", Convert.ToDecimal(txtRent.Text.Trim())), new MySqlParameter("@pay_type", "现金") }); cmd2.ExecuteNonQuery(); // 第三步:更新房源状态为已租 string sqlHouse = "UPDATE house SET status = 1 WHERE house_id = @house_id"; MySqlCommand cmd3 = new MySqlCommand(sqlHouse, conn, trans); cmd3.Parameters.AddRange(new MySqlParameter[] { new MySqlParameter("@house_id", Convert.ToInt32(cmbHouse.SelectedValue)) }); cmd3.ExecuteNonQuery(); trans.Commit(); // 全部成功,提交 MessageBox.Show("签约成功"); LoadContractList(); // 刷新合同列表 } catch (Exception ex) { if (trans != null) trans.Rollback(); // 任一失败,全部回滚 MessageBox.Show("签约失败:" + ex.Message); } }这段代码的逻辑是:BeginTransaction() 之后,所有 MySqlCommand 都要通过构造函数传入同一个 conn 和 trans 实例,否则命令不参与事务。Commit 让三个操作一起生效,Rollback 让已经执行的两个操作一起撤销。LastInsertedId 是 MySqlCommand 的属性,执行完带自增主键的 INSERT 后立即读取,能拿到新合同编号,这是第二步 payment 表的外键来源。DateTime 转字符串时用 "yyyy-MM-dd" 格式化,避免小时分秒混进日期字段。
如果老师追问"为什么不用三条普通 SQL",你就说:房源从空置变已租和合同的建立必须原子化,否则租赁数据会处于中间状态,事务就是从数据库层面保证这三条语句"要么全成、要么全不成"。能讲出这句话,课设答辩这一关基本就稳了。
5. 复现避坑指南:MySQL 8 认证、中文乱码和连接超时的五个真实翻车现场
这一章的坑全部按"现象→原因→解决"记录,每一个都是我在复现这类课设包时的真实经历,也是整个系统能否跑起来的分水岭。
5.1 Authentication plugin 'caching_sha2_password':老连接器撞上 MySQL 8
现象:双击 sln 编译通过,但一登录就抛异常:Authentication to host 'localhost' for user 'root' using method 'caching_sha2_password' failed。
原因:包里自带的 mysql-connector-net-6.9.6 只支持 MySQL 5.x 的 mysql_native_password 认证。MySQL 8.0 起默认认证插件换成 caching_sha2_password,老驱动在握手阶段就被服务器拒绝了——这不是你代码写错,是驱动版本和服务器认证协议不兼容。
解决:两条路。第一条,装 MySQL 5.7,和 6.9.6 驱动同年代,导入 sql 也最稳。第二条,坚持用 MySQL 8,则把 root 用户的认证方式改回旧协议:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;改完重启 MySQL 服务,再用第 3 章的最小连通测试验证。注意 ALTER USER 里的 '你的密码' 要和代码连接串里的 password 保持一致。我一般推荐课设用第一条路,因为答辩环境不受控,MySQL 5.7 兼容性上限最高。
5.2 导入 SQL 后中文变问号:字符集三处不一致
现象:SOURCE 导入不报错,SHOW TABLES 正常,但 SELECT 出来房源地址、租户姓名全是???或乱码。
原因:字符集不一致发生在三个层面——sql 文件本身的编码(记事本打开看,常见 UTF-8 或无 BOM 的 GBK)、MySQL 客户端连接的字符集、表字段的字符集。命令行导入时默认 character_set_client 可能是 latin1,UTF-8 的中文按 latin1 解析就变成了问号。
解决:按顺序做三件事。第一,导入前在 mysql 命令行执行:
SET NAMES utf8mb4;第二,如果 sql 文件里 CREATE TABLE 没有显式指定字符集,用记事本另存为 UTF-8 编码,并在导入前创建数据库时指定:
CREATE DATABASE roomrentsystem DEFAULT CHARACTER SET utf8mb4;第三,C# 连接串里加charset=utf8;(第 3 章已经写过)。三处都对齐后,中文基本不会再出问题。如果还乱,就用 Navicat for MySQL 的"运行 SQL 文件",它的编码处理比命令行宽容得多——Navicat 里导入时可以在向导中直接选文件编码,比反复 SET NAMES 直观。
5.3 Unable to connect to any of the specified MySQL hosts:服务、端口、密码三查
现象:最小连通测试直接弹Unable to connect to any of the specified MySQL hosts。
原因:这个报错是驱动层面的笼统提示,背后实际原因通常是三个之一:MySQL 服务没有启动;连接串端口和 MySQL 实际监听端口不一致;root 密码和连接串里写的不一致。驱动说"任何主机都连不上",其实它根本没到认证那一步。
解决:按顺序排查。第一步,services.msc打开服务管理器,找 MySQL 服务,状态为"已停止"就右键启动,并把启动类型设为自动。第二步,命令行执行mysql -u root -p能进去,说明服务和密码都对,问题就在连接串的 port 或 server 地址;如果进不去,就是密码不符。第三步,把 server 从 localhost 改成 127.0.0.1,排除 IPv6 解析问题——TCP 连接失败里这一条出现频率极高,Windows 上 localhost 常常被解析成 ::1,而 MySQL 只监听了 IPv4 的 3306。
5.4 换电脑就崩:连接串写死在代码里的后果
现象:在自己笔记本上跑得好好的,把整个项目拷到答辩机器,一点登录就连接失败。
原因:DBHelper 里连接串的 user id、password 是写死的,答辩机器上 MySQL 的 root 密码很可能不是 123456。这属于配置和代码耦合的典型问题,也是课设项目最常见的"本地能跑、异地必挂"根源。
解决:把连接串抽到 App.config,代码里用 ConfigurationManager 读取:
<connectionStrings> <add name="RoomRentDb" connectionString="server=127.0.0.1;port=3306;user id=root;password=123456;database=roomrentsystem;charset=utf8;" providerName="MySql.Data.MySqlClient" /> </connectionStrings>string connStr = System.Configuration.ConfigurationManager .ConnectionStrings["RoomRentDb"].ConnectionString;换机器时只改 App.config,不用动代码重新编译。这个改动十分钟做完,但能免掉答辩现场最尴尬的一幕——对着评委老师的电脑调试数据库连接。别忘了在项目里引用 System.Configuration 程序集,否则 ConfigurationManager 编译不过。
5.5 DataGridView 删除报错:中文别名与主键取值对应关系
现象:房源窗体点删除,抛ArgumentException: Column 房号 cannot be found,或者删除后删掉的是另一行数据。
原因:SELECT 里给列起了中文别名,代码里却用了英文列名取值;反过来用中文别名取值、但当前 DataGridView 数据源里根本没这个列,也会报同样的错。另一个隐蔽情况是 CurrentRow 取到的行和你想删的主键对不上,因为表格排序后行索引变了——ORDER BY 之后界面顺序和数据库物理顺序完全两回事。
解决:原则是"取主键永远用主键列名,不依赖显示列"。推荐做法:SELECT 里保留 house_id 原名列,在窗体设计器里把这个列 Visible 设为 false,代码里统一Cells["house_id"]取值(第 4.3 节就是这么写的)。另外删除前加确认框,防止误删:
if (MessageBox.Show("确定删除该房源吗?", "确认", MessageBoxButtons.YesNo) == DialogResult.Yes) { // 执行删除 }如果你 SELECT 用了别名且不想改,那就严格让代码里的 Cells["别名"] 和 SELECT 里的别名一一对应,并保证别名不重名。养成"主键列不显示但保留取值"的习惯后,这个坑基本就绝了。
6. 从课设到答辩:完整业务链验证与三个可落地的扩展方向
6.1 一条 SQL 验证完整业务链
系统能不能答辩,不是看界面多好看,而是看能不能讲出一条完整的业务闭环。复现完这个项目后,用下面这条 SQL 验证"签约→收租→房源状态"的数据流转是否一致:
SELECT h.house_no AS 房号, h.status AS 房源状态, c.contract_id AS 合同号, t.tenant_name AS 租户, DATE_FORMAT(c.start_date, '%Y-%m-%d') AS 租期开始, DATE_FORMAT(c.end_date, '%Y-%m-%d') AS 租期结束 FROM house h LEFT JOIN contract c ON h.house_id = c.house_id LEFT JOIN tenant t ON c.tenant_id = t.tenant_id ORDER BY h.house_id;LEFT JOIN 保证没签过约的房源也在结果里;status 为 1 的房源必须能查到对应合同,查不到就说明第 4 章的事务有问题。这条语句本身就是答辩时老师最爱问的"多表联查"演示。
6.2 扩展方向:用 MySQL 存储过程做月度收租统计
想往上提一档,把收租汇总做成存储过程是性价比最高的扩展。在 roomrentsystem.sql 里追加:
DELIMITER $$ CREATE PROCEDURE sp_monthly_income(IN month_str VARCHAR(7)) BEGIN SELECT DATE_FORMAT(pay_date, '%Y-%m') AS 月份, COUNT(*) AS 收租笔数, SUM(amount) AS 总收入 FROM payment WHERE DATE_FORMAT(pay_date, '%Y-%m') = month_str GROUP BY DATE_FORMAT(pay_date, '%Y-%m'); END$$ DELIMITER ;C# 里把 MySqlCommand 的 CommandText 设为sp_monthly_income,CommandType 设为 StoredProcedure,Add 一个名为 month_str 的参数即可。统计逻辑收进数据库,C# 只传参和展示,比在代码里拼 SUM 语句高级一个档次,也是 MySQL 存储过程在课设里最拿分的落点。
从那以后,我每次拿到课设包都强制自己先走一遍固定流程:看 SQL 脚本的表结构和字符集 → 核对驱动版本与 MySQL 版本匹配 → 最小连通测试 → 跑一条多表联查验证业务链。改数据库结构时同步更新 ER 图,改连接配置时同步更新 App.config,这套习惯帮我避开了无数个"明明照着教程做却跑不起来"的夜晚。希望帮到你。
本文还有配套的精品资源,点击获取