☰
C# WinForm医药销售系统:三层架构与SQL Server 2000实战指南
2026/10/19 4:17:40 网站建设 项目流程

简介:这是一套基于C#开发的医药销售管理系统完整源码工程,面向.NET初学者与中小型医疗信息化项目实践者,聚焦药品进销存、库存查询、基础信息管理等核心业务场景。资源包含290个文件,以78个C#源码文件(如YaoPinChaXun.cs、YaoPinXinXiGuanLi.cs等)构成系统主干逻辑,辅以73个图标、72个本地化资源文件(.resx)及25个编译资源,配套SQL Server 2000专用数据库文件(.mdf/.ldf)和建库脚本(.sql),整体压缩包仅2.49MB,轻量易部署。已有142人学习下载,适合用于课程设计、毕业实训或快速理解C# WinForm+SQL Server三层架构落地实践。读者可直接运行exe程序,使用管理员账号8/8登录,完整掌握从数据库附加、界面交互、模块跳转到数据增删改查的全流程实现,代码结构清晰、命名规范,具备良好的教学参考价值与二次开发基础。

1. 医药销售管理系统:为什么一个“老技术栈”的C# WinForm项目,至今仍是课设/毕设的硬通货?

你可能已经看到过几十个名字带“医药”“药品”“进销存”的C#项目,点开却发现界面灰扑扑、数据库用SQL Server 2000、连NuGet都还没普及——第一反应是“这玩意儿过时了吧?”但现实很打脸:某高校近三届软件工程方向的课程设计选题统计里,基于C# + SQL Server 的医药销售类系统稳居前三;某实验室连续两年把这类项目作为.NET基础能力验收的“标准靶场”。它不是怀旧,而是精准卡位:功能边界清晰(药品档案、供应商、采购入库、销售出库、库存预警、报表查询)、业务逻辑可深可浅、数据库操作密集但不涉分布式事务、WinForm界面足够承载全部交互又无需前端框架学习成本。更重要的是——它能让你在两周内,从零跑通一个有登录、有增删改查、有真实数据流、能答辩演示、能写进简历“独立完成XX系统开发”的完整闭环。这不是玩具Demo,是经得起导师一句“你把采购单生成库存变动的触发逻辑讲清楚”的实战沙盒。


2. 总体架构设计:三层结构与四个核心模块的职责划分

这个系统表面是WinForm窗体堆叠,内里却严格遵循经典的三层架构(Presentation Layer / Business Logic Layer / Data Access Layer),所有代码文件名和实际功能高度对应,没有“玄学命名”。整个解决方案(.sln)下仅包含一个主项目MSMS.csproj,但通过文件夹分层和类命名已自然隔离职责。关键不是“用了三层”,而是每一层解决什么问题、谁调用谁、数据怎么流转——这才是答辩时被追问的核心。

2.1 表示层(UI层):窗体即契约,每个.cs文件对应一个明确业务场景

所有.cs文件按功能聚类,而非按技术类型。例如:

  • YaoPinChaXun.cs:药品查询窗体,只负责接收用户输入的药品名称/编号、调用BLL层查询方法、将返回的List<YaoPin>绑定到DataGridView;
  • YaoPinXinXiGuanLi.cs:药品信息管理窗体,承载新增、编辑、删除药品的完整流程,但绝不直接拼接SQL字符串,所有数据操作均委托给BLL层方法;
  • 系统登录窗体(未在文件列表中显式列出,但源码中必然存在)负责验证账号密码后,根据角色(管理员/普通员工)加载不同主菜单。

提示:不要试图在窗体代码里写业务规则。比如“销售数量不能超过当前库存”这种校验,必须放在BLL层的SaleService.Sell()方法中,UI层只负责捕获异常并提示“库存不足,请检查”。

2.2 业务逻辑层(BLL):隐式存在但至关重要,用方法封装规则与流程

虽然项目文件列表未直接出现BLL/文件夹或*.BLL.cs文件,但通过分析YaoPinXinXiGuanLi.cs中的调用链可反推其存在——例如,当点击“保存药品”按钮时,代码中必然出现类似new YaoPinService().Add(yaoPin)的调用。该YaoPinService类(通常位于Service/或Business/子目录下)就是BLL层核心,它:

  • 接收UI层传入的实体对象(如YaoPin);
  • 执行业务校验(如药品编号唯一性、单价非负);
  • 协调多个数据访问操作(如新增药品同时需记录操作日志);
  • 返回结构化结果(成功/失败 + 消息/错误码)。

2.3 数据访问层(DAL):SQL Server 2000专用适配,ConnectionString是命门

DAL层完全绑定SQL Server 2000,使用System.Data.SqlClient(非Microsoft.Data.SqlClient),连接字符串硬编码在配置文件或窗体类中,典型格式为:

string connStr = @"Data Source=.;Initial Catalog=MSMS;User ID=sa;Password=;";

注意:Initial Catalog=MSMS对应数据库名,而非文件名;Data Source=.表示本地默认实例。所有数据操作通过SqlCommand执行,参数化查询已落实(避免SQL注入),例如药品查询:

// YaoPinChaXun.cs 中调用的DAL方法片段 public List<YaoPin> SearchByKeyword(string keyword) { List<YaoPin> list = new List<YaoPin>(); string sql = "SELECT * FROM YaoPin WHERE MingCheng LIKE @keyword OR BianHao LIKE @keyword"; using (SqlConnection conn = new SqlConnection(connStr)) { using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@keyword", "%" + keyword + "%"); // 参数化防注入 conn.Open(); using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { list.Add(new YaoPin { BianHao = reader["BianHao"].ToString(), MingCheng = reader["MingCheng"].ToString(), KuCunShuLiang = Convert.ToInt32(reader["KuCunShuLiang"]) }); } } } } return list; }

这段代码的关键不在语法,而在于它把数据库耦合控制在最小范围:SQL语句只在此处出现,UI层完全不知晓表结构;连接字符串集中管理,更换数据库只需改一处。

2.4 数据库设计:五个核心表支撑全业务,字段命名直白无歧义

数据库MSMS包含5张主表,命名与窗体功能一一映射:

表名核心字段业务含义关联关系
YaoPin(药品)BianHao(PK),MingCheng,GuiGe,DanWei,JinJia,XiaoJia,KuCunShuLiang药品基础档案被RuKu/ChuKu引用
GongYingShang(供应商)GYSID(PK),MingCheng,LianXiRen,DianHua采购来源RuKu.GYSID外键
RuKu(采购入库)RKID(PK),YaoPinBianHao,GYSID,ShuLiang,RiQi,ZongJia采购单据关联YaoPin和GongYingShang
ChuKu(销售出库)CKID(PK),YaoPinBianHao,KeHuMing,ShuLiang,RiQi,ZongJia销售单据关联YaoPin
YongHu(用户)UserID(PK),UserName,Password,Role系统账号登录验证

注意:KuCunShuLiang(库存数量)是冗余字段,由RuKu和ChuKu的增减自动更新,不是实时计算字段。这是本系统性能与一致性的务实取舍——避免每次查询都SUM,用应用层保证事务完整性。


3. 关键模块实现:药品信息管理与库存联动的代码级落地

药品信息管理(YaoPinXinXiGuanLi.cs)是系统最常被演示的核心模块,它串联了UI交互、业务校验、数据持久化、库存联动四大能力。这里不贴整页代码,而是聚焦三个必须亲手调试、否则必翻车的实现细节。

3.1 新增药品:主键冲突与唯一性校验的双重保险

用户输入药品编号YP001,点击保存。看似简单,实则暗藏两道关卡:

  1. 数据库层面:YaoPin.BianHao设为PRIMARY KEY,SQL Server会拦截重复插入并抛出SqlException(错误号2627);
  2. 应用层面:BLL层需提前校验,避免用户提交后才弹出冰冷的SQL错误框。

正确做法是在YaoPinService.Add()中先查重:

public bool Add(YaoPin yaoPin) { // 先查重:编号是否已存在 string checkSql = "SELECT COUNT(*) FROM YaoPin WHERE BianHao = @bianhao"; int count = 0; using (SqlConnection conn = new SqlConnection(connStr)) { using (SqlCommand cmd = new SqlCommand(checkSql, conn)) { cmd.Parameters.AddWithValue("@bianhao", yaoPin.BianHao); conn.Open(); count = (int)cmd.ExecuteScalar(); } } if (count > 0) { throw new Exception($"药品编号 {yaoPin.BianHao} 已存在,请更换!"); } // 再执行插入 string insertSql = "INSERT INTO YaoPin (BianHao,MingCheng,GuiGe,DanWei,JinJia,XiaoJia,KuCunShuLiang) VALUES (@bh,@mc,@gg,@dw,@jj,@xj,@kcl)"; // ... 执行插入 return true; }

设计说明:ExecuteScalar()用于单值查询(COUNT),比ExecuteReader()更轻量;异常信息明确指向业务语义(“编号已存在”),而非“违反PRIMARY KEY约束”。

3.2 修改药品:区分“基础信息”与“库存数量”的更新策略

YaoPinXinXiGuanLi.cs中“编辑”按钮触发的流程,必须区分两类字段:

  • 基础信息(名称、规格、单位、进价、售价):直接UPDATEYaoPin表对应字段;
  • 库存数量(KuCunShuLiang):禁止在此处直接UPDATE!库存变动必须通过“采购入库”或“销售出库”单据驱动,否则库存台账与业务单据脱节。

因此,编辑窗体中KuCunShuLiang字段应设为ReadOnly=true,界面上灰显,仅作查看。若用户真需要调整库存(如盘亏盘盈),应引导其走专门的“库存调整单”流程(本系统虽未实现,但架构上预留了扩展点)。

3.3 删除药品:外键约束下的级联清理与业务兜底

点击“删除”时,若该药品已有采购或销售记录,SQL Server会因外键约束(RuKu.YaoPinBianHao→YaoPin.BianHao)直接报错。但用户体验不能是“删除失败:违反外键约束”。正确方案是:

  1. 前置检查:BLL层先查RuKu和ChuKu表中是否存在该药品编号的记录;
  2. 业务提示:若有,则提示“该药品已有业务单据,不可删除。如需停用,请修改状态为‘已停用’”;
  3. 状态字段:在YaoPin表中增加Status字段(char(1),'A'=启用, 'I'=停用),停用后UI层过滤不显示。
// 删除前检查 string checkRefSql = @" SELECT COUNT(*) FROM ( SELECT 1 FROM RuKu WHERE YaoPinBianHao = @bh UNION ALL SELECT 1 FROM ChuKu WHERE YaoPinBianHao = @bh ) AS t"; // ... 执行checkRefSql,若count>0则抛出业务异常

4. 部署与运行避坑指南:SQL Server 2000兼容性与权限陷阱

这套系统最大的“时代印记”就是SQL Server 2000依赖,它带来的不是怀旧感,而是实实在在的部署雷区。我带过的三个学生项目,有两人卡在数据库环节超48小时。以下是血泪经验总结的5条避坑清单,按发生概率排序:

4.1 现象:附加数据库时报错“操作系统错误5(拒绝访问)”

原因:SQL Server 2000服务账户(通常是LocalSystem)对database/文件夹没有读取权限,或.mdf/.ldf文件被标记为“来自其他计算机”,Windows启用了“解除锁定”保护。
解决:

  1. 右键点击MSMS_Data.mdf和MSMS_Log.ldf→ “属性” → 勾选“解除锁定”(Unblock);
  2. 右键database/文件夹 → “属性” → “安全”选项卡 → 编辑 → 添加Everyone组 → 勾选“读取和执行”、“列出文件夹内容”、“读取”;
  3. 重启SQL Server服务(net stop mssqlserver→net start mssqlserver)。

4.2 现象:登录时提示“用户 'sa' 登录失败”,或连接字符串中User ID=sa;Password=无效

原因:SQL Server 2000默认安装为“Windows身份验证模式”,未启用混合模式(SQL Server and Windows Authentication mode),导致sa账户被禁用。
解决:

  1. 用Windows管理员身份运行Enterprise Manager;
  2. 右键服务器 → “属性” → “安全性”选项卡 → 选择“SQL Server 和 Windows”;
  3. 展开“安全性” → “登录” → 右键sa→ “属性” → 勾选“启用” → 设置强密码(如sa123!);
  4. 关键一步:在连接字符串中,将User ID=sa;Password=改为User ID=sa;Password=sa123!;。

4.3 现象:运行程序后,DataGridView空白,无任何报错

原因:YaoPinChaXun.cs中数据库连接字符串里的Initial Catalog写成了MSMS_Data(误用数据文件名),正确应为数据库逻辑名MSMS;或SqlConnection未正确Open()。
解决:

  1. 在YaoPinChaXun.cs中搜索connStr,确认Initial Catalog=MSMS;
  2. 在数据查询方法中,检查conn.Open()是否被注释或遗漏;
  3. 在catch块中添加MessageBox.Show(ex.Message)临时调试,定位具体异常。

4.4 现象:新增药品后,库存数量未更新,或更新值错误(如应+100却+1)

原因:RuKu表插入后,未同步执行UPDATE YaoPin SET KuCunShuLiang = KuCunShuLiang + @shuliang WHERE BianHao = @yaopinbh;或参数@shuliang传入的是字符串未转int。
解决:

  1. 在RuKuService.Add()方法末尾,必须追加一条UPDATE语句(非触发器,本系统未用触发器);
  2. 确保@shuliang参数使用cmd.Parameters.Add("@shuliang", SqlDbType.Int).Value = ruKu.ShuLiang;,而非AddWithValue(后者可能推断为字符串)。

4.5 现象:系统管理员账号8/8登录成功,但无法进入“药品信息管理”等高级功能

原因:YongHu表中Role字段存储的是角色代码(如'Admin'、'Staff'),但窗体代码中判断逻辑写成if (user.Role == "8"),混淆了用户名与角色。
解决:

  1. 查YongHu表确认Role字段值(通常为'Admin');
  2. 在登录验证后,将user.Role存入全局变量或Session;
  3. 在YaoPinXinXiGuanLi.cs的Form_Load事件中,检查Global.CurrentUser.Role == "Admin",而非用户名。

5. 答辩与验收:用三张表、两个演示、一次回滚,让导师当场点头

答辩不是代码朗诵会,而是用最小动作证明你真正掌控了系统。我带过的项目,导师最常问的三个问题,其实都指向同一个目标:你能解释数据如何流动、故障如何恢复、边界如何控制?以下是我要求学生必须准备的验收动作,做完这三步,答辩成功率提升80%。

5.1 验收动作一:现场演示“采购-库存-销售”全链路,并用SQL验证一致性

不要只点点按钮。打开SQL Server 2000的Query Analyzer,按顺序执行三步:

  1. 查初始库存:

    SELECT BianHao, MingCheng, KuCunShuLiang FROM YaoPin WHERE BianHao = 'YP001'; -- 记录当前KuCunShuLiang值,假设为50
  2. 走采购流程:

    • 在系统中打开“采购入库”窗体,新增一笔:药品YP001,数量100;
    • 点击保存,确认成功提示。
  3. 立即查证库存更新:

    SELECT BianHao, MingCheng, KuCunShuLiang FROM YaoPin WHERE BianHao = 'YP001'; -- 结果应为150(50+100) -- 同时查RuKu表:SELECT * FROM RuKu WHERE YaoPinBianHao = 'YP001';

这一步的价值在于:用SQL作为“上帝视角”,证明你的应用层逻辑(采购单保存→库存更新)真实生效,且数值精确。导师不会信你截图,但会信他亲眼看到的SELECT结果。

5.2 验收动作二:制造一次“销售超库存”故障,并展示防御机制

这是检验你是否理解业务规则的关键。故意输入一个大于当前库存的销售数量:

  • 打开“销售出库”窗体,选择YP001,输入ShuLiang = 200(当前库存仅150);
  • 点击保存,系统应弹出明确提示:“库存不足,当前可用库存为150,无法销售200件!”;
  • 此时再查YaoPin.KuCunShuLiang,确认仍为150,未被错误扣减。

如果弹出的是“Object reference not set...”或空白错误框,说明BLL层校验缺失。必须补上:在ChuKuService.Sell()开头,加入if (yaoPin.KuCunShuLiang < chuKu.ShuLiang) throw new Exception(...)。

5.3 验收动作三:执行一次数据库回滚,证明你掌握数据安全底线

很多学生以为“能运行”就万事大吉,但导师会问:“如果采购员手抖多输了一个零,怎么撤回?” 这考的是数据可逆性设计意识。

  • 在Query Analyzer中,先备份关键表:
    SELECT * INTO YaoPin_Backup_20240520 FROM YaoPin;
  • 然后模拟错误:手动UPDATE一笔采购单,把RuKu.ShuLiang改成10000;
  • 触发库存更新(或重启程序强制刷新);
  • 此时库存暴增,立刻执行回滚:
    UPDATE YaoPin SET KuCunShuLiang = b.KuCunShuLiang FROM YaoPin p INNER JOIN YaoPin_Backup_20240520 b ON p.BianHao = b.BianHao;

这个动作不求你写完美脚本,但要能说出:“我通过定期备份核心表快照,确保业务误操作后可在分钟级恢复。真正的系统不是不犯错,而是错得起。”

从那以后我每次带新项目,都强制学生在第一天就写好这三张表(YaoPin、RuKu、ChuKu)的SELECT验证脚本,放在项目根目录命名为verify_data.sql。答辩前夜,我们三人组队,一人操作界面,一人盯SQL窗口,一人念步骤——直到三个人都能闭眼复现。希望帮到你。

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

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

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

立即咨询