资讯动态

C# WinForms三层架构图书管理系统源码全解析

发布时间:2026/10/19 20:05:23 来源:尧图企业网站定制
简介这是一套基于C# WinForm与SQL Server的图书信息管理系统项目采用三层架构面向具备初步C#语法基础、想学习窗体应用与数据库联动的初学者。压缩包共135个文件大小约901KB包含C#源码、解决方案sln与工程csproj、编译产物exe/dll/pdb以及resx/resources资源文件和txt说明文档数据库部分以表结构代码形式提供按要求生成即可接入系统。系统核心功能围绕信息记录的增删查改展开界面与数据访问分层设计代码量适中调试简单能直观看到WinForm控件与SQL语句之间的调用关系。包内项目按Models、DAL、BLL等分层组织方便逐个模块阅读和改造同时附带使用说明可帮助初学者快速搭建一个可运行的图书管理系统也可作为毕业设计的基础项目便于在此基础上扩展功能。目前这套资源已有3087人学习下载对于需要快速上手C#连接数据库的学习者而言是一份轻量实用的参考。1. 从一次课设翻车说起这套C#windows窗体图书信息管理系统值得拆我第一次拆这套C#windows窗体图书信息管理系统(含数据库)的源码是帮别人看课设的时候。打开压缩包文件夹里躺着一堆.csproj和大把.cache文件哪个是主窗体、哪段SQL对应哪张表全得自己翻。等我把四个工程的关系理顺才发现这套系统的价值不在界面而在分层——界面、业务、数据访问、实体模型被拆成四个独立工程每一层只干自己那摊事。没有EF没有DapperSQL全是手写ADO.NET反而让数据从按钮点击到落库的每一步都摸得着。如果你正在做C#课程设计或者第一次接触WinForms三层架构想找一个能快速跑通、又能逐行看懂的样例这套系统是个不错的切入对象。2. 架构与目录四个工程文件拼出的数据闭环2.1 先分清谁是谁四个csproj文件各管哪摊事打开工程目录最显眼的是四个.csproj文件。先别急着找Form1.cs按工程边界把它们分清楚整个项目就通透了一半。BSM.csproj是WinForms主工程窗体、控件、按钮事件全在它里面BSM.BLL.csproj是业务逻辑层负责参数校验、默认值赋值这类业务规则BSM.DAL.csproj是数据访问层专门和SqlConnection、SqlCommand打交道BSM.Models.csproj是实体模型层里面全是只有属性没有方法的POCO类。拿典型的增删查改功能来说四个工程的工作流是UI收到用户输入交给BLL处理规则BLL将过滤后的请求传给DALDAL执行SQL并把数据转成实体原路返回UI。要特别注意引用方向UI可以引用BLL和ModelsBLL可以引用DAL和ModelsDAL只能引用Models反了就报编译错。工程文件典型内容职责红线BSM.csproj窗体、按钮事件、DataGridView不能出现SqlConnection、SELECTBSM.BLL.csprojStudentBLL等业务类不能出现SQL语句BSM.DAL.csprojStudentDAL、SQLHelper不弹MessageBox、不做业务判断BSM.Models.csprojStudent实体类只有属性没有方法逻辑有一点要说清楚这套系统为了演示方便查询直接返回List 没有多做DTO包装。初学者别觉得这层多余也别觉得非要再包一层——现阶段保持简单能跑通、能看懂就是最大的收获。等你接触复杂项目再引入DTO、AutoMapper这些方案也不迟。2.2 一次查询的完整路线从按钮到DataGridView拿“查询全部学生信息”这个功能举例完整链路是六步用户在窗体上点“查询”按钮按钮Click事件里new一个StudentBLL对象调用方法StudentBLL先做基础校验校验通过后调用StudentDAL的查询方法DAL执行SQL把SqlDataReader读成Student对象列表返回UI收到List 直接赋给DataGridView的DataSource。核心在第4和第6步UI只认识Student实体不关心数据从哪来DAL只负责把SQL跑完不关心数据最终显示在哪个控件。这种“中间人”设计就是三层架构和单文件全塞代码最本质的差别。传输对象在这里很有意思UI、BLL、DAL三个工程都认识List 这个类型它就像三个部门之间的交接单。只要交接单的属性稳定任何一个部门改内部实现都不影响另外两个。这就是分层带来的可替换性也是这套系统最值得借鉴的架构思维。2.3 满屏的.cache文件能删但更建议忽略项目包里大量出现AssemblyReference.cache、DesignTimeResolveAssemblyReferencesInput.cache四个工程文件夹里几乎都有。这些是Visual Studio在设计时为了加快程序集引用解析生成的缓存文件每次编译都会重新生成属于编译副产物提交到源码库里只会制造噪音和合并冲突。直接删掉完全不影响编译。如果你用git管理这个项目我一般会在根目录放一个.gitignore至少包含这些内容*.cache bin/ obj/ .vs/这段处理完目录立刻清爽。顺带说一句项目里还有不带Input的DesignTimeResolveAssemblyReferences.cache变体同样是设计时缓存忽略规则写*.cache就能全盖住。别在它上面浪费时间。2.4 连接字符串程序怎么找到SQL Server数据库连不上八成问题出在连接字符串。这套系统是WinForms项目常见的配置位置有两个一是app.config的connectionStrings节点二是DAL工程里单独写一个连接字符串类。我建议用app.config因为改数据库地址只需改配置不用重新编译。配置内容是这种格式configuration connectionStrings add nameBSMDB connectionStringData Source.;Initial CatalogBSMDB;User IDsa;Password123456; providerNameSystem.Data.SqlClient/ /connectionStrings /configuration拆开看参数Data Source.代表本机默认实例如果装的是命名实例或远程数据库要写成“服务器名\实例名”或IP地址Initial Catalog是数据库名User ID和Password是SQL Server登录账号如果环境用的是Windows身份验证把后半段换成Integrated SecurityTrueData Source保持不动。DAL里读取连接字符串的代码通常是private string _connStr ConfigurationManager.ConnectionStrings[BSMDB].ConnectionString;这里的BSMDB必须和app.config里name属性值一字不差。我见过不少项目就是在这个字符串里多打了一个空格程序启动时直接抛连接异常排查起来特别费劲。另外提醒一句很多初学者遇到SSMS能连、程序连不上的情况多半是SQL Server实例启用了Windows身份验证而程序却用sa账号去登录。检查SQL Server属性里的安全性设置或改用Integrated SecurityTrue比瞎试密码靠谱得多。3. 核心模块拆解从实体类到界面绑定的完整链路3.1 实体层Student类只负责搬运数据Models工程里的Student类是整套系统的最小公约数它的属性就是学生表的列。看这段代码namespace BSM.Models { /// summary /// 学生实体对应数据库表 Student /// /summary public class Student { public int Id { get; set; } // 自增主键 public string StudentNo { get; set; } // 学号唯一 public string Name { get; set; } // 姓名 public string Gender { get; set; } // 性别 public string Major { get; set; } // 专业 public string Phone { get; set; } // 联系电话 } }这个类没有方法、没有任何逻辑存在的意义就是让三层之间流动数据时有共同语言。为什么不用DataTable因为DataTable取字段要写reader[Name]这种魔法字符串写多了容易手滑编译期还不报错用强类型实体类属性名写错编译器直接拦你。这个经验我拆过好几个项目后感受特别深实体类越简单后面越省心。3.2 数据访问层手写SQL的查询方法DAL层是这套系统的“心脏”。先看查询全部学生的方法public ListStudent GetAllStudents() { ListStudent list new ListStudent(); string sql SELECT Id, StudentNo, Name, Gender, Major, Phone FROM Student;; using (SqlConnection conn new SqlConnection(_connStr)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { using (SqlDataReader reader cmd.ExecuteReader()) { while (reader.Read()) { Student stu new Student { Id Convert.ToInt32(reader[Id]), StudentNo reader[StudentNo].ToString(), Name reader[Name].ToString(), Gender reader[Gender].ToString(), Major reader[Major].ToString(), Phone reader[Phone].ToString() }; list.Add(stu); } } } } return list; }这段代码的三层using非常关键。SqlConnection、SqlCommand、SqlDataReader都实现了IDisposable用using包住方法跑完数据库连接和网络资源立即释放不会占满连接池。这是资源管理的基本功很多初学者会漏。reader[Id]返回的是object类型所以需要Convert.ToInt32转换其他字段用ToString()兜底。如果数据库里Phone是NULLreader[Phone].ToString()返回空字符串不会崩但如果你对NULL列用Convert.ToInt32会当场抛异常——这是DAL层最常见的翻车点。3.3 数据访问层新增学生必须用参数化SQL新增方法最能看出代码功力。糟糕的写法是把值直接拼进字符串我见过太多代码这样写// 错误写法绝对不要用 string sql INSERT INTO Student (StudentNo, Name) VALUES ( stu.StudentNo , stu.Name );正确写法是参数化SQLpublic bool AddStudent(Student stu) { string sql INSERT INTO Student (StudentNo, Name, Gender, Major, Phone) VALUES (StudentNo, Name, Gender, Major, Phone);; using (SqlConnection conn new SqlConnection(_connStr)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(StudentNo, stu.StudentNo); cmd.Parameters.AddWithValue(Name, stu.Name); cmd.Parameters.AddWithValue(Gender, stu.Gender); cmd.Parameters.AddWithValue(Major, stu.Major); cmd.Parameters.AddWithValue(Phone, stu.Phone); return cmd.ExecuteNonQuery() 0; } } }参数说明StudentNo、Name这些参数名必须和SQL语句里的占位符严格一致大小写不敏感但拼写不能错。AddWithValue不是参数化的唯一写法但在绝大多数场景够用。为什么参数化能防SQL注入因为参数值会被当成数据处理而不是SQL片段拼接进语句你再也不用担心学生名字里出现单引号导致语法错误。我见过用拼接方式做的系统一个学生名字叫OBrien整条INSERT直接报错这就是把数据当代码的代价。3.4 业务逻辑层校验不该撒在UI里BLL层做的事情在代码里看着很轻容易被忽略。看一个AddStudent方法public bool AddStudent(Student stu) { if (string.IsNullOrWhiteSpace(stu.StudentNo)) { throw new ArgumentException(学号不能为空, StudentNo); } if (string.IsNullOrWhiteSpace(stu.Name)) { throw new ArgumentException(姓名不能为空, Name); } return studentDAL.AddStudent(stu); }如果不写这一层同样的校验会被复制到每个窗体的每个事件里新增窗体校验一遍修改窗体再校验一遍规则变了全局搜索找三遍。把校验放在BLL窗体只管调用被拦下时弹个异常提示就行。这套系统的校验还比较简单实际业务里可以继续加“学号重复检查”“手机号格式匹配”这类规则。因为BLL在中间层UI改版不会影响规则本身规则修改时UI也不用动排错范围反而缩小了。3.5 界面层按钮事件与DataGridView绑定最后一步是在BSM主工程里把数据展示出来。典型代码private void btnQuery_Click(object sender, EventArgs e) { StudentBLL bll new StudentBLL(); ListStudent list bll.GetAllStudents(); dataGridViewStudents.DataSource list; }这段代码很短但体现的调用习惯很重要UI不写SQL、不直接new SqlConnection而是让BLL去调DAL。dataGridViewStudents.DataSource直接接收List DataGridView会自动识别实体的公共属性把Id、StudentNo、Name等渲染成列不需要手工配置列。如果你想按学号过滤只需要把文本框的值传进BLL方法由BLL决定是带WHERE查还是查全部别在窗体里拼SQL。这样哪天把文本框搜索框改成下拉搜索框BLL和DAL一行都不用动。4. 从零建库到跑通SQL脚本、连接配置与运行检查单4.1 用表结构代码把数据库建出来这套系统的数据库是SQL Server源码包没有直接给.mdf而是给了表结构SQL脚本——这是为了避免VS版本、SQL Server版本和.mdf附加路径差异引起的兼容性问题。用脚本生成数据库反而有两个好处一是任何版本都能跑二是你亲手把表和约束建一遍对结构记忆更深。打开SSMS新建查询执行下面这段脚本CREATE DATABASE BSMDB; GO USE BSMDB; GO CREATE TABLE Student ( Id INT IDENTITY(1,1) PRIMARY KEY, StudentNo NVARCHAR(20) NOT NULL UNIQUE, Name NVARCHAR(50) NOT NULL, Gender NVARCHAR(2) NOT NULL DEFAULT N男, Major NVARCHAR(50) NULL, Phone NVARCHAR(20) NULL ); GO INSERT INTO Student (StudentNo, Name, Gender, Major, Phone) VALUES (N20240001, N学生A, N男, N计算机科学, N13800138000), (N20240002, N学生B, N女, N软件工程, N13900139000); GO前两行先建库再切库。如果实例上已经有同名数据库会报错手动删掉旧库再执行。NVARCHAR是为了支持中文SQL Server里N前缀把字符串显式声明为Unicode避免插入中文变成乱码。Id用IDENTITY(1,1)是自增列从1开始、每次加1插入时完全不用管它。学号列加了UNIQUE约束防止两条数据用同一个学号数据库层直接拒绝重复值比代码里List.Contains判断可靠得多。4.2 把连接字符串改成本机环境脚本建完后需要让程序知道数据库在哪。右键BSM主工程的app.config打开看到connectionStrings节点把Data Source改成你的SQL Server地址。数据库就在本机、实例是默认实例的话Data Source.就够了。笔记本用户如果只装了LocalDB要改成Data Source(LocalDB)\MSSQLLocalDBInitial Catalog还是BSMDB。改完保存重新生成解决方案。有一点特别值得养成习惯app.config被修改后需要重新编译才会复制到bin目录下的BSM.exe.config里。如果你直接双击bin目录下的exe改了源码里的app.config不会生效——这个细节我翻过车。另外连接字符串里的密码属于敏感信息本地学习直接写在app.config里没问题但如果要发布给别人至少用DPAPI加密或者把配置节单独放。我自己做课设时密码裸写在app.config里被同学传了几轮想想挺后怕。4.3 从打开解决方案到看到主窗体一份检查单我按自己的习惯整理了一份运行检查单避免每次打开都从头猜用Visual Studio打开BSM.sln注意是.sln不是单独的.csproj右键解决方案检查四层工程引用有没有断线还原NuGet包确认app.config里连接字符串的Data Source和数据库名正确按CtrlShiftB编译留意错误列表F5运行进入主窗体点“查询”看DataGridView是否有数据如果报“无法连接到服务器”依次确认SQL Server服务状态、防火墙放行、连接字符串这里最坑的是第一步只打开BSM.csproj的话BLL和DAL不会被自动编译运行时会报“找到了该程序集清单定义但找不到程序集”。所以务必打开.sln。这套检查单覆盖了最常见的失败模式多数时候不是代码写错而是环境没对齐。数据库名称、登录方式、服务状态、防火墙这些和代码无关的细节反而最耗时。5. 常见问题与排查五个让新手原地踏步的坑5.1 现象程序报“在建立与服务器的连接时出错”原因绝大多数情况不是代码问题是连接字符串里的Data Source和实际SQL Server实例不匹配或者SQL Server服务根本没启动。解决先在SSMS里确认自己能不能连上本机。SSMS都连不上去Windows服务里找到“SQL Server (MSSQLSERVER)”看状态是否“已停止”右键启动。SSMS能连、程序连不上那就是连接字符串不一致或账号类型不对按4.2节排查重点核对User ID/Password和Integrated Security。这个问题常被当成玄学其实十次里有九次是配置没对齐。5.2 现象insert成功了但DataGridView还是旧数据原因查询是重新查的但绑定的数据源没重新赋值或者新增数据插到了另一个数据库。“另一个数据库”这种坑最隐蔽app.config里Initial Catalog写的BSMDB但表建在了master或tempdb里。解决在SSMS执行USE BSMDB; SELECT COUNT(*) FROM Student;看看数据是不是真在BSMDB里。如果不在重新执行建表脚本。刷新界面的话在按钮事件里重新调用一次GetAllStudents并重新给DataSource赋值别忘了这步。5.3 现象删除时提示“DELETE语句与REFERENCE约束冲突”原因数据库里设计了外键关联比如借阅表关联学生表但界面层的删除功能没做级联处理也没提示哪些记录正在被引用。解决两种改法。轻量级做法是在DAL的删除方法里先删引用表中的相关记录再删主表记录包到一个事务里重量级做法是给外键加ON DELETE CASCADE但误删风险很大初学阶段不建议用。核心是先理解外键关系再决定业务上删除时该怎么处理依赖数据。5.4 现象编译报错“未能找到类型或命名空间名”原因四层工程的引用关系断了或者打开的不是.sln而是单独.csproj。解决右键解决方案选“重新生成”按依赖顺序先编译Models再DAL再BLL最后UI。如果仍然标黄打开每个工程的引用节点检查是否丢失了ProjectReference有则手动添加。记住这个经验拿到任何多工程源码第一件事是看引用方向别急着写代码。引用关系对了编译错误能少一半。5.5 现象运行后中文显示成???或乱码原因数据库表字段用的是VARCHAR而不是NVARCHAR或者SQL脚本没加N前缀直接插入中文。解决把字段类型改成NVARCHAR。如果表已经建好执行ALTER TABLE Student ALTER COLUMN Name NVARCHAR(50);重新插入带N前缀的数据。从这套系统开始就养成用NVARCHAR的习惯能省下很多换行符和编码的破事。5.6 现象DesignTimeResolveAssemblyReferencesInput.cache总被人当成故障原因它不是故障是Visual Studio设计时缓存编译后自动生成。很多教程都没解释清楚初学者以为项目坏了。解决直接无视它。在.gitignore里加上*.cache提交代码时根本不会混进去比手动删除干净得多。真正的编译错误会出现在错误列表里不会藏在这种缓存文件里。6. 二次开发的方向把单表增删查改升级成真正可用的系统6.1 添加借阅表从管理学生延伸到管理图书最有效的扩展是加一张BorrowRecord表记录哪个学生借了哪本书。表结构可以这样设计CREATE TABLE BorrowRecord ( Id INT IDENTITY(1,1) PRIMARY KEY, StudentNo NVARCHAR(20) NOT NULL, BookName NVARCHAR(100) NOT NULL, BorrowDate DATETIME NOT NULL DEFAULT GETDATE(), ReturnDate DATETIME NULL );对应的Models工程加BorrowRecord类DAL加Insert和QueryByStudentNo方法BLL加“同一学生未还书超过3本不能再借”的规则再把一个借阅记录窗体挂到主菜单上。三层架构的好处此时会体现得很明显UI加窗体和菜单BLL加规则DAL加SQL每层改动范围都很小互相不污染。6.2 顺手把查询改成模糊搜索现在的查询是精确匹配可以把它改成LIKE模糊搜索一行SQL就能让功能体验提升一个档次string sql SELECT Id, StudentNo, Name, Gender, Major, Phone FROM Student WHERE StudentNo LIKE keyword; // 参数值传 % keyword %注意keyword里如果本身就带%会被当成通配符需要先转义或者把用户输入里的%替换成[%]这个我在实际项目里踩过。改完后回到主窗体验证三件事正常关键词能查到、含%的关键词不报错、空关键词返回全部。6.3 验证习惯改完代码先看编译再看数据我自己的习惯是每次改完一层重新生成解决方案然后用三类数据验证正常数据、边界数据空值、超长值、异常数据重复主键、非法字符。这套验证流程很短但很有效。回顾这几年拆项目的经历多数翻车都不是大改造成的而是小改后没做回归。从那以后我每次拿到别人的C#多工程源码第一件事就是先看.csproj引用方向和连接字符串再动手改任何代码。如果你正在做C#相关的课设建议把这套源码下载下来按这个思路把四个工程逐一读一遍收获会比我这篇笔记大得多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑