news 2026/9/26 0:26:03

MySQL与C#实战:房屋租赁管理系统数据库课程设计全流程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL与C#实战:房屋租赁管理系统数据库课程设计全流程解析

简介:一份面向高校数据库课程设计与毕业设计的房屋租赁管理系统完整源码包,基于C#与MySQL实现,适合正在完成管理信息系统类课题的学生作为参考。系统涵盖房屋信息、租客合同、费用结算等常见业务模块,可帮助理解C# WinForms界面开发与MySQL数据库联动的基本流程。压缩包共71个文件,大小约12.84MB,包含25个C#源码文件、项目解决方案与配置文件、数据库初始化脚本、可执行程序、界面资源文件以及设计文档,另有ER图与数据流图,可用于辅助理解数据库设计与系统架构。目前已有415人学习下载。通过这份资料,读者可以获得完整的系统源码、数据库建表脚本、课程设计报告和配套说明文档,便于对照运行演示、梳理代码逻辑,并在此基础上进行功能扩展或改造,适合作为课程设计报告撰写和答辩准备的参考资料。

1. 房屋租赁管理系统这个课设题,为什么总被数据库课程选作压轴题

数据库课程设计走到最后,十个组里有三四个都在做房屋租赁管理系统,这不是巧合。房源、租客、合同、缴费、退租,每一类数据之间都有清晰的主外键关系,天然就是一张好画的ER图;而“有空房→登记租客→签合同→收租→退租结算”这条链路,又恰好能把增删改查、事务、存储过程、报表这些数据库课设必须覆盖的点全部串起来。对要拿C#和MySQL交作业的同学来说,这套系统工作量适中、业务逻辑好讲、答辩时老师爱问的问题也有标准答案。这篇笔记从建库讲到连库,再讲到业务层和相处半学期的各种坑,照着做就能把课设从“跑不起来”推进到“能演示、能答辩”。

2. 先把MySQL底子打好:房屋租赁的表设计与初始化脚本

2.1 别急着写代码,先画清楚这五张核心表

很多同学拿到课程设计.zip之后,第一反应是解压、双击.sln、点启动,结果MySQL服务没开、数据库没导入、连接字符串写错,界面刚弹出来就报异常。我的习惯是反着来:先把数据库想清楚,再回头写C#。房屋租赁管理系统无论界面做得多花哨,最终要落地的就五张表。

第一张是房屋表,核心字段是房屋编号、地址、面积、户型、月租金、押金、状态。状态字段建议用TINYINT,0表示空闲、1表示已出租,不要用字符串“空闲”“已租”,字符串存在数据库里占空间,写错一个字就匹配不上。第二张是租客表,身份证号一定要设UNIQUE,这是业务上的天然约束,不设的话同一个身份信息能在两台电脑录进两条记录,答辩时老师一问就露馅。

第三张是合同表,它是整个系统的枢纽,外键指向房屋和租客。合同表里除了起止日期、月租、押金,最好再加一个“状态”字段,表示执行中、已退租、已到期,后续所有统计都靠这个字段过滤。第四张是缴费表,记录每一笔租金和押金的收付,字段包括金额、类型、日期、对应合同。第五张是管理员表,只有账号和密码两列,登录模块用。

提示:课设里最常见的改表方式不是因为需求复杂,而是前期少画了一个字段,后期在代码里用字符串拼接去“补逻辑”。所以画表时请把租赁业务从头走一遍,至少把“合同终止后押金退不退、水电费怎么算”这两个问题想清楚再建表。

2.2 建库脚本:从ER图到可执行的MySQL DDL

画完ER图就可以写DDL了,不要用Navicat或MySQL Workbench的图形界面一张一张建。图形界面建表虽然快,但建完无法在答辩时快速讲清楚约束关系,而且如果是MySQL 8.0版本,图形工具生成的外键命名规则看着就头疼。把脚本存成init.sql,在MySQL命令行里一次性执行,这才是课设该有的交付物。

CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE house_rental; CREATE TABLE admin ( admin_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINE=InnoDB; CREATE TABLE house ( house_id INT AUTO_INCREMENT PRIMARY KEY, house_no VARCHAR(20) NOT NULL UNIQUE COMMENT '房源编号,如A-101', address VARCHAR(100) NOT NULL, area DECIMAL(6,2) NOT NULL COMMENT '面积,单位平方米', house_type VARCHAR(20) COMMENT '户型,如两室一厅', monthly_rent DECIMAL(8,2) NOT NULL, deposit DECIMAL(8,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0空闲 1出租', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB;

房屋表是业务起点,SQL里要注意的三处:一是DECIMAL不要用FLOAT,租金这种精确数值用FLOAT会出现二进制舍入误差,打印账单时差一分钱没法解释;二是house_no设了UNIQUE,同一个小区的房号不可能重复,这是数据库替你守住的数据完整性;三是ENGINE必须指定InnoDB,只有InnoDB支持外键和事务,MyISAM没法建外键,退租结算这种多表操作没有事务就等着账目错乱。

CREATE TABLE tenant ( tenant_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL, id_card CHAR(18) NOT NULL UNIQUE COMMENT '身份证号固定18位', phone VARCHAR(20) NOT NULL, emergency_contact VARCHAR(30), emergency_phone VARCHAR(20), register_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; CREATE TABLE rental_contract ( contract_id INT AUTO_INCREMENT PRIMARY KEY, contract_no VARCHAR(30) NOT NULL UNIQUE COMMENT '合同编号,如HT20250101', 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, deposit_paid DECIMAL(8,2) NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 COMMENT '1执行中 2已退租 3已到期', CONSTRAINT fk_contract_house FOREIGN KEY (house_id) REFERENCES house(house_id), CONSTRAINT fk_contract_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(tenant_id), INDEX idx_contract_status (status) ) ENGINE=InnoDB; CREATE TABLE payment ( payment_id INT AUTO_INCREMENT PRIMARY KEY, contract_id INT NOT NULL, pay_type TINYINT NOT NULL COMMENT '1租金 2押金 3水电费', amount DECIMAL(8,2) NOT NULL, pay_date DATETIME DEFAULT CURRENT_TIMESTAMP, remark VARCHAR(100), CONSTRAINT fk_payment_contract FOREIGN KEY (contract_id) REFERENCES rental_contract(contract_id) ) ENGINE=InnoDB;

合同表和缴费表是“一主多子”的关系,一张合同对应多笔缴费记录。写外键时我建议给约束起名字,比如fk_contract_house,因为MySQL默认生成的约束名是表名加随机后缀,后期要删约束时比对着字段名猜还费劲。id_card设成CHAR(18)而不是VARCHAR(20),能同时约束长度和存储方式,答辩时被问到“身份证号为什么不用VARCHAR”能答出这个点,印象分直接拉满。

2.3 初始化数据与演示账号:让课设答辩不卡壳

表建完必须导数据。我见过太多课设系统,登录进去房源列表都是空的,老师问“这个房源能不能租?”只能当场现填表单,填完又因为日期格式不对报错,整段演示全在翻车。预置数据的核心目的不是省事,而是让演示路径可控——你先知道列表里有什么房子,才能预设“把A-101租给张三”这条演示线索。

INSERT INTO admin (username, password) VALUES ('admin', MD5('123456')); INSERT INTO house (house_no, address, area, house_type, monthly_rent, deposit, status) VALUES ('A-101', '阳光花园1栋1单元101室', 89.50, '两室一厅', 2200.00, 2200.00, 0), ('A-102', '阳光花园1栋1单元102室', 89.50, '两室一厅', 2200.00, 2200.00, 0), ('B-201', '阳光花园2栋2单元201室', 65.00, '一室一厅', 1600.00, 1600.00, 0);

密码用MD5加密是一般做法,但MD5本身不是安全的加密算法,这里是为了对齐课程里“密码不能明文存”的考点才这么写。如果你愿意多花半小时,可以在C#里用SHA256加盐代替,答辩时还能主动讲一句“我了解MD5碰撞的问题,所以用了更稳妥的做法”——这句话对拿高分非常有效。房源数建议至少插入10条,覆盖不同户型、不同租金区间,做查询功能时才有的查,统计报表时图表才不空。

注意:MySQL 8.0里MD5()函数仍然可用,但如果在初始化脚本里写INSERT,执行完最好自查一遍,用SELECT * FROM admin确认结果,因为某些图形客户端执行脚本时会把SQL语句里的中文注释转成乱码。遇到这种情况,把脚本文件另存为UTF-8无BOM格式再重新执行。

3. C# 连接 MySQL:从连接字符串到三层结构的第一个窗口

3.1 选WinForm还是WPF?课设场景下的取舍

C#做桌面端有两种主流选择,WinForm和WPF,历届学生里选WinForm的占大多数。原因是它的控件足够老旧也足够成熟,DataGridView两行代码就能绑定一张表,ComboBox下拉框拖上去就能用,对只要求“完成增删改查”的课程设计来说,开发效率极高。WPF的数据绑定需要理解DependencyProperty和INotifyPropertyChanged,学习曲线两个月起步,不适合两周交稿的课设。

但WinForm有一个先天毛病:高分屏下控件缩放模糊,答辩投影仪上一片虚影。解决办法是在Main方法入口调用SetProcessDPIAware,或者把项目清单里的DPI设置改成PerMonitorV2。你不需要理解GDI内部原理,只要知道这是WinForm在高分屏下的已知问题,加几行调用代码就能让字体清晰很多。这是课设里少有的“不增加任何功能却能让演示观感质变”的玄学操作,值得做。

另一个取舍是.NET Framework还是.NET 6/8。如果电脑上装的是Visual Studio 2022,新建项目时默认是.NET 8,但很多课设模板和老师给的范例都是.NET Framework 4.7.2。我建议跟着你电脑里已经能跑通的模板走,不要为了追新版本单独配环境,课设评审看的是业务完整性,不是运行时版本。MySQL连接库用的是MySql.Data(Connector/NET),无论哪个框架版本,用NuGet安装的方式完全一致。

3.2 DbHelper:一个能扛住课设全部功能的数据库访问基类

不管界面是登录、房源列表还是缴费流水,所有窗口访问数据库的方式都相同:拼SQL、拿连接、执行、关连接。如果不做封装,每个页面都会重复写一遍连接开关、命令参数赋值、异常处理,到后期一处改动全盘重改。所以开局建一个DbHelper基类,后面所有窗体都调用它,是这个课设最值得花时间的一件事。

using System; using System.Data; using MySql.Data.MySqlClient; public class DbHelper { public static readonly string ConnStr = "Server=localhost;Port=3306;Database=house_rental;Uid=root;Pwd=123456;" + "Charset=utf8mb4;SslMode=none;AllowPublicKeyRetrieval=true;"; public static DataTable QueryToTable(string sql, params MySqlParameter[] parameters) { using (MySqlConnection conn = new MySqlConnection(ConnStr)) { conn.Open(); using (MySqlCommand cmd = new MySqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); MySqlDataAdapter adapter = new MySqlDataAdapter(cmd); DataTable dt = new DataTable(); adapter.Fill(dt); return dt; } } } public static int ExecuteNonQuery(string sql, params MySqlParameter[] parameters) { using (MySqlConnection conn = new MySqlConnection(ConnStr)) { conn.Open(); using (MySqlCommand cmd = new MySqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteNonQuery(); } } } }

这个类解决两个问题:一是连接生命周期,using块保证连接用完后自动关闭,不会出现连接池爆满;二是参数化,params MySqlParameter[]允许上层任意传参数,SQL里写@name、@pwd之类的占位符,值通过参数传入,永远不拼字符串。关于SslMode=none和AllowPublicKeyRetrieval=true这两个参数,是MySQL 8.0本地开发最常见的组合,前者关闭SSL校验避免本地证书问题,后者允许客户端自动获取服务器公钥,缺了它会在认证握手阶段直接抛异常。

3.3 登录模块:第一道门要能过编译更要能过答辩

登录是每个评委都会亲手点一遍的入口。这里不要直接用SQL拼where username='"+textbox.Text+"',这是所有安全相关提问的靶子。用参数化查询把用户名和密码都作为参数传入,同时密码要与初始化脚本里的MD5算法对应,代码里对输入框再做一次哈希再比对。

private void btnLogin_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(txtUser.Text) || string.IsNullOrWhiteSpace(txtPwd.Text)) { MessageBox.Show("用户名和密码不能为空!"); return; } string hashed = Md5Helper.Hash(txtPwd.Text.Trim()); string sql = "SELECT admin_id FROM admin WHERE username=@u AND password=@p"; DataTable dt = DbHelper.QueryToTable(sql, new MySqlParameter("@u", txtUser.Text.Trim()), new MySqlParameter("@p", hashed)); if (dt.Rows.Count == 1) { this.Hide(); MainForm main = new MainForm(); main.Show(); } else { MessageBox.Show("用户名或密码错误"); txtPwd.Clear(); txtPwd.Focus(); } }

这段代码里两个容易被忽略的细节:一是空白输入要拦在校验之前,不然空密码的MD5值也会去数据库比对一次,虽然查不到结果但浪费一次连接;二是登录失败后只清空密码框、保留用户名,让用户少敲几个字。Md5Helper这个工具类写起来很简单,用System.Security.Cryptography的MD5.Create,对输入字符串的UTF-8字节流计算哈希,再转成小写十六进制字符串,和MySQL端MD5()生成的结果一致。答辩时老师通常会问“密码为什么是32位的一串字母数字”,顺着这个点把MD5哈希讲清楚,顺便提一句“实际生产环境会用加盐哈希”,这段对话就是你的加分环节。

4. 把租赁业务跑通:房屋管理、租客登记与退租结算

4.1 房屋列表与状态联动:一个下拉框是怎么绑定数据库的

房屋管理页面在课设里通常是“用DataGridView做增删改查”的展示点。这里最容易出现的失误是把所有查询逻辑都塞在同一个方法里,导致页面加载时执行一次、点按钮时又执行一次。正确的做法是写一个RefreshHouseList()方法,内部调用DbHelper查询当前页面的列表数据,页面Load、ComboBox切换、增删操作完成后都调用同一个方法,保证所有入口的数据一致。

private void RefreshHouseList() { string sql = "SELECT house_id, house_no, address, area, house_type, " + "monthly_rent, deposit, " + "CASE status WHEN 0 THEN '空闲' WHEN 1 THEN '已出租' END AS status_text " + "FROM house ORDER BY house_no"; DataTable dt = DbHelper.QueryToTable(sql); dgvHouse.DataSource = dt; // 状态筛选下拉框,ValueMember为数据库状态值,DisplayMember为界面显示文本 DataTable statusTable = new DataTable(); statusTable.Columns.Add("status", typeof(int)); statusTable.Columns.Add("text", typeof(string)); statusTable.Rows.Add(0, "空闲"); statusTable.Rows.Add(1, "已出租"); cmbFilter.DataSource = statusTable; cmbFilter.ValueMember = "status"; cmbFilter.DisplayMember = "text"; }

DataGridView绑定DataTable之后,列头默认会显示数据库字段名,比如house_no、monthly_rent,很生硬。可以在表设计器里把这两列改成“房号”“月租金”,也可以在SQL里用别名直接改。我个人推荐SQL别名方案,因为查询结果统一由SQL层控制,改起来不用动窗体设计器。状态字段在数据库里是0和1,界面上要显示“空闲/已出租”,这里用了CASE WHEN把整数翻译成文字,比在C#代码里循环遍历DataTable去转换要简单,一行SQL就解决了。

注意:不能直接用cmbFilter.SelectedValue拿状态值再拼WHERE条件,“SELECT * FROM house WHERE status=" + cmbFilter.SelectedValue这种写法会出现两个问题——SelectedValue可能为null,导致SQL多出一个“= ”语法错误;同时它把用户输入拼进SQL,违背了前面DbHelper封装参数化的初衷。正确写法是把SelectedValue先Convert.ToInt32转成int变量,再作为MySqlParameter传入。

4.2 签订合同:用事务把一次签约拆成三步

房屋从“空闲”变“已出租”,不是改一个状态位就行,而是要同时完成三件事:插入合同记录、写入一笔押金缴费记录、把房屋表状态改成1。这三步中的任何一步失败,数据库都会处于“合同建了但房子还是空闲”或“房子已出租但没有合同”的矛盾状态。单条UPDATE或INSERT不会出问题,但三步合起来必须用事务包裹。

public bool SignContract(int houseId, int tenantId, DateTime startDate, DateTime endDate, decimal monthlyRent, decimal deposit) { string contractNo = "HT" + DateTime.Now.ToString("yyyyMMddHHmmss"); string sqlInsert = @"INSERT INTO rental_contract (contract_no, house_id, tenant_id, start_date, end_date, monthly_rent, deposit_paid, status) VALUES (@contractNo, @houseId, @tenantId, @startDate, @endDate, @monthlyRent, @deposit, 1)"; string sqlPayment = @"INSERT INTO payment (contract_id, pay_type, amount, remark) VALUES (@contractId, 2, @deposit, '签约押金')"; string sqlUpdateHouse = "UPDATE house SET status=1 WHERE house_id=@houseId AND status=0"; using (MySqlConnection conn = new MySqlConnection(DbHelper.ConnStr)) { conn.Open(); using (MySqlTransaction trans = conn.BeginTransaction()) { try { // 第一步:插入合同并获得自增ID long contractId; using (MySqlCommand cmd1 = new MySqlCommand(sqlInsert, conn, trans)) { cmd1.Parameters.Add(new MySqlParameter("@contractNo", contractNo)); cmd1.Parameters.Add(new MySqlParameter("@houseId", houseId)); cmd1.Parameters.Add(new MySqlParameter("@tenantId", tenantId)); cmd1.Parameters.Add(new MySqlParameter("@startDate", startDate)); cmd1.Parameters.Add(new MySqlParameter("@endDate", endDate)); cmd1.Parameters.Add(new MySqlParameter("@monthlyRent", monthlyRent)); cmd1.Parameters.Add(new MySqlParameter("@deposit", deposit)); cmd1.ExecuteNonQuery(); contractId = cmd1.LastInsertedId; } // 第二步:写入押金流水 using (MySqlCommand cmd2 = new MySqlCommand(sqlPayment, conn, trans)) { cmd2.Parameters.Add(new MySqlParameter("@contractId", contractId)); cmd2.Parameters.Add(new MySqlParameter("@deposit", deposit)); cmd2.ExecuteNonQuery(); } // 第三步:把房屋改为已出租,加AND status=0防止重复签约 using (MySqlCommand cmd3 = new MySqlCommand(sqlUpdateHouse, conn, trans)) { cmd3.Parameters.Add(new MySqlParameter("@houseId", houseId)); int affected = cmd3.ExecuteNonQuery(); if (affected == 0) throw new Exception("房屋可能已被他人签约,请刷新后重试"); } trans.Commit(); return true; } catch { trans.Rollback(); throw; } } } }

事务代码的要点是同一个连接必须传给所有Command对象和BeginTransaction的返回值,cmd1、cmd2、cmd3这三个命令对象不能各自new一个MySqlConnection,那样事务就不起作用了,因为每个连接是独立会话。cmd1.LastInsertedId这个属性是MySql.Data库特有的,执行完INSERT后自动拿到自增主键的值,不需要再回查数据库。UPDATE house加AND status=0这行很重要,它用一行代码解决并发问题:如果两个人同时签约同一间房,先执行的人更新成功,后执行的人影响行数为0,直接抛异常,比“先SELECT再UPDATE”的检查后执行逻辑安全得多。

4.3 退租结算:计算器、流水记录与坑点

退租是课设业务链的最后一环,也是最容易出逻辑漏洞的地方。退租行为同时包含:把合同状态改成已退租、把房屋状态改回空闲、根据是否到期计算违约金或押金退还、把退还金额记录进缴费表。很多学生只做前两步,押金退还完全不走数据库,直接在界面上扣了个数字就完事,答辩时老师让看流水,这里一片空白,整段业务逻辑就断了。

private void btnDoReturn_Click(object sender, EventArgs e) { if (dgvContract.SelectedRows.Count == 0) return; int contractId = Convert.ToInt32(dgvContract.SelectedRows[0].Cells["contract_id"].Value); int houseId = Convert.ToInt32(dgvContract.SelectedRows[0].Cells["house_id"].Value); decimal deposit = Convert.ToDecimal(dgvContract.SelectedRows[0].Cells["deposit_paid"].Value); DateTime endDate = Convert.ToDateTime(dgvContract.SelectedRows[0].Cells["end_date"].Value); bool overdue = DateTime.Now.Date > endDate.Date; // 逾期退租扣除全部押金作为违约金,这是课设里的常见简化口径 decimal refund = overdue ? 0m : deposit; string sql1 = "UPDATE rental_contract SET status=2 WHERE contract_id=@contractId AND status=1"; string sql2 = "UPDATE house SET status=0 WHERE house_id=@houseId"; string sql3 = @"INSERT INTO payment (contract_id, pay_type, amount, remark) VALUES (@contractId, 2, @refund, '退租退押金')"; using (MySqlConnection conn = new MySqlConnection(DbHelper.ConnStr)) { conn.Open(); using (MySqlTransaction trans = conn.BeginTransaction()) { try { using (MySqlCommand cmd1 = new MySqlCommand(sql1, conn, trans)) { cmd1.Parameters.Add(new MySqlParameter("@contractId", contractId)); cmd1.ExecuteNonQuery(); } using (MySqlCommand cmd2 = new MySqlCommand(sql2, conn, trans)) { cmd2.Parameters.Add(new MySqlParameter("@houseId", houseId)); cmd2.ExecuteNonQuery(); } using (MySqlCommand cmd3 = new MySqlCommand(sql3, conn, trans)) { cmd3.Parameters.Add(new MySqlParameter("@contractId", contractId)); cmd3.Parameters.Add(new MySqlParameter("@refund", refund)); cmd3.ExecuteNonQuery(); } trans.Commit(); MessageBox.Show("退租结算完成,退还押金:" + refund.ToString("F2")); } catch (Exception ex) { trans.Rollback(); MessageBox.Show("退租失败:" + ex.Message); } } } }

退租逻辑里的两个坑值得单独说。第一是日期比较不能用“>= endDate”草草了事,DateTime.Now带时分秒,而endDate是DATE类型,凌晨零点到当天午夜之间,必须用DateTime.Now.Date和endDate.Date做同量纲比较。第二是违约金的口径要在界面和说明文档里讲清楚——这里定义成“逾期一天即扣除全部押金”只是课设简化口径,真实业务通常按天比例计算。你只要在代码注释和答辩PPT里把口径写明白,老师不会纠结比例是否合理,最怕的是代码里写了扣除、界面却不显示扣除明细,前后对不上账。

5. 课设避坑指南:从连接失败到删除被拒的5个现场

5.1 MySQL 8.0 连不上:caching_sha2_password 引发的连锁反应

现象:代码里明明数据库名、账号、密码全是对的,一运行就在DbHelper.QueryToTable里抛异常,报错信息带“Authentication method 'caching_sha2_password' not supported”或者“Unable to connect to any of the specified MySQL hosts”。

原因:MySQL 8.0默认把root用户的认证插件从mysql_native_password改成了caching_sha2_password,而老版本的MySql.Data.dll连接器不认这个新插件,双方握手失败。问题不在密码,在认证协议。

解决:有两条路。第一条是在DbHelper的ConnStr里加SslMode=none和AllowPublicKeyRetrieval=true,新版Connector/NET支持公钥检索后就能通过认证;第二条是降级认证插件,在MySQL命令行执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'。课设场景推荐第一条,因为不用动数据库用户配置,只改代码里的连接字符串就行。

5.2 中文全部变成问号:utf8 和 utf8mb4 的差异不能靠运气

现象:C#界面里录入“张三”,存进数据库或者从数据库读出来显示成了“???”。有时初始化脚本里明明写了DEFAULT CHARACTER SET utf8mb4,插入的中文还是乱码。

原因:乱码链条有三个环节,任何一个环节断了都乱——数据库和表的字符集、C#连接字符串的Charset参数、MySQL客户端导入脚本时的文件编码。最常见的断点在连接字符串:Charset=utf8mb4写对了,但表本身是utf8mb4,两边不一致时连接层会做一次转码,转完就丢了信息。

解决:三层全部对齐。建库脚本统一用utf8mb4,连接字符串写Charset=utf8mb4,同时确认NuGet里MySql.Data的版本在8.0.22以上。最后用SELECT直接查表内容验证一次,不要只看界面显示正常就收工,因为有些控件做了自动编码转换,界面正常但库里存的是乱码,答辩时打开表看数据就翻车。

5.3 外键约束报错:删除房屋时提示不能删除

现象:在房屋管理页面点删除一条房源记录,直接弹“Cannot delete or update a parent row: a foreign key constraint fails”,数据库没删成,界面却弹了删除成功。

原因:这条房源已经被签过合同,合同表里存在外键指向它。外键约束要求子表没有引用记录时父表才能执行DELETE,否则MySQL拒绝删除。界面上“删除影响行数=1”的提示没有与数据库异常挂钩,异常被吞掉了。

解决:删房屋前先查合同表,有合同记录的房源只做“逻辑删除”而不是物理删除。逻辑删除的做法是在house表加一个deleted字段,删除操作变成UPDATE house SET deleted=1;没有合同引用的房源才走DELETE。这个点答辩时被问到的概率极高,你把“保留历史合同、只做逻辑删除”这个理由讲出来,比解释SQL错误强得多。

5.4 日期时间到处飘:DateTimePicker 默认值导致的隐藏 bug

现象:程序偶尔在“签合同”或“退租结算”时报参数异常,或者在DataGridView里日期列显示“0001/1/1”,存进去再读出来对不上。

原因:WinForm里DateTimePicker控件在正常情况下默认值是当前时间,但如果页面加载时把Format设为Custom而没给Value赋初值,某些初始化顺序下Value会变成DateTime.MinValue(0001-01-01),这个值超出MySQL DATE类型的合法范围(1000-01-01起),插入时直接被MySQL拒绝。

解决:在窗体构造函数里显式给所有DateTimePicker赋初值,例如startDatePicker.Value = DateTime.Today,endDatePicker.Value = DateTime.Today.AddMonths(6)。另外约定所有从DataGridView取日期时用Convert.ToDateTime而不是直接强转,Convert方法遇到DBNull会返回默认值而不是抛异常,至少能让错误在界面层暴露而不是炸在SQL执行层。

5.5 数据库文件拷不走:课设验收前的最后一道坎

现象:把项目复制到另一台电脑上演示,打开登录界面就报“Can't connect to MySQL server on 'localhost'”,检查服务开了、账号密码也没变,还是连不上。

原因:你本机的MySQL服务虽然装了,但另一台电脑上根本没有这个数据库实例和数据,你拷走的只是代码和编译输出,数据库还在旧机器的数据目录里待着。

解决:课设验收前做一个标准动作——导出完整SQL脚本。在Navicat或MySQL Workbench里选择“转储SQL文件”,包含表结构、索引、外键和预置数据,另存为init.sql,和项目源码放在同一目录。换机器演示时,先安装MySQL服务,再导入init.sql,数据库即恢复完整。目录结构建议做成CourseDesign_HouseRental,里面放.sln、init.sql、README,README里写清楚MySQL版本要求、连接字符串、默认管理员账号,这份材料本身就是课设报告的一部分。

6. 把课设从“能用”变成“加分”:三处不改结构的小升级

到这一步,系统已经能跑通“登录—录房源—登记租客—签约—收租—退租”的完整链路,但课设想拿高分,还需要在答辩时让评委看到“结构之外的思考”。我建议从三处不动表结构的升级里选一个去做。

第一处是给缴费统计加一个“本月应收”视图。用一条SQL把执行中合同的月租金汇总,和当月实收流水作对比,做成一个只读的DataGridView。这一步没有改任何表,只是在列表页加了一个分组查询,却能让老师看到你理解“数据从业务中来、再回到报表中去”这条主线。第二处是把状态筛选下拉框做成联动搜索,按“空闲/已出租”筛选房源,配合前面的CASE WHEN显示文字,再给DataGridView加一个行背景色,空闲的房源显示浅绿色,已出租的显示浅灰色。颜色逻辑放在DataGridView的RowPostPaint事件里,代码量不超过十行,演示效果却很直观。

第三处是写一个可以反复执行的“模拟月结”功能,把当月租金批量生成缴费记录。这个点实现起来涉及循环和日期计算,但业务路径非常清晰:遍历所有status=1的合同、把月租写入payment表、备注写“自动月结”。做出来后,答辩时你可以现场演示“模拟两个月后”的报表,老师往数据库里一看账目是完整的,而不是只有登录页和空空的流水,这套完整性演示在课设答辩里很能说服人。我的习惯是把这些升级都放在一个独立面板里,叫“演示功能”,和课设要求的CRUD页面分开,这样评委能明确看出哪些是课程要求的、哪些是你主动做的扩展。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 0:17:54

Clawdbot 配置豆包1.8模型:settings.json 骨架与连通性验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 0:13:12

UE5多人FPS网络同步实战:客户端预测与服务器回滚机制详解

1. 多人FPS网络同步的整体设计思路1.1 为什么FPS游戏的网络同步这么难做UE5的多人FPS网络同步,说白了就是让几个甚至几十个玩家在各自的屏幕上看到大致一样的战场画面,同时保证每个人的操作都能被服务器认可并广播给其他人。这件事听起来简单&#xff0c…

作者头像 李华
网站建设 2026/9/26 0:10:50

华为昇腾Atlas 300V推理卡上部署YOLOv5全流程详解

“atlas”这个项目标题配合上热搜词来看,明显是指华为昇腾的Atlas系列。不少朋友在二手平台看到“Atlas 300V 24G”,第一反应是“这玩意是不是个大显存的运算加速卡,能不能捡漏”。这个问题的答案,对也不对。说它对,因…

作者头像 李华
网站建设 2026/9/26 0:09:47

大模型如何让智能家居从执行器变成决策者:架构与实操

1. 从一个真实场景说起:为什么大家都在问这个问题去年年底我在做一个全屋智能改造项目,业主是一位四十多岁的企业主,家里装了大概六十多个智能设备节点,灯光、窗帘、空调、地暖、新风、安防、影音全都接进了中控系统。验收那天他站…

作者头像 李华