ARTICLE DETAIL

资讯详情

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

C#+MySQL仓库管理系统实战:从表结构到事务部署避坑

C#+MySQL仓库管理系统实战:从表结构到事务部署避坑 简介一套基于C#与MySQL开发的仓库管理系统完整项目面向C#初学者和数据库学习者适合课程设计、毕业设计或项目实训。系统包含商品管理、库存管理、出入库记录、报表统计等功能并采用数据访问层、业务逻辑层、表示层的分层结构便于理解真实项目的组织方式。压缩包共135个文件大小约1.2MB主要包含49个C#源文件、资源文件、界面截图、SQL脚本、配置文件和可执行程序等且附带数据库文件可省去手动建表。项目工程文件齐全可直接用Visual Studio打开调试。已有266人学习下载。通过分析源码可掌握ADO.NET访问MySQL、异常与事务处理、WinForm界面设计以及进销存类系统的常见模块划分为后续开发提供可参考的代码模板。1. 为什么C#MySQL的仓库管理系统还能成为今天的常青项目实际开发里仓库管理系统常以压缩包形式在开发者和毕业生之间流传大多是C#的WinForms或WPF项目数据库脚本挂一个.sql文件连上就能跑。这类项目最大的价值不是开箱即用而是提供一套成熟的数据表结构和业务闭环——从MySQL里的商品档案、入库单、出库单到库存台账每一步都有参照。拿到的压缩包里通常是一个Visual Studio工程加一个数据库文件改改连接字符串就能登录后台接下来是把别人逻辑变成自己的。适合刚接手内部工具开发、要给工厂做进销存或准备毕业设计的人。MySQL是地基C#负责界面交互连接、事务、字符集是后续坑的源头。下边按先看表、再跑通、再改造、再避坑的顺序讲。2. 先拆仓库管理系统的表结构和数据流从MySQL文件里读出业务2.1 从数据库文件反推业务核心表与字段设计大多数C#版WMS压缩包的数据库文件里表结构绕不开这几张User用户、Product商品档案、Supplier供应商、InStock与InStockDetail入库单及明细、OutStock与OutStockDetail出库单及明细、Stock实时库存、Log操作日志。主外键关系很好认入库主表对明细表一对多明细表通过ProductId关联商品表通过InStockId关联主表库存表以商品ID为唯一索引入库累加、出库扣减。为什么要先看懂表再动代码因为很多流传的项目业务逻辑写得随意直接在C#代码里拼接SQL。我接手过一套压缩包入库明细表里连“操作员”字段都没有库存表干脆没有期初字段导致上线第一天就不知道账面对不对。接手的第一步不是改界面而是把每张表的主键、外键、索引过一遍确认哪张表是库存快照、哪张表只做流水记录。表名关键字段职责UserId, UserName, Password, DisplayName登录与权限ProductId, ProductCode, ProductName, Spec, Unit, Price商品档案SupplierId, SupplierCode, SupplierName, Contact供应商InStockId, InStockNo, SupplierId, Operator, InStockDate, Status入库单主表InStockDetailId, InStockId, ProductId, Quantity, Price, TotalAmount入库明细OutStockId, OutStockNo, CustomerId, Operator, OutStockDate, Status出库单主表OutStockDetailId, OutStockId, ProductId, Quantity, Price, TotalAmount出库明细StockProductId, Quantity, UpdateTime实时库存OperationLogId, Operator, Action, TargetId, CreateTime操作留痕表设计里最容易翻车的是Quantity字段类型。用int做库存数量小仓库看着够用一旦出现负库存或大批次盘点就溢出凡是涉及数量、金额的列建议统一decimal(18,2)。金额列绝对不要用float等对账单里出现0.30000000000000004这种数你就明白为什么了。另外Stock表里的Quantity默认值要设置成0而不是NULL否则登录后看到的库存列表全是一堆空值这是MySQL里很小却很烦的一个点。2.2 数据流闭环入库单、出库单、库存表怎么协作业务主流程是个闭环创建入库单→审核→写明细→累加库存→写日志出库则反过来创建出库单→校验库存→扣减库存→写明细→写日志。很多压缩包代码跳过了“审核”这一步点保存就落库录错数据没有后悔药。我改造时习惯加一个Status字段0草稿、1已审核、2已作废这才有挽回空间。在这个闭环里Stock不是业务流水表而是快照表。期初数量只初始化一次之后每次业务都通过更新来变化。写入库时要特别注意不要在插入明细后先“SELECT Quantity FROM Stock WHERE ProductIdpid”再用取到的值在C#里算好新库存去UPDATE这会在并发下丢更新。正确做法是让MySQL自己原子加减UPDATE Stock SET Quantity Quantity delta, UpdateTime NOW() WHERE ProductId pid;逻辑说明这条语句让数据库在锁行状态下完成增量更新而不是应用程序先读再写。只要Stock表里已经存在该商品的期初行每次入库都会在此基础上做加法天然避免两个窗口互相覆盖。整个闭环的核心是事务。一次入库涉及到主表插入、明细多行插入、库存更新、日志插入四件事任何一步失败都要整体回滚否则就会出现“单据没了、库存多了”的灵异现象。在C#里用MySqlConnection.BeginTransaction()包裹所有命令最后统一Commitcatch里Rollback第4章会给完整写法。这个做法不只在入库出库、盘点、调拨都适用。2.3 C#连接MySQL驱动选型与连接字符串的关键参数C#连MySQL最常见的是MySql.Data官方Connector/NETNuGet包名就是MySql.Data另一条路是MySqlConnectorAPI几乎兼容、异步支持更好。压缩包自带的项目如果引用了MySql.Data.dll优先保留它改动最小如果要新写连接层我更倾向MySqlConnector线程安全性和异步场景表现更好。连接字符串是第一个卡点最常见问题集中在SSL、字符集和版本匹配上。仓库系统部署在局域网我常用这样一段Serverlocalhost;Port3306;Databasewms;Uidroot;Pwd123456;CharSetutf8mb4;SslModeNone;AllowPublicKeyRetrievalTrue;逻辑说明Server和Port指向MySQL地址本机部署用localhost:3306Database填导入后的库名Uid/Pwd是MySQL账号CharSetutf8mb4是为了中文不乱码SslModeNone跳过握手期的SSL协商适合纯内网部署AllowPublicKeyRetrievalTrue专治MySQL 8.0下“Public Key Retrieval is not allowed”的报错。参数说明里最值得记的两项是CharSet和SslMode。很多项目跑起来中文显示成问号不是代码问题是连接串没声明utf8mb4而MySQL 5.7的默认字符集可能是utf8导入的sql文件头部如果没有SET NAMES、表结构还是老字符集两边不一致就会出现乱码。驱动版本上MySQL 5.7配MySql.Data 8.0.x通常没问题反过来MySQL 8.0配很老的6.x驱动会报“Character set utf8mb4 is not supported”优先升驱动再排查代码。网上mysql安装教程很多这里只看两个决策点版本选5.7还是8.0认证插件是否兼容——8.0的默认认证方式第5章会专门说。3. 从压缩包到能点开的登录框初始化MySQL、导入数据库文件与还原C#工程3.1 环境准备MySQL 5.7/8.0选型和Visual Studio框架核实动手前先确认两件事MySQL版本和.NET框架版本。压缩包里的数据库文件通常是用mysqldump导出的.sql或者用Navicat for MySQL备份出来的备份包。前者直接命令行导入后者需要用Navicat的还原功能。如果是.sqlMySQL 5.7和8.0基本都能导但要注意sql文件里有没有MySQL 8.0特有的排序规则比如utf8mb4_0900_ai_ci5.7不认识会报1273错误。我开发时习惯用5.7.44做测试交付到客户那边再按其现有版本决定避免导出文件自带新语法。Visual Studio与.NET框架也要对齐。老压缩包大多是.NET Framework 4.5或4.6.2的WinForms工程用Visual Studio 2019/2022打开时通常会自动重新定位到4.7.2。如果提示目标框架不受支持就在项目属性里改成4.7.2再还原NuGet包。另一个隐藏变量是平台位数MySQL驱动是32位的生成平台要选x86如果项目里引用了64位报表组件则选x64。这个设置错了不会编译报错而是运行时抛“未能加载文件或程序集”是最迷惑人的错误之一。3.2 导入数据库文件命令行建库、导入与常见报错拿到.sql文件后我习惯先建空库、再重定向导入不直接用source。这样不需要关心sql里有没有CREATE DATABASE也能避免导入到错误的库。# 先建库再导入字符集统一utf8mb4 mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS wms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p wms D:\project\wms\db\wms.sql逻辑说明第一步建库时显式声明字符集和排序规则避免继承MySQL默认的latin1第二步把.sql文件重定向进wms库。如果sql文件内部带有USE wms或CREATE DATABASE语句也不冲突只是最终库名要跟连接串保持一致。参数说明-u指定用户-p提示输密码避免把密码留在cmd历史里Windows下路径不要带中文否则cmd代码页容易解析失败。导入遇到ERROR 1064时先检查文件开头有没有BOM——用记事本另存为UTF-8无BOM再试遇到ERROR 1366是字符集不匹配字段里的中文无法转成目标字符集解决思路是把库、表、字段三级字符集统一成utf8mb4。3.3 修改连接字符串App.config配置与第一次编译导入成功后打开Visual Studio里的App.config把连接串填进connectionStrings节点。老项目常用这样的配置!-- wms数据库连接字符集utf8mb4内网可关SSL -- connectionStrings add nameWMSConnString connectionStringServerlocalhost;Port3306;Databasewms;Uidroot;Pwd123456;CharSetutf8mb4;SslModeNone;AllowPublicKeyRetrievalTrue; providerNameMySql.Data.MySqlClient/ /connectionStrings逻辑说明name是代码里读取时的键用ConfigurationManager.ConnectionStrings[WMSConnString].ConnectionString拿连接串。providerName要和实际引用驱动匹配如果老项目用的是ODBC方式连接串要换成Driver{MySQL ODBC 8.0 Unicode Driver};Server...;Database...的形式两者不能混。参数说明Persist Security Info默认不开连接池参数Connection Lifetime、Min Pool Size这些等真出现连接数过多再调不要一上来就堆一堆配置。编译期最常见的三类错误一是找不到MySql.Data命名空间在NuGet里重新安装MySql.Data二是ConfigurationManager未引用添加System.Configuration引用三是SQL里的表名不存在回第2章核对表名。第一次启动若能弹出登录框但点登录报错多在Navicat里用同一账号测一次先排除MySQL侧问题。4. 把入库、出库、库存查询改成能上线的代码事务、参数化与界面刷新4.1 入库单保存逻辑事务包裹主从表、原子更新库存入库动作是仓库系统的第一个核心也是坏代码重灾区。常见做法是用户在主界面填单头DataGridView里录明细点保存一次性写主表和明细表。我会把整个过程包进事务确保主表和明细要么都在、要么都不在。这段代码可以直接照着改using (var conn new MySqlConnection(connString)) { conn.Open(); var tx conn.BeginTransaction(); try { // 主表插入同时取回自增ID作为明细外键 string sqlMain INSERT INTO InStock(InStockNo, SupplierId, Operator, InStockDate, Status) VALUES(no, supplierId, operator, date, status); SELECT LAST_INSERT_ID();; var cmdMain new MySqlCommand(sqlMain, conn, tx); cmdMain.Parameters.AddWithValue(no, RK DateTime.Now.ToString(yyyyMMddHHmmss)); cmdMain.Parameters.AddWithValue(supplierId, supplierId); cmdMain.Parameters.AddWithValue(operator, currentUser); cmdMain.Parameters.Add(date, MySqlDbType.DateTime).Value inStockDate; cmdMain.Parameters.AddWithValue(status, 1); int mainId Convert.ToInt32(cmdMain.ExecuteScalar()); foreach (var item in details) { // 明细插入并原子更新库存不依赖期初行是否存在 string sqlDetail INSERT INTO InStockDetail(InStockId, ProductId, Quantity, Price, TotalAmount) VALUES(inStockId, productId, quantity, price, totalAmount); INSERT INTO Stock(ProductId, Quantity, UpdateTime) VALUES(productId, quantity, NOW()) ON DUPLICATE KEY UPDATE Quantity Quantity quantity, UpdateTime NOW();; var cmdDetail new MySqlCommand(sqlDetail, conn, tx); cmdDetail.Parameters.AddWithValue(inStockId, mainId); cmdDetail.Parameters.AddWithValue(productId, item.ProductId); cmdDetail.Parameters.AddWithValue(quantity, item.Quantity); cmdDetail.Parameters.AddWithValue(price, item.Price); cmdDetail.Parameters.AddWithValue(totalAmount, item.Price * item.Quantity); cmdDetail.ExecuteNonQuery(); } tx.Commit(); } catch { tx.Rollback(); throw; } finally { tx.Dispose(); } }逻辑说明两条SQL在一个事务里先取主表自增ID作为明细外键再逐行写入明细并更新库存。更新库存用了INSERT...ON DUPLICATE KEY UPDATE不依赖期初数据是否存在即使Stock表里没这商品的行也会自动建行。事务对象单独声明而不放进using确保Rollback和Commit的时序可控。参数说明日期参数显式指定了MySqlDbType.DateTime而不是用AddWithValue靠驱动推断避免“string到datetime转换失败”。单号用“RK时间戳”生成简单但会撞单若同一秒保存两单需要加随机后缀或使用数据库自增序列。这段代码里最值得记住的是主表、明细、库存的写入全部挂在同一个tx上任何一行抛异常catch里的Rollback会把前面的操作全部撤销。4.2 库存扣减的两种做法先查再改是最容易翻车的写法出库单的核心是扣库存。行业内对“扣减”有两条路线先查再改、原子更新。先查再改是网上最常见也最容易翻车的写法string sqlCheck SELECT Quantity FROM Stock WHERE ProductIdpid; int qty Convert.ToInt32(ExecuteScalar(sqlCheck, pid)); if (qty need) { ExecuteNonQuery(UPDATE Stock SET Quantity Quantity - need WHERE ProductIdpid, pid, need); }逻辑说明这段逻辑在单用户试跑时完全正常但两个操作员同时录出库单时两人都查到库存够然后都执行扣减最后库存变负账面全乱。原因是SELECT和UPDATE之间存在时间窗口既没有事务保护也没有行锁。参数说明需要need、pid两个参数但无论怎么传参这个写法本质上就是非原子的“检查-更新”。正确的替代方案是把条件写进UPDATEUPDATE Stock SET Quantity Quantity - need WHERE ProductId pid AND Quantity need然后检查ExecuteNonQuery的影响行数返回1才表示扣减成功如果返回0说明库存不足整体回滚。更严格的做法是SELECT ... FOR UPDATE加事务但对小仓库来说带条件的UPDATE配合事务已经够用代码也容易让别人看懂。4.3 DataGridView刷新、跨线程更新与列名绑定的坑界面层最常见的问题是保存后列表不刷新、列头是英文、后台查询一卡界面就假死。我习惯把库存查询结果放进DataTable再赋值给dataGridView.DataSource。保存结束必须重新查询不要自己在界面上改单元格——那是在骗自己下一次查询又会把旧数据盖回来。绑定前处理列头有三条路SQL别名写中文、DataGridView列头写中文、动态生成列。我倾向保留英文列名在ColumnHeaderText里设置中文这样SQL变动不影响界面。还有一个高频坑AutoGenerateColumns默认打开时SQL里只要多select一个字段界面就会多出一列排查界面多了列时先看SQL返回了哪些列。跨线程更新控件是另一个让人抓狂的问题。一旦在BackgroundWorker或Task里执行查询回填DataGridView必须用Invoke回到UI线程。很多做上位机开发的在这翻过车后台线程直接操作控件抛“跨线程操作无效”。仓库系统数据量虽小但查询超过几千行就可能卡界面提前写好Invoke封装比晚点补要省事得多。我一般会把“设置DataSource”和“刷新显示”封装成两个方法后台线程统一调Invoke版本界面就再没因为刷新闪退过。5. 部署到内网后的高频避坑记录现象、原因、处理5.1 中文乱码登录正常商品名显示成问号现象功能都能用但所有中文显示为??或Navicat里看正常、软件里看乱码。原因字符集链路有一环不一致就会乱码数据库表用utf8mb4、连接串CharSet用utf8、sql文件内容又是latin1三个环节只要有一个错位结果必乱。处理先确认三处SHOW VARIABLES LIKE character_set_database;、sql文件头部的SET NAMES、连接串的CharSet。然后统一成utf8mb4ALTER DATABASE wms CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE Product CONVERT TO CHARACTER SET utf8mb4;改完重连再查一次不要改了数据库忘了改连接串也不能反过来。5.2 MySQL 8.0认证插件导致C#连接失败现象命令行、Navicat都能连唯独C#程序报“Authentication method caching_sha2_password not supported”或者“Public Key Retrieval is not allowed”。原因MySQL 8.0默认用户认证插件是caching_sha2_password老驱动不认。处理两条路。优先升级驱动并加AllowPublicKeyRetrievalTrue无法升级驱动时把应用账号改回老认证方式ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;这里建议单独建一个wms_app账号只授权wms库不给整个实例权限别拿root裸奔。5.3 导入.sql文件报错BOM、1064、1273现象命令行导入抛ERROR 1064 (42000)或者ERROR 1273 (HY000) Unknown collation。原因1064多半是文件被文本编辑器加过BOM首条语句解析失败1273是sql文件里的排序规则在低版本MySQL不存在。处理1273把utf8mb4_0900_ai_ci替换成utf8mb4_general_ci再导入1064先把文件转成UTF-8无BOM。用Navicat备份出来的.bak文件不能命令行导入用Navicat的备份还原功能路径不要带中文。5.4 杀毒软件把编译好的exe当木马现象Debug或Release目录下的exe一生成就被隔离换台电脑拷贝过去同样报。原因.NET编译产物体积小、结构固定容易被启发式引擎误判某些旧版第三方dll被标记过威胁特征整个exe连坐。处理先全量编译一次关掉杀毒看是否只有exe被隔离再把第三方dll逐个移除排查特征最后给发布目录加白名单。发布时用强签名能降低误报率但不是100%杜绝。确认代码是自己的再决定是否点信任建议不要让杀毒软件长期关闭。5.5 重复扣减单据一张、库存少了两次现象用户连点两次保存或网络卡顿后重试数据库里出现两条相同单据库存被扣两次。原因按钮没有防重复提交事务各自独立第一次事务还没提交完成时第二次操作又进来了。处理界面层保存时把按钮置灰finally中恢复更硬的办法是业务层加幂等控制在单据主表加BusinessNo唯一索引重复插入直接顺事务回滚。两个办法同时上不要只靠界面置灰因为异常抛太快时置灰不一定来得及恢复。日志表里记录每次保存的请求ID排查时能顺着它把重复动作定位到秒级。6. 上线前我最后会做的三处小改动索引、备份和操作留痕第一处是给高频查询加索引。库存查询页通常一打开就查全表商品名模糊查询在上千行数据后会明显拖慢响应。我会在Product表ProductName上加普通索引在出入库明细表的(InStockId, ProductId)上建联合索引Stock表ProductId上建唯一索引。改完用EXPLAIN SELECT验证是否走索引。这个改动不动代码但对体验提升最明显。第二处是定备份策略。mysqldump每天凌晨导一次保留近7天。Windows任务计划里可以放这样一条命令mysqldump -uroot -p --default-character-setutf8mb4 --single-transaction --quick wms D:\backup\wms_%date:~0,4%%date:~5,2%%date:~8,2%.sql参数说明--single-transaction在InnoDB下用一致性快照不锁业务表--quick逐行输出减少内存占用后面的%date:~0,4%是在cmd里截取系统日期的年、月、日避免日期带斜杠导致文件名非法。恢复时用mysql -uroot -p wms 备份文件库名要和连接串一致正好衔接第3章的导入步骤。第三处是补操作留痕。很多压缩包项目只有登录日志没有业务日志。我习惯补一张OperationLog字段只要Id、Operator、Action、TargetId、CreateTime在每个写操作后面插一行。这个习惯救过我一次用户说库存对不上账查到最后是误删了期初记录没有日志根本无从定位。日志不用复杂重在“每个写操作都记”而不是只在登录时记一下。说到底这类压缩包项目的价值在表结构和业务闭环不在那几行界面代码。先按第2章读懂表第3章把它跑通再谈改造。MySQL版本、字符集、驱动版本是三个隐藏变量任何一个不对代码再好也白搭。我自己的排查口令是“先查版本、后查连接、再查事务”这套口令帮我把无数玄学问题变成了具体报错。希望帮到你。本文还有配套的精品资源点击获取
返回列表