C#医药销售管理系统实战:数据库配置与登录校验全解析
简介基于C#开发的医药销售管理系统完整源码与数据库资源适合正在学习C# WinForms、SQL Server数据库开发或医药进销存项目的学生与初级开发者可用于课程设计、毕业设计或项目二开参考。整个资源包共290个文件包含78个.cs源码文件、72个.resx界面资源、73个.ico图标以及SQL脚本、MDF/LDF数据库文件、项目工程文件和可执行程序压缩包仅2.49MB目录结构清晰。数据库需使用SQL Server 2000已附带MSMS_Data与MSMS_Log数据文件既可直接附加也可通过MSMS.sql导入并预置管理员账号8、密码8便于快速运行与调试。系统涵盖药品信息管理、药品查询等典型业务模块能直观展示数据表设计、增删改查逻辑与界面绑定方式。目前已有141人学习下载对于希望掌握C#SQL Server经典开发模式的人来说是一份轻量且完整的实践素材。1. 医药销售管理系统一份能跑通课程设计的 C# 老项目如果你手里正攥着一份 C# 医药销售管理系统源码却卡在数据库附加、登录报错或者根本看不懂代码结构——那这篇文章就是给你写的。这套系统是典型的 WinForms SQL Server 架构功能集中在药品信息管理和药品查询两条主线上附带基础的用户登录与权限校验。它不像互联网项目那样花哨但作为课程设计、毕业设计或者 C# 数据库编程的入门复现对象结构足够清晰窗体、数据访问、业务逻辑三层都摊开了摆在你面前。系统的默认管理员账号是 8、密码是 8数据库文件已经放在 database 文件夹里你只需要把 MSMS_Data 和 MSMS_Log 附加进 SQL Server 2000改好连接字符串就能跑起来。听起来简单但真正动手时你会遇到版本兼容、连接字符串、文件权限这些坑下面我从数据库到代码层逐一拆开讲。2. 数据库附加与初始化SQL Server 2000 老库的两种恢复方式2.1 附加 MSMS_Data 与 MSMS_Log从备份文件到可查询的库这套系统的数据库文件放在 database 文件夹下包含 MSMS_Data 主数据文件和 MSMS_Log 日志文件。附加是恢复 SQL Server 数据库最直接的方式本质就是把这两个文件重新挂载到 SQL Server 实例上让系统能识别到库结构和数据。操作路径是打开 SQL Server 企业管理器右键点击数据库节点选择所有任务下的附加数据库然后在弹出的对话框中指定 MSMS_Data.MDF 文件所在路径确认后系统会自动关联同名的 .LDF 日志文件。如果你的环境是 SQL Server 2000这一步通常很顺畅。但如果你跟我一样机器上装的是 SQL Server 2008 或更高版本附加 2000 时代的库文件可能会直接报版本不兼容或无法打开物理文件。这时候常见的做法不是硬附加而是改用脚本导入项目里同时也提供了 MSMS.sql 文件作为备用方案。脚本导入的方式是用查询分析器打开 MSMS.sql在 master 库上执行它会自动创建 MSMS 数据库、建表并插入初始数据——包括药品台账和用户账号信息。-- 在 SQL Server 查询分析器中执行前先确认当前连接是 master 库 USE master; GO -- 创建数据库指定物理文件路径注意修改为你机器上的实际目录 CREATE DATABASE MSMS ON PRIMARY ( NAME NMSMS_Data, FILENAME ND:\MSSQL\Data\MSMS_Data.mdf, SIZE 10MB, FILEGROWTH 10% ) LOG ON ( NAME NMSMS_Log, FILENAME ND:\MSSQL\Data\MSMS_Log.ldf, SIZE 5MB, FILEGROWTH 10% ); GO这段脚本先切换到 master 库再用 CREATE DATABASE 手动定义数据库文件和日志文件的物理路径。NAME 是逻辑文件名FILENAME 是磁盘路径——这里最容易出错的是路径不存在SQL Server 不会自动帮你建目录执行前必须保证 D:\MSSQL\Data\ 已经存在。SIZE 和 FILEGROWTH 定义了初始容量和自动增长比例对于课程设计几百条数据量来说10MB 起步完全够用。执行完 CREATE DATABASE 之后MSMS.sql 里通常还有后续的 USE MSMS、CREATE TABLE 和 INSERT 语句一次批量执行完就行。如果你是在企业管理器里用打开 SQL 脚本的方式执行记得把 GO 分隔符保留否则批处理会被截断表建一半就停了。2.2 高版本 SQL Server 兼容处理版本向上迁移的两条路径很多读者拿到的机器环境不是 SQL Server 2000而是 2005、2008 R2、2012 甚至 2016。直接附加 2000 的 MDF 文件在高版本上十有八九报错因为数据库文件格式的内部版本号不同高版本引擎不认旧格式。SQL Server 官方支持低版本附加到高版本但不支持高版本文件降级附加到低版本——所以如果你手头只能装 SQL Server 2000反而没这个问题。处理方法我一般分两步走。第一步先不附加直接用脚本建库也就是 2.1 里的路径。第二步是版本兼容性验证脚本里如果用了 2000 时代的语法比如 FROM 子句带表别名不加 AS在 2008 之后依然能跑但如果你遇到关键字 AS 附近有语法错误这类问题多半是脚本里混入了高版本保留字需要手动替换。另外注意排序规则2000 老库默认排序规则通常是 Chinese_PRC_CI_AS高版本安装时如果选了不同的排序规则脚本执行时会因为排序规则冲突报错解决办法是在 CREATE DATABASE 语句里显式加上 COLLATE Chinese_PRC_CI_AS。如果你确实只有高版本 SQL Server又不想跑脚本另一个办法是装一个 SQL Server 2000 的实例并行。2000 的企业版支持安装在 Windows 7 之后的系统上但需要开启命名管道协议并且要对 SQLServer.exe 设置兼容模式。相比之下脚本导入是更省事的路径我建议直接走这条路绕开版本附加的兼容黑洞。2.3 账号验证管理员账号 8 背后的表结构与初始数据系统默认的管理员账号是 8、密码是 8这个信息不是写在代码里的常量而是存在数据库的用户表中。你要理解这套系统的登录逻辑得先找到存放用户信息的表。根据项目文件的命名习惯和常规课程设计结构用户表通常叫 MSMS_User 或者类似的名字字段至少包含用户编号、用户名、密码和角色标识。用查询分析器连接上 MSMS 库后执行下面的 SQL 把用户表的内容捞出来看-- 查看用户表结构和现有账号 USE MSMS; GO SELECT * FROM dbo.MSMS_User; GO如果你发现这个表名不对可以先执行SELECT name FROM sys.tables把所有表名列出来再逐一定位。账号 8、密码 8 说明系统没做加密存储密码字段是明文或者简单处理的——这在课程设计里很常见但也意味着你部署到真实环境前必须改掉。作为复现者你要做的第一件事是用这个账号登录成功后在数据库里直接插入一个自己的管理员账号免得在代码里到处搜密码硬编码。插入新账号的参考语句如下-- 插入一个新的管理员账号密码字段按原表结构存储 USE MSMS; GO INSERT INTO dbo.MSMS_User (UserID, UserName, UserPwd, UserRole) VALUES (admin, 系统管理员, 123456, 管理员); GO字段名如果和实际表结构不一致执行会报错所以先用 SELECT * 看明白原表的列名再按实际字段调整。2.4 登录逻辑追溯从界面输入到数据库校验的完整链路理解了用户表之后登录逻辑就好拆了。C# WinForms 登录窗体的典型流程是用户在文本框输入账号密码点击登录按钮触发 Click 事件事件里读取文本框内容拼接 SQL 查询语句或调用存储过程把查询结果和输入比对比对成功则打开主窗体否则弹出提示。这个项目里登录校验的逻辑大概率写在某个 Login.cs 或 MainForm.cs 中关键代码先建立 SqlConnection再通过 SqlCommand 执行SELECT COUNT(*) FROM 用户表 WHERE 账号 uid AND 密码 pwd。用参数化查询是规范做法但如果原项目偷懒用了字符串拼接你复现时最好改成参数化否则容易被 SQL 注入课程设计答辩时老师也喜欢揪这个点。参数化改造的示例片段// 参数化查询避免字符串拼接导致注入风险 string sql SELECT COUNT(*) FROM MSMS_User WHERE UserID uid AND UserPwd pwd; using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(uid, txtUserId.Text.Trim()); cmd.Parameters.AddWithValue(pwd, txtPassword.Text); conn.Open(); int count (int)cmd.ExecuteScalar(); if (count 0) { // 登录成功跳转主窗体 } }这里要点是 AddWithValue 的用法uid 和 pwd 是 SQL 里的参数占位符C# 这边通过 Parameters 集合传值类型和长度由 SQL Server 自动推断。如果原代码用的是拼接你会看到形如SELECT * FROM ... WHERE UserID txtUserId.Text 的写法这种写法只要输入一个单引号就能把 SQL 语句炸穿务必替换。3. 核心代码结构拆解YaoPinChaXun 与 YaoPinXinXiGuanLi 两个窗体的职责边界3.1 药品信息管理窗体增删改查的四大操作如何落地项目文件里反复出现的 YaoPinXinXiGuanLi.cs 是药品信息管理的主窗体职责是维护药品基础数据对应数据库里的药品表通常包含药品编号、药品名称、规格、生产厂家、批号、有效期、库存数量、进货价格、销售价格等字段。这个窗体的核心操作是增删改查四件事分别对应不同的按钮事件。新增操作的实现逻辑是把窗体上的各个文本框内容拼装成 INSERT 语句调用 ExecuteNonQuery 执行执行成功后重新加载列表数据。修改操作是先定位选中行的主键再执行 UPDATE。删除操作是 DELETE 语句但课程设计里常常忽略关联数据——如果药品表被销售记录表外键引用直接删除会触发外键冲突。我发现很多同学在这个位置卡住报错信息是DELETE 语句与 REFERENCE 约束冲突解决思路是先查关联表有销售记录就提示用户不能删而不是把外键规则去掉强行删。参考代码结构// 删除药品前先检查是否存在销售明细 string checkSql SELECT COUNT(*) FROM SaleDetail WHERE DrugID drugId; using (SqlCommand checkCmd new SqlCommand(checkSql, conn)) { checkCmd.Parameters.AddWithValue(drugId, currentDrugId); int refCount (int)checkCmd.ExecuteScalar(); if (refCount 0) { MessageBox.Show(该药品已有销售记录无法删除。); return; } }这段检查逻辑单独拎出来说是因为它反映了一个设计习惯删除操作不能闷头执行先做引用完整性校验是必要的。如果你只是交作业不写检查也能跑通但一旦数据库里有了销售数据翻车概率百分之百。3.2 药品查询窗体模糊查询与精确查询的筛选实现YaoPinChaXun.cs 是药品查询窗体功能是让用户输入关键词或条件从数据库里筛选出符合条件的药品记录。核心是查询条件构造逻辑常见的套路是把文本框内容拼进 WHERE 子句比如按药品名称模糊查询时用 LIKE %关键词%。但这里有个常见坑如果你想让某个条件为空时不过滤就需要在 C# 代码里动态拼 SQL而不是写死一条完整语句。动态拼接的条件逻辑参考string sql SELECT * FROM Drugs WHERE 1 1; if (!string.IsNullOrEmpty(txtDrugName.Text)) { sql AND DrugName LIKE name; cmd.Parameters.AddWithValue(name, % txtDrugName.Text.Trim() %); } if (!string.IsNullOrEmpty(txtFactory.Text)) { sql AND Factory LIKE factory; cmd.Parameters.AddWithValue(factory, % txtFactory.Text.Trim() %); }WHERE 1 1是一个取巧写法目的是让后续的 AND 条件不需要判断是不是第一条直接追加字符串就行。这种写法在课程设计里很常见可读性也还行但注意参数的顺序必须和 SQL 字符串里的 name、factory 声明顺序一致SqlCommand 的参数集合不要求物理顺序但逻辑上可读性差的话后面排查问题会想骂人。查询结果展示通常是绑定到 DataGridView用 SqlDataAdapter 填充 DataTable再把 DataTable 赋给 DataSource 属性。如果你发现查询后表格不刷新多半是 DataSource 重新赋值后没有调用 DataGridView 的 Refresh 方法或者 BindingSource 没有重新定位。一个简单粗暴的办法是每次查询都 new 一个新的 DataTable 再赋值强制触发重新绘制。3.3 项目文件地图哪些文件是核心逻辑哪些可以忽略项目文件清单里有一堆 .cache 文件——DesignTimeResolveAssemblyReferencesInput.cache、ResolveAssemblyReference.cache、GenerateResource.Cache——这些是 Visual Studio 编译过程中生成的中间缓存不是源码。你拿到压缩包后第一步应该把这三个 .cache 文件删掉它们不代表任何业务逻辑留着只会让项目目录变乱也不会影响编译。真正需要关注的是这几个核心文件MSMS.csproj 是 C# 项目文件定义了整个项目的编译入口、引用程序集和窗体列表YaoPinChaXun.cs 是查询窗体代码YaoPinXinXiGuanLi.cs 是管理窗体代码MSMS.cdb 可能是数据库连接配置或者备份文件视项目打包者的命名习惯而定。打开 MSMS.csproj 能看到完整的源文件清单如果代码里引用了其他窗体或类编译时缺文件会直接报错。我的习惯是拿到这类项目后先用 Visual Studio 打开 .csproj尝试生成一次解决方案把编译错误当成路线图——编译器会告诉你哪些类没找到、哪些引用缺失、哪些命名空间冲突。这比逐行读代码高效得多。4. 连接字符串配置SQL Server 2000 环境下的连接黑匣子4.1 SqlConnection 连接字符串的五种形态这套系统要跑起来连接字符串是绕不过去的配置项。C# 里连接 SQL Server 最经典的是 System.Data.SqlClient 命名空间连接字符串写在 App.config 或窗体代码的构造函数里。不同数据库实例、不同认证方式字符串差异很大你必须根据自己机器的实际环境改否则报用户登录失败或服务器不存在毫不奇怪。常见连接字符串形态如下表场景连接字符串说明SQL Server 2000 本机默认实例 Windows 认证Serverlocalhost;DatabaseMSMS;Integrated Securitytrue;本机跑最省事不需要账号密码SQL Server 2000 本机命名实例 混合认证Serverlocalhost\SQLEXPRESS;DatabaseMSMS;User IDsa;Password123456;实例名按你安装时设置的改远程服务器 SQL 认证Server192.168.1.100,1433;DatabaseMSMS;User IDsa;Password你的密码;逗号后面是端口号带连接超时的版本Serverlocalhost;DatabaseMSMS;Integrated Securitytrue;Connect Timeout5;超时 5 秒快速报错便于排查兼容老驱动ProviderSQLOLEDB;Data Sourcelocalhost;Initial CatalogMSMS;User IDsa;Passwordxxx;这是 OleDb 写法不是 SqlClient课程设计项目里连接字符串通常写在 App.config 的 connectionStrings 节点或者直接硬编码在代码里。硬编码的问题是换机器就必须改源码重新编译我建议你拿到项目后第一时间改成 App.config 配置这样以后部署不用动代码。App.config 的配置节点示例connectionStrings add nameMSMSConnectionString connectionStringServerlocalhost;DatabaseMSMS;Integrated Securitytrue; providerNameSystem.Data.SqlClient / /connectionStrings代码里通过 ConfigurationManager 读取string connStr ConfigurationManager.ConnectionStrings[MSMSConnectionString].ConnectionString;注意使用 ConfigurationManager 前要在项目里添加 System.Configuration 程序集引用否则编译时提示找不到类型。如果你不想引这个也可以用 Properties.Settings.Default 的方式但那个是强类型设置配置路径在项目设置文件里不如 App.config 直观。4.2 TCP/IP 与命名管道SQL Server 2000 网络协议引发的连接失败SQL Server 2000 默认启用的是命名管道Named Pipes协议而 SqlClient 默认的连接顺序是先 TCP/IP 再命名管道。如果你的 SQL Server 2000 没开 TCP/IP或者防火墙挡了 1433 端口你就可能遇到超时时间已到或者在向服务器发送请求时发生传输级错误这类报错。排查方法是打开 SQL Server 2000 的网络实用工具在服务器端启用 TCP/IP 协议并确认端口是 1433。然后用命令行工具测试连通性telnet localhost 1433如果 telnet 窗口直接黑屏或者提示连接失败说明 SQL Server 没监听 1433。这时候去 SQL Server 2000 的服务器网络配置里把 TCP/IP 启用重启 SQL Server 服务。命名管道也可以保留但 C# 连接字符串里即使你不指定协议SqlClient 也会自动尝试所以协议没开全就会先卡在 TCP/IP 上等待超时。另一个隐蔽问题是 SQL Server 2000 在 Windows 7 之后系统上默认不自动启动 SQL Server 服务你要去服务管理器services.msc里找到 MSSQLSERVER 服务把它设为自动启动然后手动启动一次。很多同学项目代码写对了但服务没起报了could not open a connection to SQL Server就觉得是代码问题其实只是服务没运行。4.3 登录账号 8 与连接字符串中的 sa 账号是两回事这里需要特别区分两组账号连接字符串里用的 sa 或者 Windows 认证账号是数据库引擎层面的登录账号系统里输的管理员账号 8、密码 8是应用系统在用户表里维护的业务账号。两者不是一回事顺序是C# 程序先通过连接字符串连接数据库连接成功后程序再执行登录 SQL 去校验用户表里的 8 和 8。如果你遇到用户 sa 登录失败那是连接字符串的问题去检查 SQL Server 认证模式。SQL Server 2000 默认是 Windows 认证模式你需要在企业管理器里把服务器认证模式改为SQL Server 和 Windows混合模式然后给 sa 设一个密码。改完要重启 SQL Server 服务。如果你改成混合模式后不想用 sa也可以新建一个数据库登录名指向当前 Windows 账号或者新建 SQL 账号然后在连接字符串里换成这个账号。这个小节想强调的是别改了半天系统登录逻辑结果发现连接字符串的账号根本进不了数据库问题根本不是出在业务代码上。5. 避坑指南C# 医药销售系统复现路上最常见的五个坑5.1 附加数据库失败错误 9003 或版本不兼容现象在 SQL Server 2008 及以上版本附加 MSMS_Data.mdf 时弹出错误提示日志显示数据库版本 539服务器版本 XX 以上无法打开。原因SQL Server 2000 的数据库文件内部版本号是 539高版本引擎不能直接附加旧版本文件必须通过脚本建库或使用 SSMS 的导入数据功能。解决放弃附加路径改用项目附带的 MSMS.sql 脚本执行建库。执行时注意 GO 分隔符建议直接在 SQL Server Management Studio 中打开脚本文件整批执行遇到报错就看具体是哪条语句通常是路径问题或排序规则冲突。5.2 连接字符串报错提示找不到网络路径或实例名无效现象运行程序后弹窗提示在建立与服务器的连接时出错。在连接到 SQL Server 2000 时在默认的设置下 SQL Server 不允许进行远程连接可能会导致此失败或者实例名无效。原因连接字符串里写的服务器名和实际 SQL Server 实例不匹配。默认实例是 localhost 或机器名命名实例需要写机器名\实例名。另外 SQL Server 2000 有时服务没启动也会被误判为实例名无效。解决先用 SSMS 或查询分析器手动连接一次确认能连上再把连接字符串复制到代码里。如果查询分析器用 Windows 认证能连但代码里用 SQL 认证报错去确认你是否开启了混合认证模式。5.3 运行程序时点登录没反应不报错也不跳转现象在登录界面输入 8 和 8点击登录按钮程序既不报错也不进入主界面像是卡住或者静默失败。原因典型的逻辑设计缺陷——登录失败的代码路径里 MessageBox 被注释掉了或者 SQL 查询结果判断逻辑有误count 值被赋值但条件判断写成了if (count 1)单等号赋值而非双等号比较导致永远为 true 或永远进不了分支。解决在按钮事件的第一行加断点F11 逐行执行查看 count 的真实值。如果是赋值符号问题改成if (count 1)。另外检查 SQL 语句查的是哪张表确认用户表里确实有 8 这个账号。5.4 DataGridView 数据不刷新查询后还是旧数据现象第一次加载正常但新增或删除药品后表格内容不更新必须重启程序才能看到最新数据。原因DataGridView 的 DataSource 没有被重新绑定。新增或删除后代码可能只操作了数据库但没有重新执行 SELECT 语句也没有把结果赋给 DataSource。解决在增删改操作成功后调用一个公共的 LoadData() 方法这个方法里重新执行查询、重新 new DataTable、重新赋值 DataSource。注意每次赋值前可以先将 DataSource 设为 null再赋新值强制触发刷新事件。5.5 中文字符乱码药品名称显示问号或乱码现象从数据库查出来的药品名称在 DataGridView 里显示成???或乱码。原因数据库字段的排序规则不是 Chinese_PRC_CI_AS或者 SQL Server 安装时选择了非中文排序规则导致存储和读取时字符集不一致。C# 的 SqlClient 默认使用 Unicode 传输一般不会因为传输编码出错问题基本都出在数据库端。解决在查询语句里对关键字段显式指定排序规则或者修改数据库排序规则ALTER DATABASE 语句。修改数据库排序规则对已有数据可能无效最稳妥的方式是重新建库导入时指定排序规则或者直接修改表的列排序规则属性。6. 实战扩展给系统加一个库存预警功能把 DataGridView 用活这套系统的核心是药品信息管理和查询但如果你要在答辩时展示一些自己的改动最加分的方向是加一个库存预警模块。思路很简单在药品表里假设有库存数量字段StoreCount和预警阈值字段AlertCount查询时筛选出 StoreCount AlertCount 的记录用一个专门的状态标志列展示出来。具体的改动分成三块数据库层面检查有没有 AlertCount 字段没有就加一列默认值设为 10C# 层面新增一个查询按钮点击后执行筛选 SQL界面层面在 DataGridView 里对预警行用不同颜色标注。新增字段的 SQL-- 检查列是否存在不存在则添加预警阈值列 IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id OBJECT_ID(Ndbo.Drugs) AND name AlertCount ) BEGIN ALTER TABLE dbo.Drugs ADD AlertCount INT NOT NULL DEFAULT 10; END GOC# 代码里加载数据时遍历 DataRow判断库存是否低于阈值然后对当前行设置行背景色// 加载完 DataTable 后对预警数据行标记颜色 foreach (DataRow row in dt.Rows) { int store Convert.ToInt32(row[StoreCount]); int alert Convert.ToInt32(row[AlertCount]); if (store alert) { // 这里通过数据绑定找到对应行的索引 int rowIndex dt.Rows.IndexOf(row); dataGridView1.Rows[rowIndex].DefaultCellStyle.BackColor Color.LightYellow; } }这段代码本身并不复杂但它是你理解 DataGridView 数据绑定和行样式控制的入口。顺带一提真要做这个功能最好把判断逻辑封装成一个独立方法而不是全堆在按钮事件里。从那以后我每次拿到课程设计类项目都会先跑通一条核心链路——数据库能连、账号能登、列表能查、增删改能用再去谈花哨的功能扩展这套系统我复现了两遍每一步的坑都记在这篇文章里了希望帮到你。本文还有配套的精品资源点击获取