☰
C#仓库管理系统+MySQL源码拆解:WinForms分层、八种查询与事务避坑
2026/10/1 22:29:51 网站建设 项目流程

简介:这是一套面向C#初学者与数据库课程实践者的仓库管理系统完整项目源码,基于C#与MySQL开发,可满足中小企业物品入库、出库、查询、统计等日常作业需求,并兼顾扩展性与安全性。资源包共163个文件,约1.22MB,以49个cs源代码文件、18个resx资源文件、18个png界面素材及27个zbak备份文件为主,另含sln解决方案、csproj工程文件、sql数据库脚本、config配置与README说明文档,结构完整便于直接编译运行。已有29人学习下载。项目采用面向对象思想封装业务逻辑,数据库部分包含物品、库存、供应商、客户等基础表,并通过视图与存储过程支持按名称、类别、出入库时间、库存量、供应商、客户等八种查询场景;登录验证、权限管理与参数化查询等安全措施也有体现,适合作为课程设计、毕业设计或C#与MySQL综合练习的参考范例。

1. 从一堆缓存文件里翻出能跑的 C# 仓库管理系统:这套 MySQL 源码到底值不值得拆

拿到这个资源包的时候,第一眼看到的不是Program.cs,而是一串.cache文件——stock1.csprojAssemblyReference.cache、DesignTimeResolveAssemblyReferencesInput.cache、stock1.csproj.CoreCompileInputs.cache,还有App.config、stock1.exe.config、DefaultFrm.Designer.cs。这说明它是一份从 Visual Studio 工作目录里直接打包出来的 C# WinForms 项目,不是那种只丢几个.cs文件的半成品。对正在找「C# 仓库管理系统 + MySQL 数据库文件」练手项目的人来说,这套东西的核心价值在于:它把入库、出库、查询、统计这条业务链和 MySQL 表结构绑在一起了,而且摘要里明确提到「查询物品的八种情况」和存储过程、视图,这意味着它不是只教你拖控件的玩具,而是能让你把 C# 连接 MySQL、参数化查询、OOP 分层这几件事一次性走通的完整素材。适合谁?适合已经会写 C# 基础语法、但没独立做过带数据库的桌面项目的人;也适合想拿一套现成表结构改造成自己公司内部小工具的人。不适合谁?指望开箱即用、双击 exe 就上线的人——因为 MySQL 连接串、数据库导入、.NET Framework 版本这些坑,一个都不会少。

2. 先看清项目骨架:WinForms + MySQL 的分层与文件构成

2.1 从文件清单反推项目类型和技术栈

资源包里出现的DefaultFrm.Designer.cs是典型的 WinForms 窗体设计器生成文件,配合App.config和stock1.exe.config,可以确定这是一个 .NET Framework 的 Windows 桌面程序,不是 ASP.NET Web 项目,也不是 .NET Core/.NET 5+ 的跨平台应用。stock1.csproj是项目文件,那一堆.cache是 VS 编译和资源生成过程中留下的中间产物,正常情况下可以删掉,但它们的出现说明打包者是把整个obj或bin目录一起压缩了。

对使用者来说,这意味着两件事:第一,你拿到的可能不是干净的源码目录,需要自己清理缓存文件再重新生成;第二,项目依赖的 .NET Framework 版本要从.csproj里的TargetFrameworkVersion看,常见的是 v4.5、v4.6.1 或 v4.7.2,如果本机没装对应版本,打开就会提示「目标框架未安装」。

技术栈上,C# 负责界面和业务逻辑,MySQL 负责持久化。摘要里提到「利用 .NET Framework 提供的网络功能」「Windows Forms 或 WPF 技术实现用户友好的图形界面」,结合DefaultFrm.Designer.cs这个命名,基本可以判定主界面用的是 WinForms。数据库访问层常见做法是MySql.Data或MySqlConnector这两个 NuGet 包之一,老项目里MySql.Data出现频率更高。

2.2 数据库文件里到底有什么:表、视图、存储过程

摘要里写得很清楚:基础表包括物品信息表、库存信息表、供应商信息表、客户信息表,高级部分包括视图和存储过程,用来支撑「查询物品的八种情况」。这八种情况按摘要列举,可能包括按名称、按类别、按入库时间、按出库时间、按库存量、按供应商、按客户等维度查询。

一个合格的仓库管理系统数据库,至少要有下面这几张核心表。下面给出一份常见的建表参考,字段名可以根据实际.sql文件调整,但结构逻辑是通用的:

-- 物品信息表:存物品的基础属性 CREATE TABLE item ( item_id INT PRIMARY KEY AUTO_INCREMENT, item_name VARCHAR(100) NOT NULL, category_id INT, unit VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 库存信息表:存每个物品的当前库存和仓库位置 CREATE TABLE stock ( stock_id INT PRIMARY KEY AUTO_INCREMENT, item_id INT NOT NULL, quantity INT DEFAULT 0, warehouse_id INT, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (item_id) REFERENCES item(item_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 入库记录表:每次入库留痕 CREATE TABLE inbound ( inbound_id INT PRIMARY KEY AUTO_INCREMENT, item_id INT NOT NULL, quantity INT NOT NULL, supplier_id INT, inbound_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 出库记录表:每次出库留痕 CREATE TABLE outbound ( outbound_id INT PRIMARY KEY AUTO_INCREMENT, item_id INT NOT NULL, quantity INT NOT NULL, customer_id INT, outbound_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段 SQL 的逻辑是:item管物品主数据,stock管实时库存,inbound和outbound管流水。参数上,ENGINE=InnoDB是为了支持外键和事务,CHARSET=utf8mb4是为了避免中文乱码——这一点在 MySQL 5.7 和 8.0 上都要显式指定,否则默认字符集可能存不进生僻字。

提示:如果资源包里的.sql文件已经包含建表语句,优先用它的,不要用上面这份覆盖。上面这份的作用是帮你理解表之间的关系,以及在原 SQL 缺失时兜底。

2.3 八种查询对应的 SQL 写法

摘要里「查询物品的八种情况」是这个项目最值得拆的部分。它本质上是在训练你把业务需求翻译成 SQL 的能力。下面按常见维度给出参考写法,实际项目里可能封装成存储过程或视图:

-- 1. 按名称模糊查询 SELECT * FROM item WHERE item_name LIKE CONCAT('%', @keyword, '%'); -- 2. 按类别查询 SELECT i.*, s.quantity FROM item i LEFT JOIN stock s ON i.item_id = s.item_id WHERE i.category_id = @categoryId; -- 3. 按入库时间范围查询 SELECT * FROM inbound WHERE inbound_time BETWEEN @startTime AND @endTime; -- 4. 按出库时间范围查询 SELECT * FROM outbound WHERE outbound_time BETWEEN @startTime AND @endTime; -- 5. 按库存量阈值查询(低于安全库存) SELECT i.item_name, s.quantity FROM stock s JOIN item i ON s.item_id = i.item_id WHERE s.quantity < @safeStock; -- 6. 按供应商查询入库记录 SELECT * FROM inbound WHERE supplier_id = @supplierId; -- 7. 按客户查询出库记录 SELECT * FROM outbound WHERE customer_id = @customerId; -- 8. 按物品+时间联合查询 SELECT * FROM inbound WHERE item_id = @itemId AND inbound_time >= @startTime;

参数说明:@keyword、@categoryId这些是占位符,在 C# 里对应MySqlParameter,不要用字符串拼接。LIKE CONCAT('%', @keyword, '%')这种写法比在 C# 里拼"%"+keyword+"%"更安全,也更容易被 MySQL 缓存执行计划。第 5 条的安全库存阈值查询是仓库系统里最实用的一个,很多小系统会漏掉。

3. 把项目跑起来:MySQL 建库、连接串配置与 C# 数据访问层

3.1 MySQL 安装与数据库导入的实操顺序

热词里「mysql安装教程」「mysql安装配置教程」「windows 安装 mysql 8」出现频率很高,说明很多人卡在第一步。这里给一条经过验证的顺序:

  1. 从 MySQL 官网下载 MySQL Installer for Windows,选 Server only 或 Developer Default。
  2. 安装时记住 root 密码,字符集选utf8mb4,认证方式如果项目用的是老版MySql.Data,选Use Legacy Authentication Method。
  3. 安装完成后用 MySQL Workbench 或命令行登录,执行CREATE DATABASE stock_db DEFAULT CHARSET utf8mb4;。
  4. 用source命令或 Workbench 的 Data Import 导入资源包里的.sql文件。
# 命令行导入示例 mysql -u root -p stock_db < stock_db.sql

这条命令的含义是:以 root 身份登录,把stock_db.sql里的语句全部执行到stock_db库。如果导入时报ERROR 2002 (HY000): Can't connect to local MySQL server through socket,说明 MySQL 服务没启动,去服务管理器里启动MySQL80即可。如果报ERROR 1045,是密码不对;报Unknown database,是库没建。

3.2 App.config 里的连接串怎么改

App.config和stock1.exe.config是 WinForms 项目读配置的地方。连接串通常长这样:

<configuration> <connectionStrings> <add name="StockConn" connectionString="server=localhost;port=3306;database=stock_db;uid=root;pwd=你的密码;charset=utf8mb4;" providerName="MySql.Data.MySqlClient" /> </connectionStrings> </configuration>

参数逐个说:server是本机就写localhost;port默认 3306,改过就写实际端口;database要和导入的库名一致;uid/pwd是 MySQL 账号;charset=utf8mb4必须加,否则中文物品名会变问号。如果项目用的是MySqlConnector而不是MySql.Data,providerName要相应改成MySqlConnector。

注意:stock1.exe.config是编译后生成的,改App.config后要重新生成项目才会同步。直接改 exe.config 也能生效,但下次编译会被覆盖。

3.3 C# 数据访问层的封装写法

一个能维护的仓库管理系统,不会把 SQL 写在窗体按钮事件里。常见做法是抽一个DbHelper或StockRepository类。下面给一个基于MySql.Data的最小可用封装:

using MySql.Data.MySqlClient; using System.Configuration; using System.Data; public class DbHelper { // 从 App.config 读取连接串,避免硬编码 private static readonly string ConnStr = ConfigurationManager.ConnectionStrings["StockConn"].ConnectionString; // 执行查询,返回 DataTable,适合绑定 DataGridView public static DataTable Query(string sql, params MySqlParameter[] parameters) { using (var conn = new MySqlConnection(ConnStr)) using (var cmd = new MySqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); var adapter = new MySqlDataAdapter(cmd); var table = new DataTable(); adapter.Fill(table); return table; } } // 执行增删改,返回受影响行数 public static int Execute(string sql, params MySqlParameter[] parameters) { using (var conn = new MySqlConnection(ConnStr)) using (var cmd = new MySqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteNonQuery(); } } }

逻辑说明:Query用MySqlDataAdapter填充DataTable,这样 WinForms 里可以直接dataGridView1.DataSource = table;。Execute用于入库、出库这种写操作。两个方法都用using确保连接释放,params MySqlParameter[]让调用方可以传任意个参数。参数说明:调用时写成DbHelper.Query("SELECT * FROM item WHERE item_name LIKE @kw", new MySqlParameter("@kw", "%" + keyword + "%")),这样既防注入,又不用手动拼 SQL。

3.4 入库和出库的事务处理

入库和出库不是单表操作:入库要同时写inbound和更新stock,出库要写outbound并扣减stock。这两步必须在一个事务里,否则会出现「记录写了但库存没加」的脏数据。

public static bool Inbound(int itemId, int qty, int supplierId) { using (var conn = new MySqlConnection(ConnStr)) { conn.Open(); using (var tran = conn.BeginTransaction()) { try { // 写入库记录 var cmd1 = new MySqlCommand( "INSERT INTO inbound(item_id,quantity,supplier_id) VALUES(@i,@q,@s)", conn, tran); cmd1.Parameters.AddWithValue("@i", itemId); cmd1.Parameters.AddWithValue("@q", qty); cmd1.Parameters.AddWithValue("@s", supplierId); cmd1.ExecuteNonQuery(); // 更新库存,存在则累加,不存在则插入 var cmd2 = new MySqlCommand( "INSERT INTO stock(item_id,quantity) VALUES(@i,@q) " + "ON DUPLICATE KEY UPDATE quantity = quantity + @q", conn, tran); cmd2.Parameters.AddWithValue("@i", itemId); cmd2.Parameters.AddWithValue("@q", qty); cmd2.ExecuteNonQuery(); tran.Commit(); return true; } catch { tran.Rollback(); return false; } } } }

这里ON DUPLICATE KEY UPDATE依赖stock表上item_id的唯一索引,如果原项目没建这个索引,需要先补上ALTER TABLE stock ADD UNIQUE KEY uk_item(item_id);。事务的Commit和Rollback是保证数据一致的关键,少了任何一个,出问题时都没有后悔药。

4. 避坑与排查:这套 C# + MySQL 项目最容易翻车的五个地方

4.1 现象:打开项目提示「目标框架未安装」

原因:.csproj里指定的 .NET Framework 版本本机没有。常见的是项目用 v4.6.1,而机器只装了 v4.8 或根本没装。

解决:右键.csproj用文本编辑器打开,看<TargetFrameworkVersion>标签,然后去微软官网下载对应版本的 Developer Pack 安装。如果不想装旧版,可以把标签改成已安装的版本,比如v4.8,但要注意 API 兼容性,一般 WinForms 项目改动不大。

4.2 现象:编译报「未能找到类型或命名空间名 MySql」

原因:NuGet 包没还原,或者packages.config里的包路径失效。老项目常用packages.config管理依赖,换机器后路径对不上。

解决:在 Visual Studio 里右键解决方案,选择「还原 NuGet 包」。如果还是不行,删掉packages文件夹和.csproj里的HintPath,重新用Install-Package MySql.Data安装。注意版本要和项目原本用的一致,版本差异可能导致MySqlConnection的某些方法签名变化。

4.3 现象:连接 MySQL 报「Authentication method 'caching_sha2_password' is not supported」

原因:MySQL 8.0 默认用caching_sha2_password认证插件,而老版MySql.Data(低于 8.0.9)不支持。

解决:两个办法。一是升级MySql.Data到 8.0.9 以上;二是改 MySQL 用户认证方式:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;

改完重启连接即可。生产环境建议用第一种,第二种会降低认证强度。

4.4 现象:DataGridView 显示中文乱码

原因:数据库、表、连接串三处字符集不一致。常见的是库建成了latin1,或者连接串没写charset。

解决:先查库和表的字符集:

SHOW VARIABLES LIKE 'character_set%'; SHOW CREATE TABLE item;

如果库不是utf8mb4,用ALTER DATABASE stock_db CHARACTER SET utf8mb4;改。连接串里补上charset=utf8mb4。已经存进去的乱码数据改不回来,需要重新录入。

4.5 现象:出库时库存扣成负数

原因:出库逻辑只做了UPDATE stock SET quantity = quantity - @q,没有校验当前库存是否足够,也没有在事务里加锁。

解决:在扣减前先查库存,或者用带条件的 UPDATE:

UPDATE stock SET quantity = quantity - @q WHERE item_id = @i AND quantity >= @q;

然后判断ExecuteNonQuery返回的影响行数,如果是 0,说明库存不足,回滚事务并提示用户。这个坑在并发场景下尤其明显,两个出库请求同时读到相同库存就会超卖,加AND quantity >= @q是最低成本的防护。

5. 进阶用法:把八种查询做成可复用视图,以及验证数据一致性的技巧

项目跑通之后,真正拉开差距的是怎么让查询逻辑不散落在 C# 代码里。摘要提到数据库文件里已经有视图和存储过程,我的习惯是把「查询物品的八种情况」里重复度高的部分固化成视图,C# 只负责传参数和绑定结果。比如按类别查库存这个需求,多个窗体都会用到,就可以建一个视图:

CREATE VIEW v_item_stock AS SELECT i.item_id, i.item_name, i.category_id, i.unit, IFNULL(s.quantity, 0) AS quantity, s.warehouse_id FROM item i LEFT JOIN stock s ON i.item_id = s.item_id;

之后不管按类别、按名称还是按库存量查,都从v_item_stock出发,SQL 短了,字段口径也统一了。IFNULL的作用是让没有库存记录的物品显示 0 而不是 NULL,避免 C# 里到处判空。

验证数据一致性,我一般用两条 SQL 对账。第一条查库存表和流水表的汇总是否一致:

SELECT s.item_id, s.quantity AS stock_qty, IFNULL(inb.total_in, 0) - IFNULL(outb.total_out, 0) AS flow_qty FROM stock s LEFT JOIN (SELECT item_id, SUM(quantity) total_in FROM inbound GROUP BY item_id) inb ON s.item_id = inb.item_id LEFT JOIN (SELECT item_id, SUM(quantity) total_out FROM outbound GROUP BY item_id) outb ON s.item_id = outb.item_id HAVING stock_qty <> flow_qty;

这条查询返回的行就是账实不符的物品。正常情况下应该返回空结果,如果有行,说明某次入库或出库的事务没提交完整,或者有人直接改了stock表没走流水。第二条是检查孤儿记录:

SELECT * FROM stock WHERE item_id NOT IN (SELECT item_id FROM item);

这种数据在界面上会显示成空白行,早点清掉省得后面排查半天。

还有一个实用技巧:把App.config里的连接串做成可切换的。开发时连本地库,演示时连测试库,不用每次改代码重新编译。常见做法是在App.config里放多个connectionString,用name区分,启动时根据命令行参数或配置文件选择。这样你在客户现场演示时,不会因为连错库把生产数据改了。

从那以后我每次拿到这种带数据库文件的 C# 项目,都强制先跑一遍对账 SQL 再动业务代码,确认库存和流水对得上,再开始改功能。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询