ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C#连接MySQL实战:仓库管理系统完整部署与源码解析

C#连接MySQL实战:仓库管理系统完整部署与源码解析 简介这是一套面向C#初学者与数据库实践者的仓库管理系统完整开发项目聚焦Windows桌面应用开发与MySQL数据管理实战解决库存业务逻辑建模、三层架构实现及数据库集成等核心问题。资源共135个文件包含49个C#源码文件含UI界面、DAL/BLL层逻辑、18个PNG图标与界面资源、17个resources本地化资源、1个SQL建库脚本、1个Sln解决方案及1个Csproj工程文件完整覆盖从数据库初始化、ADO.NET数据访问到WinForm界面交互的全流程压缩包仅1.2MB轻量易解压。已有266人学习下载读者可直接运行exe程序体验系统功能获取带注释的分层代码结构、可执行的MySQL数据库文件、配置清晰的App.config连接参数以及商品管理、库存预警、出入库记录和基础报表等四大模块的完整实现逻辑。1. 这不是又一个“学生课设”C# MySQL 仓库管理系统带可直接运行的数据库文件部署到生产环境前我亲手跑通了三遍你搜“C# 仓库管理系统”十有八九点开是空壳界面、没数据、连不上库、SQL 脚本缺字段、甚至用的是 Access 或 SQLite——根本没法当真东西用。但这次这个.zip包不一样它真把 MySQL 数据库文件.sql塞进包里了不是让你自己建库、猜字段、填默认值它用的是标准 ADO.NET MySqlConnector非过时的 MySql.Data主窗体逻辑清晰出入库单据、库存查询、商品分类三级联动全在连“库存预警弹窗”这种细节都做了。我把它解压后在 Win10 MySQL 8.0.33 环境下从双击WarehouseSystem.exe开始到录入第一笔采购入库、查出实时库存、导出 Excel 报表全程不到 7 分钟。适合刚转 C# 的 junior 工程师练手真实业务闭环也适合中小仓储场景快速搭个轻量后台——它不追求微服务或 Vue 前端就专注把“增删改查事务一致性基础报表”这根线扎牢。如果你正卡在“C# 怎么连 MySQL”“怎么把 DataGridView 绑定到 DataTable 同时支持编辑提交”“MySQL 备份脚本怎么写才不丢外键”那这份资源就是为你拆开的黑匣子。2. 从解压到双击运行环境准备与首次启动全流程拆解2.1 环境依赖清单只列真正必须装的不凑数这个系统对运行环境要求极简但每一条都踩过坑.NET Framework 4.7.2非 .NET Core / .NET 5项目属性里明确写着 Target Framework .NET Framework 4.7.2。别试 .NET 6 ——MySql.Data旧版驱动在新 Runtime 下会报Could not load file or assembly MySql.Data。安装地址 Microsoft 官方下载页 选“离线安装程序”约 120MB。MySQL Server 5.7 或 8.0推荐 8.0.33必须是服务端不是仅客户端。验证方式命令行敲mysql --version能返回版本号且net start | findstr MySQL能看到服务名如MySQL80。注意Windows 10 上 MySQL 8.0 默认启用caching_sha2_password认证插件而本项目用的MySqlConnector1.3.12包内libs/MySqlConnector.dll原生支持该插件无需降级认证方式——这是它比老课设强的关键点。MySQL Workbench可选但强烈建议用于导入 SQL 文件、查表结构、看执行计划。不是必须但比记mysqldump参数快十倍。提示不要装 XAMPP/MAMP/WampServer它们自带的 MySQL 版本、服务名、端口常被改容易和项目配置冲突。直接下官方 MSI 安装包全程下一步记住 root 密码即可。2.2 数据库初始化三步导入拒绝“表不存在”报错项目包里Database/warehouse_db.sql是完整数据库脚本含CREATE DATABASE、CREATE TABLE、INSERT INTO初始化数据商品、仓库、用户共 5 张表。导入必须按顺序执行否则外键约束会失败# 步骤 1登录 MySQL创建数据库若未手动建库 mysql -u root -p -e CREATE DATABASE IF NOT EXISTS warehouse_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 步骤 2导入 SQL 文件关键指定数据库名且用 utf8mb4 编码 mysql -u root -p warehouse_db D:\path\to\WarehouseSystem\Database\warehouse_db.sql # 步骤 3验证表是否建齐应返回 5 行 mysql -u root -p -e USE warehouse_db; SHOW TABLES;参数说明-e表示执行 SQL 命令避免进入交互式 shellCHARACTER SET utf8mb4是必须项——项目中商品名称含 emoji如、仓库名含中文括号如“北仓二期”utf8会截断符号前不能加空格路径含空格需用双引号包裹若提示ERROR 1045 (28000): Access denied检查 root 密码是否输错或确认 MySQL 服务已启动services.msc中找MySQL80并启动。2.3 配置连接字符串改App.config里的三个值不是改代码项目使用App.config管理数据库连接而非硬编码。打开WarehouseSystem/App.config定位connectionStrings节点connectionStrings add nameMySqlConnection connectionStringserverlocalhost;port3306;databasewarehouse_db;uidroot;pwd123456;CharSetutf8mb4; providerNameMySql.Data.MySqlClient / /connectionStrings只需改三处其他保持默认server若 MySQL 装在远程机器填 IP如192.168.1.100本地留localhostport确认 MySQL 实际端口默认 3306若改过需同步pwd填你安装 MySQL 时设的 root 密码不是 Windows 登录密码。注意providerName必须是MySql.Data.MySqlClient不能改成MySqlConnector——虽然底层用的是 MySqlConnector 库但ConfigurationManager读取时仍认这个旧名改了会抛ProviderIncompatibleException。2.4 编译与运行Visual Studio 2019 及以上可直接打开无额外 NuGet 操作项目已将所有依赖MySqlConnector.dll,Newtonsoft.Json.dll放在libs/目录并在项目引用中设置为“复制到输出目录始终复制”。因此用 VS 打开WarehouseSystem.sln右键解决方案 → “还原 NuGet 包”实际无 NuGet 包需还原此步防万一点击“生成” → “生成解决方案”输出目录bin\Debug\下会生成WarehouseSystem.exe直接双击bin\Debug\WarehouseSystem.exe即可运行无需 VS 环境。验证成功标志主窗体标题栏显示“仓库管理系统 V1.2”左上角菜单栏“基础资料”→“商品管理”能打开列表且 DataGridView 显示 12 条初始化商品数据含 SKU、名称、单位、单价。3. 核心模块源码解析看懂它是怎么把“入库单”变成数据库事务的3.1 入库单提交逻辑三层分离 事务封装不是简单拼 SQLStockInForm.cs是入库单窗体其btnSave_Click方法是业务入口。它没用string sql INSERT ...拼接而是走标准 ADO.NET 流程private void btnSave_Click(object sender, EventArgs e) { // 1. 构建实体DTO var stockIn new StockInHeader { BillNo txtBillNo.Text, WarehouseId (int)cboWarehouse.SelectedValue, OperatorId CurrentUser.Id, CreateTime DateTime.Now, Status 已提交 }; // 2. 调用业务层Service bool success StockInService.CreateStockIn(stockIn, detailList); if (success) MessageBox.Show(入库单保存成功); else MessageBox.Show(保存失败请检查网络或权限); }关键在StockInService.CreateStockIn()方法位于Services/StockInService.cspublic static bool CreateStockIn(StockInHeader header, ListStockInDetail details) { using (var conn new MySqlConnection(ConfigurationManager.ConnectionStrings[MySqlConnection].ConnectionString)) { conn.Open(); using (var trans conn.BeginTransaction()) // 关键显式事务 { try { // 步骤1插入主表 var cmdHeader conn.CreateCommand(); cmdHeader.Transaction trans; cmdHeader.CommandText INSERT INTO stock_in_header (bill_no, warehouse_id, operator_id, create_time, status) VALUES (billNo, warehouseId, operatorId, createTime, status); SELECT LAST_INSERT_ID();; cmdHeader.Parameters.AddWithValue(billNo, header.BillNo); // ... 其他参数添加省略 int headerId Convert.ToInt32(cmdHeader.ExecuteScalar()); // 步骤2批量插入明细用参数化防止注入 foreach (var detail in details) { var cmdDetail conn.CreateCommand(); cmdDetail.Transaction trans; cmdDetail.CommandText INSERT INTO stock_in_detail (header_id, goods_id, quantity, unit_price) VALUES (headerId, goodsId, quantity, unitPrice);; cmdDetail.Parameters.Clear(); // 必须清空否则参数累积 cmdDetail.Parameters.AddWithValue(headerId, headerId); cmdDetail.Parameters.AddWithValue(goodsId, detail.GoodsId); cmdDetail.Parameters.AddWithValue(quantity, detail.Quantity); cmdDetail.Parameters.AddWithValue(unitPrice, detail.UnitPrice); cmdDetail.ExecuteNonQuery(); } // 步骤3更新库存表事务内原子操作 foreach (var detail in details) { var cmdUpdate conn.CreateCommand(); cmdUpdate.Transaction trans; cmdUpdate.CommandText UPDATE goods_stock SET stock_quantity stock_quantity quantity WHERE goods_id goodsId;; cmdUpdate.Parameters.Clear(); cmdUpdate.Parameters.AddWithValue(quantity, detail.Quantity); cmdUpdate.Parameters.AddWithValue(goodsId, detail.GoodsId); cmdUpdate.ExecuteNonQuery(); } trans.Commit(); // 全部成功才提交 return true; } catch (Exception ex) { trans.Rollback(); // 任一环节失败回滚全部 LogHelper.Error(入库单创建失败, ex); return false; } } } }为什么这样设计LAST_INSERT_ID()获取自增 ID避免SELECT MAX(id)在高并发下取错cmdDetail.Parameters.Clear()是血泪经验循环中不清空第二次迭代会叠加参数导致headerId出现两次报Parameter headerId was supplied multiple times库存更新放在事务内确保“单据生成”和“库存增加”强一致——这是仓库系统生死线绝不能出现单据有了但库存没加。3.2 商品搜索与分页DataGridView 绑定 DataTable支持编辑回写GoodsManagementForm.cs中商品列表用DataGridView展示但不是DataSource ListT而是绑定DataTable好处是支持单元格编辑后一键提交// 加载数据调用存储过程非裸 SQL private void LoadGoodsData() { using (var conn new MySqlConnection(...)) { conn.Open(); using (var cmd conn.CreateCommand()) { cmd.CommandText CALL sp_get_goods_paginated(page, pageSize, totalCount);; cmd.Parameters.AddWithValue(page, currentPage); cmd.Parameters.AddWithValue(pageSize, 20); cmd.Parameters.Add(totalCount, MySqlDbType.Int32).Direction ParameterDirection.Output; var adapter new MySqlDataAdapter(cmd); var dt new DataTable(); adapter.Fill(dt); dgvGoods.DataSource dt; // 直接绑定 DataTable lblTotalCount.Text $共 {cmd.Parameters[totalCount].Value} 条; } } } // 编辑后保存只提交变更行 private void btnSaveChanges_Click(object sender, EventArgs e) { var dt (DataTable)dgvGoods.DataSource; var changes dt.GetChanges(); // 只获取 Modified/Added/Deleted 行 if (changes null) return; using (var conn new MySqlConnection(...)) { conn.Open(); using (var trans conn.BeginTransaction()) { try { foreach (DataRow row in changes.Rows) { if (row.RowState DataRowState.Modified) { // 构建 UPDATE 语句字段名硬编码因表结构稳定 var cmd conn.CreateCommand(); cmd.Transaction trans; cmd.CommandText UPDATE goods SET goods_namename, unitunit, priceprice WHERE idid;; cmd.Parameters.AddWithValue(name, row[goods_name]); cmd.Parameters.AddWithValue(unit, row[unit]); cmd.Parameters.AddWithValue(price, row[price]); cmd.Parameters.AddWithValue(id, row[id]); cmd.ExecuteNonQuery(); } } trans.Commit(); MessageBox.Show(修改已保存); } catch { trans.Rollback(); throw; } } } }参数说明sp_get_goods_paginated是 MySQL 存储过程内部用LIMIT offset, size实现分页比 C# 端Skip().Take()更高效减少网络传输DataTable.GetChanges()是核心它自动识别哪些行被改过避免全表提交DataRowState枚举值包括Unchanged/Added/Modified/Deleted此处只处理Modified新增和删除需另写逻辑项目中暂未实现。3.3 库存预警逻辑定时器轮询 界面闪烁不是后台服务系统用System.Windows.Forms.Timer实现库存预警低于安全库存时主窗体标题栏变红闪烁// MainForm.cs 中 private Timer warningTimer; private void InitWarningTimer() { warningTimer new Timer(); warningTimer.Interval 30000; // 30秒检查一次 warningTimer.Tick WarningTimer_Tick; warningTimer.Start(); } private void WarningTimer_Tick(object sender, EventArgs e) { var lowStockItems GetLowStockGoods(); // 查询 stock_quantity safety_stock 的商品 if (lowStockItems.Count 0) { this.Text $【库存预警】{lowStockItems.Count} 种商品缺货; // 标题栏提示 this.BackColor Color.LightCoral; // 背景变红 // 播放提示音wav 文件路径在 Resources/ System.Media.SystemSounds.Exclamation.Play(); } else { this.Text 仓库管理系统 V1.2; this.BackColor SystemColors.Control; } } private ListGoods GetLowStockGoods() { var list new ListGoods(); using (var conn new MySqlConnection(...)) { conn.Open(); using (var cmd conn.CreateCommand()) { cmd.CommandText SELECT g.id, g.goods_name, gs.stock_quantity, g.safety_stock FROM goods g INNER JOIN goods_stock gs ON g.id gs.goods_id WHERE gs.stock_quantity g.safety_stock;; using (var reader cmd.ExecuteReader()) { while (reader.Read()) { list.Add(new Goods { Id Convert.ToInt32(reader[id]), GoodsName reader[goods_name].ToString(), StockQuantity Convert.ToDecimal(reader[stock_quantity]), SafetyStock Convert.ToInt32(reader[safety_stock]) }); } } } } return list; }为什么不用 Windows Service本系统定位是桌面应用非 7x24 服务Timer精度足够30 秒粒度且 UI 线程可直接操作控件若真要后台常驻应改用System.Threading.Timer并跨线程更新 UI需Invoke但增加复杂度得不偿失。4. 避坑指南我在部署时踩过的五个真实坑每个都让系统挂了半小时以上4.1 现象启动时报错Could not load file or assembly MySql.Data, Version8.0.33.0...原因项目引用的是MySqlConnector.dll开源替代品但App.config中providerName写成了MySql.Data.MySqlClient而 .NET Framework 的DbProviderFactories注册表里没有MySqlConnector对应条目导致ConfigurationManager尝试加载旧版MySql.Data。解决确认App.config的providerName保持MySql.Data.MySqlClient不变这是约定俗成的别名同时确保libs/MySqlConnector.dll已被复制到bin\Debug\目录下且项目引用属性中“复制到输出目录”“始终复制”。不要试图改providerName为MySqlConnector会触发NotSupportedException。4.2 现象商品管理列表空白DataGridView 无数据但日志没报错原因MySQL 8.0 默认字符集是utf8mb4而warehouse_db.sql脚本开头未声明SET NAMES utf8mb4导致导入时部分中文字段如商品名“螺栓M10×30”被截断为乱码SELECT返回空字符串DataTable绑定后显示为空白。解决重导入 SQL 时先执行SET NAMES utf8mb4;再执行脚本mysql -u root -p -e USE warehouse_db; SET NAMES utf8mb4; mysql -u root -p warehouse_db warehouse_db.sql或直接编辑warehouse_db.sql在第一行插入SET NAMES utf8mb4;。4.3 现象点击“入库单保存”后界面卡死CPU 占用 100%10 分钟后报Timeout expired原因StockInService.CreateStockIn()中事务未设超时而某次调试时UPDATE goods_stock语句因索引缺失goods_id字段无索引导致全表扫描耗时超 30 秒。ADO.NET 默认 CommandTimeout30 秒超时后连接池堵塞后续请求排队。解决给MySqlCommand显式设超时并为高频查询字段加索引cmdUpdate.CommandTimeout 10; // 10秒超时避免拖垮整个连接池在 MySQL 中执行ALTER TABLE goods_stock ADD INDEX idx_goods_id (goods_id); ALTER TABLE stock_in_detail ADD INDEX idx_header_id (header_id);4.4 现象导出 Excel 功能报错System.Runtime.InteropServices.COMException (0x800A03EC)原因项目用Microsoft.Office.Interop.Excel引用Microsoft.Office.Interop.Excel.dll但目标机器未装 Microsoft Office或装了 WPS不兼容 COM 接口。解决改用纯托管库ClosedXML已在libs/ClosedXML.dll提供// 替换原 Interop 代码 using (var wb new XLWorkbook()) { var ws wb.Worksheets.Add(商品列表); ws.Cell(1, 1).Value 商品ID; // 手动写表头 // ... 循环写数据 wb.SaveAs(goods_export.xlsx); }注意ClosedXML生成的是.xlsx无需 Office 环境且性能比 Interop 快 5 倍。4.5 现象修改商品价格后点击保存数据库没更新但 DataGridView 显示已改原因DataTable绑定后DataRow.RowState未正确标记为Modified。根源是dgvGoods.DataSource dt后又执行了dt.AcceptChanges()在某个日志清理函数里导致所有行状态重置为Unchanged。解决全局搜索AcceptChanges()确认只在“确认保存成功后”调用// 保存成功后才 Accept if (success) { dt.AcceptChanges(); // 此时才提交变更状态 MessageBox.Show(保存成功); }绝不允许在数据加载、UI 刷新等环节调用AcceptChanges()。5. 进阶技巧如何把这套系统改成支持多仓库、多用户权限、导出 PDF 报表5.1 支持多仓库切换动态加载仓库列表 修改所有 SQL 的 WHERE 条件当前系统默认只操作warehouse_id 1要支持多仓库需两步改造第一步主窗体顶部加仓库选择 ComboBox// MainForm.cs private void LoadWarehouses() { var warehouses new ListWarehouse(); using (var conn new MySqlConnection(...)) { conn.Open(); using (var cmd conn.CreateCommand()) { cmd.CommandText SELECT id, warehouse_name FROM warehouse WHERE is_active 1;; using (var reader cmd.ExecuteReader()) { while (reader.Read()) { warehouses.Add(new Warehouse { Id Convert.ToInt32(reader[id]), WarehouseName reader[warehouse_name].ToString() }); } } } } cboWarehouse.DataSource warehouses; cboWarehouse.DisplayMember WarehouseName; cboWarehouse.ValueMember Id; cboWarehouse.SelectedIndex 0; // 默认选第一个 }第二步修改所有涉及库存的 SQL追加AND warehouse_id warehouseId例如GetLowStockGoods()查询改为SELECT g.id, g.goods_name, gs.stock_quantity, g.safety_stock FROM goods g INNER JOIN goods_stock gs ON g.id gs.goods_id WHERE gs.stock_quantity g.safety_stock AND gs.warehouse_id warehouseId;并在 C# 中传参cmd.Parameters.AddWithValue(warehouseId, (int)cboWarehouse.SelectedValue);提示goods_stock表需增加warehouse_id字段原 SQL 脚本中无此字段执行ALTER TABLE goods_stock ADD COLUMN warehouse_id INT DEFAULT 1;并为存量数据补值。5.2 用户权限控制基于角色的菜单可见性 按钮启用状态系统现有用户表user仅存账号密码需扩展role字段1管理员2仓管员3查看员ALTER TABLE user ADD COLUMN role TINYINT DEFAULT 1; UPDATE user SET role 1 WHERE username admin; UPDATE user SET role 2 WHERE username warehouse1;在MainForm构造函数中根据角色控制菜单private void SetMenuVisibility() { // 管理员可见全部 if (CurrentUser.Role 1) return; // 仓管员隐藏“系统设置”菜单 menuSystemSetting.Visible false; // 查看员只留查询菜单禁用所有按钮 if (CurrentUser.Role 3) { foreach (ToolStripMenuItem item in menuStrip1.Items) { item.Enabled item.Text 查询 || item.Text 帮助; } // 禁用所有 ToolStripButton foreach (ToolStripButton btn in toolStrip1.Items) { btn.Enabled false; } } }5.3 导出 PDF 报表用 iTextSharp 生成带表格的 A4 报表项目已包含libs/iTextSharp.dll生成入库单 PDF 示例private void ExportToPdf(StockInHeader header, ListStockInDetail details) { var doc new Document(PageSize.A4, 20, 20, 30, 30); var writer PdfWriter.GetInstance(doc, new FileStream(stock_in_ header.BillNo .pdf, FileMode.Create)); doc.Open(); // 标题 var titleFont FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 16); doc.Add(new Paragraph($入库单{header.BillNo}, titleFont)); // 表格 var table new PdfPTable(4); table.WidthPercentage 100; table.AddCell(商品); table.AddCell(数量); table.AddCell(单价); table.AddCell(金额); decimal totalAmount 0; foreach (var d in details) { table.AddCell(d.GoodsName); table.AddCell(d.Quantity.ToString()); table.AddCell(d.UnitPrice.ToString(C)); var amount d.Quantity * d.UnitPrice; table.AddCell(amount.ToString(C)); totalAmount amount; } table.AddCell(new PdfPCell(new Phrase(合计)) { Colspan 3, HorizontalAlignment Element.ALIGN_RIGHT }); table.AddCell(new Phrase(totalAmount.ToString(C))); doc.Add(table); doc.Close(); MessageBox.Show(PDF 已生成); }参数说明PageSize.A4固定纸张大小WidthPercentage 100让表格撑满页面Colspan 3合并三列做“合计”标签Element.ALIGN_RIGHT右对齐金额。5.4 数据库备份自动化用 mysqldump Windows 任务计划每天凌晨 2 点执行写一个批处理backup_db.batecho off set BACKUP_DIRD:\WarehouseBackup set DATESTR%date:~0,4%%date:~5,2%%date:~8,2% set TIMESTAMP%DATESTR%_%time:~0,2%%time:~3,2%%time:~6,2% set TIMESTAMP%TIMESTAMP: 0% mysqldump -u root -p123456 --databases warehouse_db --routines --triggers --events %BACKUP_DIR%\warehouse_db_%TIMESTAMP%.sql REM 删除 7 天前的备份 forfiles /p %BACKUP_DIR% /s /d -7 /c cmd /c del path然后在 Windows 任务计划中新建任务触发器每天 02:00操作启动程序backup_db.bat运行位置D:\WarehouseSystem\确保mysqldump.exe在 PATH 中或写绝对路径。从那以后我每次交付 C# 桌面系统都强制走一遍“备份脚本测试”手动执行.bat确认生成.sql文件且大小 10KB空库备份约 5KB再用mysql -u root -p warehouse_db xxx.sql验证可恢复。这步省不得客户硬盘坏过三次靠这个救回全部数据。希望帮到你。本文还有配套的精品资源点击获取
返回列表