☰
C# WinForms三层架构图书管理系统:从增删查改到SQL Server实战
2026/10/19 10:42:28 网站建设 项目流程

简介:面向C#初学者与课程设计场景,这套Windows窗体图书信息管理系统源码提供了完整的三层架构示例,基于SqlServer数据库实现增删查改,操作逻辑清晰,调试简单。压缩包共135个文件,主体为44个cs源码文件、13个dll运行库、14个pdb调试信息以及3个exe可执行程序,另有resx资源、配置文件等,整体仅901KB,并附带数据库表结构生成代码和简体说明,还原环境省时省力。目前已有3087人学习下载,是入门Winform与数据库编程的热门参考。通过阅读源码和调试,可掌握项目分层思想、ADO.NET或SqlClient连接方式、增删查改语句写法,以及界面与逻辑分离的实现技巧;也可以在其基础上继续扩展图书借还、分类查询等模块,适合快速积累实战经验。资源内还包含sln解决方案文件,可直接用Visual Studio打开运行;数据库脚本独立给出,避免版本限制带来的环境问题,对新手相当友好。

1. 这套C# Windows窗体图书信息管理系统:先跑通增删查改,再谈架构

C# Windows窗体配上SQL Server数据库,几乎是每个做WinForms课程设计的人绕不开的组合。这套名为BSM的图书信息管理系统,就是这样一个能直接跑通的项目:项目名叫图书管理,资源内的主要功能接口落点是学生(读者)信息的增删查改,把查询、录入、修改、删除这些最常见的数据库操作完整串了一遍。系统用Visual Studio编写,数据库采用SQL Server,按BLL、DAL、Models三层拆开,适合想弄懂三层架构怎么落地、又不想一开始就被复杂业务淹没的初学者。如果你正在找C# winform数据库系统的参考代码,或者需要一个能改两三个字段就交差的练习样本,这一套值得拆开看一看。

2. 三层架构在项目里长什么样:从BSM工程文件反推职责边界

2.1 资源包里的工程与那些.cache文件究竟是谁

拿到的资源包里没有源码文件清单,只列了一批.csproj和.cache。先别急着翻代码,把工程结构看懂,后面排查问题会省一大半力气。整个BMS系统实际上由四个独立工程组成,映射到三层架构上非常工整。

工程文件名层级职责
BSM.csproj表示层(UI)窗体界面、按钮点击事件、DataGridView 数据绑定
BSM.BLL.csproj业务逻辑层承载业务规则,向上对 UI 提供方法,向下调用 DAL
BSM.DAL.csproj数据访问层负责连接 SQL Server、执行 SQL、返回数据
BSM.Models.csproj实体模型层定义与数据表对应的实体类,传递业务数据

至于BSM.csproj.AssemblyReference.cache、BSM.BLL.csproj.AssemblyReference.cache、DesignTimeResolveAssemblyReferencesInput.cache这一堆文件,是 Visual Studio 在编译过程中自动生成的中间产物,用来缓存程序集引用状态,不参与最终运行,提交代码时通常应该从版本库里忽略掉。看到它们不用紧张,真正要读的是四个.csproj对应的.cs源文件。

2.2 每层该干什么,不该干什么

很多初学者拿到三层架构的代码,第一反应是“类好多、调用好绕”,于是把业务逻辑直接写在窗体按钮里,三层就名存实亡。理解每层的边界,比背“高内聚低耦合”那套理论有用得多。

表示层(BSM)只做两件事:把用户输入收集起来,把业务层返回的结果展示出去。拿查询功能举例,用户在文本框里输入关键字、点击“查询”按钮,窗体代码只负责把文本框的值传给 BLL,然后接收 BLL 返回的 DataTable,丢给 DataGridView 显示。按钮里不出现任何SqlConnection、SqlCommand字样。

业务逻辑层(BLL)是承上启下的中间层。比如新增一条学生记录时,BLL 可以检查学号是否重复、手机号位数是否合法,再决定是否放行到 DAL。这层写得厚一点,后续改规则时就不必动窗体和 SQL。

数据访问层(DAL)只管数据库打交道:拼参数化 SQL、执行命令、把结果集填充到 DataTable 或实体对象里。至于这个数据要展示成什么样子,DAL 不关心。

实体模型层(Models)是各层之间的传话筒。这个资源里很多方法直接返回DataTable,对于学习项目来说完全够用,不必强求改成 List 。

2.3 代码链路示例:查询按钮按下后发生了什么

以“查询所有学生记录”为例,三层协作关系非常清晰。先看数据访问层的写法:

using System.Configuration; using System.Data; using System.Data.SqlClient; public class StudentDAL { private readonly string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; public DataTable GetStudents() { string sql = "SELECT StudentID, StuName, Gender, ClassName, Phone FROM StudentInfo"; using (SqlConnection conn = new SqlConnection(connStr)) using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn)) { DataTable dt = new DataTable(); adapter.Fill(dt); return dt; } } }

这里用SqlDataAdapter直接Fill到DataTable,连接对象不需要手动Open,适配器内部会自动开关连接。using双嵌套确保连接和适配器用完后立刻释放,避免连接句柄泄漏。

再来看业务逻辑层,它只是包了一层,但保留了将来插入校验逻辑的位置:

public class StudentBLL { private readonly StudentDAL dal = new StudentDAL(); public DataTable GetStudents() { // 将来可以在这里加入权限校验、结果过滤等逻辑 return dal.GetStudents(); } }

窗体代码拿到 BLL 返回的数据后,直接绑定列表控件:

private void btnQuery_Click(object sender, EventArgs e) { StudentBLL bll = new StudentBLL(); dataGridView1.DataSource = bll.GetStudents(); }

整个链路下来可以看到,UI 层不感知 SQL 语句,DAL 层不了解界面控件。出了问题,比如查询超时,先查 DAL;比如数据展示不对,先查 BLL 的取值逻辑。这种定位方式比在一大坨按钮事件代码里翻找要快得多。

3. 数据库生成实操:用表结构SQL脚本跑出可连接库

3.1 生成数据库的两种姿势,这个资源用的是脚本

下载资源时最容易踩的坑是:拿到一个.mdf数据库文件,折腾半天附加不上,报一堆版本错误。这套系统在资源描述里写明“将给定表结构代码,只需简单生成数据库即可”,意思是里面提供的是 SQL 建表脚本,不是数据库备份文件。

用脚本建库的最大好处是跨版本兼容。SQL Server 2008 到 2019 的语法差异不大,只要不涉及高阶功能,脚本基本通吃。操作流程是:打开 SQL Server Management Studio,连接到本机实例,新建一个查询窗口,把脚本内容粘贴进去执行。

IF DB_ID('BSM_DB') IS NULL CREATE DATABASE BSM_DB; GO USE BSM_DB; GO IF OBJECT_ID('StudentInfo', 'U') IS NULL CREATE TABLE StudentInfo ( StudentID INT IDENTITY(1,1) PRIMARY KEY, StuName NVARCHAR(20) NOT NULL, Gender NVARCHAR(10), ClassName NVARCHAR(30), Phone NVARCHAR(20) ); GO

第一段IF DB_ID('BSM_DB') IS NULL判断的是系统中是否已存在同名数据库,不存在才创建,避免重复执行脚本时直接报错。第二段CREATE TABLE用IF OBJECT_ID(...) IS NULL做保护,表存在时就跳过。这两层保护对初学者特别友好,脚本即使执行两次也不会翻车。

3.2 参考表结构:学生信息表与借阅数据的字段设计

这个系统的功能重心是学生信息的维护,围绕它至少会有一张学生表。参考同类系统的常见设计,核心字段大致如下:

字段名类型说明
StudentIDINT IDENTITY(1,1)主键,自增编号
StuNameNVARCHAR(20)姓名,必填
GenderNVARCHAR(10)性别,存中文“男/女”
ClassNameNVARCHAR(30)班级名称
PhoneNVARCHAR(20)联系电话

字段类型选择上有两个参数值得说:一是主键用了INT IDENTITY(1,1),自增起始值和步长都是 1,新增记录时数据库自动编号,不需要应用程序生成;二是字符类字段统一用NVARCHAR而不是VARCHAR,区别在于NVARCHAR以 Unicode 存储,直接存中文不会出现乱码和长度计算偏差。

如果资源脚本里还包含图书相关表,那么通常会有一张 BookInfo 表,字段类似BookID主键、BookName、Author、Publisher、Stock。学生表和图书表之间通过借阅记录表关联,这就是后面删除学生记录时可能触发外键约束的原因。

3.3 连接字符串放在App.config:参数逐项说明

数据库建好后,系统怎么找到它?答案在表示层(或启动项目)的App.config文件里。WinForms 程序运行时,会读取这个配置文件中的connectionStrings节点,把它交给 DAL 层的ConfigurationManager。

<connectionStrings> <add name="connStr" connectionString="Data Source=.;Initial Catalog=BSM_DB;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>

Data Source=.表示连接本机默认 SQL Server 实例,如果机器装的是命名实例,比如.\SQLEXPRESS,就必须改成对应名称,否则运行时第一个报错就是“无法连接到服务器”。Initial Catalog=BSM_DB指定要连接的数据库名,必须和脚本里创建的名字一致。Integrated Security=True使用 Windows 集成认证,开发机上不需要额外配置账号密码,最省事。

改完配置文件切记要保存并重新生成项目。App.config的修改不会像代码文件那样在编译时自动触发重新生成,有时候改了半天没生效,其实是因为运行的还是旧版本的程序集。

4. 让系统跑起来:从工程打开到完成一次完整增删查改

4.1 用Visual Studio打开项目的正确姿势

资源清单里没有.sln解决方案文件,只有四个.csproj。这种情况下不要用“打开项目”去找解决方案,直接在 Visual Studio 里用“打开项目文件”选中BSM.csproj即可。打开后,如果项目引用不到同目录下的其他三个工程,在解决方案资源管理器里右键引用节点,手动把 BSM.BLL、BSM.DAL、BSM.Models 加进项目引用。

老项目拿到新版本 Visual Studio 里,大概率会遇到“此项目与当前 Visual Studio 版本不兼容”的弹窗。处理思路是:先尝试不升级直接打开,如果编译报错,再考虑把目标框架改成当前机器上已安装的版本,比如4.6.1或4.7.2。修改目标框架后,个别 API 可能有细微差异,通常不会影响这套简单系统的运行。

4.2 新增、修改、删除:三个核心方法的正确写法

增删查改是这套系统的核心,也是课程设计答辩时老师最常盯着看的部分。DAL 层新增方法的标准写法是:

public bool AddStudent(string stuName, string gender, string className, string phone) { string sql = "INSERT INTO StudentInfo(StuName, Gender, ClassName, Phone) " + "VALUES(@StuName, @Gender, @ClassName, @Phone)"; using (SqlConnection conn = new SqlConnection(connStr)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@StuName", stuName); cmd.Parameters.AddWithValue("@Gender", gender); cmd.Parameters.AddWithValue("@ClassName", className); cmd.Parameters.AddWithValue("@Phone", phone); conn.Open(); int rows = cmd.ExecuteNonQuery(); return rows > 0; } }

ExecuteNonQuery返回的是受影响的行数,插入一条成功则返回 1,返回布尔值方便 BLL 层判断操作是否成功。所有用户输入都通过Parameters.AddWithValue作为参数传入,而不是用字符串拼接 SQL,这是防止注入的基本底线。

修改和删除方法结构几乎一样,关键在于匹配主键:

public bool UpdateStudent(int studentId, string stuName, string gender, string className, string phone) { string sql = "UPDATE StudentInfo SET StuName = @StuName, Gender = @Gender, " + "ClassName = @ClassName, Phone = @Phone WHERE StudentID = @StudentID"; using (SqlConnection conn = new SqlConnection(connStr)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@StudentID", studentId); cmd.Parameters.AddWithValue("@StuName", stuName); // 其余参数类似,省略 conn.Open(); return cmd.ExecuteNonQuery() > 0; } } public bool DeleteStudent(int studentId) { string sql = "DELETE FROM StudentInfo WHERE StudentID = @StudentID"; using (SqlConnection conn = new SqlConnection(connStr)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@StudentID", studentId); conn.Open(); return cmd.ExecuteNonQuery() > 0; } }

修改方法里WHERE StudentID = @StudentID的@StudentID是参数化占位符,不是字符串拼接,大写的表字段名在 SQL Server 中不区分大小写,但参数名必须和AddWithValue里写的一致。

4.3 操作完成后的界面刷新与数据绑定陷阱

窗体上执行完新增、修改、删除后,最典型的失误是数据操作成功了,界面上却看不到变化。原因很简单:dataGridView1.DataSource还是旧数据集的引用,DataGridView 不会自动感知数据库里的变化。

正确做法是操作完成后重新绑定一次数据源:

private void btnAdd_Click(object sender, EventArgs e) { StudentBLL bll = new StudentBLL(); bool ok = bll.AddStudent(txtName.Text.Trim(), txtGender.Text.Trim(), txtClass.Text.Trim(), txtPhone.Text.Trim()); if (ok) { dataGridView1.DataSource = bll.GetStudents(); // 重新拉取数据 } }

这里有个值得注意的细节:不要在绑定前先调用dataGridView1.DataSource = null再赋值。某些版本的 DataGridView 在置空后会取消列自动生成,重新赋值时列头就乱了。直接重新赋值是最稳妥的。另外,txtName.Text.Trim()这种写法,去掉用户误输入的首尾空格,避免把带空格的脏数据写进数据库。

5. 避坑记录:新手运行这套系统时常见的五类翻车

5.1 Visual Studio版本不兼容导致编译失败

现象:打开 BSM.csproj 后,解决方案资源管理器里项目旁边有黄色感叹号,生成时报“不受支持”或“目标框架无效”。

原因:旧版 .NET Framework 工程没有安装对应版本的 Developer Pack,或者 VS 默认打开方式不对。

解决:右键项目选“编辑项目文件”,把<TargetFrameworkVersion>节点改成机器上已安装的版本,例如v4.7.2。改完保存后重新加载项目即可。

5.2 SQL脚本执行完却找不到表

现象:在 SSMS 里执行建表脚本,提示“命令已成功完成”,但刷新表列表,只在系统表里看到一堆dbo.sys*,看不到 StudentInfo。

原因:执行脚本时选择的数据库不对。SSMS 新建查询默认连接的是 master 库,脚本里若没有明确的USE BSM_DB语句,表就建到了 master 下面。

解决:执行前先确认查询窗口工具栏下拉框选中的数据库,或者脚本开头写上USE BSM_DB; GO再执行。

5.3 程序启动报“用户登录失败”

现象:运行窗体程序,点击查询按钮后报类似“无法连接到数据库服务器”或“用户 'sa' 登录失败”的异常。

原因:连接字符串里Integrated Security=True却同时给了用户名密码;或者 SQL Server 实例名称对不上本机安装的实例。

解决:只保留Data Source=.;Initial Catalog=BSM_DB;Integrated Security=True这段 Windows 认证配置。如果实例是SQLEXPRESS,改Data Source=.\SQLEXPRESS后重试。

5.4 表格里的中文显示成问号

现象:新增记录时录入的是中文,但在 DataGridView 或 SQL Server 管理工具里看到的全是??。

原因:建表时字段类型用了VARCHAR,而VARCHAR是单字节存储,容不下中文字符。

解决:把字段类型改成NVARCHAR。如果表已经建好,执行ALTER TABLE StudentInfo ALTER COLUMN StuName NVARCHAR(20)修改类型,再把数据重新插一遍。

5.5 删除学生记录时被外键约束拦下

现象:删除一条学生记录,程序抛出异常“DELETE 语句与 REFERENCE 约束冲突”。

原因:学生表和借阅记录表之间有关联,存在借阅记录的学生不能直接从主表删除。

解决:先删除子表(借阅记录表)中该学生对应的记录,再删除主表记录。DAL 层 DeleteStudent 方法里可以先把两个操作放进一个事务,保证要么都成功要么都回滚。

6. 把这套系统改成你自己的:加模糊查询功能并验证改动

拿到一套能跑的代码,最有价值的做法不是原封不动交差,而是动手改一个功能,彻底走通“DAL改方法、BLL加转发、UI加控件”的完整流程。这里演示一个高频需求:按姓名或班级模糊查询学生。

数据访问层新增一个方法,SQL 用LIKE通配符,但通配符是拼在参数值里而不是拼在 SQL 语句里:

public DataTable SearchStudents(string keyword) { string sql = "SELECT StudentID, StuName, Gender, ClassName, Phone FROM StudentInfo " + "WHERE StuName LIKE @kw OR ClassName LIKE @kw"; using (SqlConnection conn = new SqlConnection(connStr)) using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn)) { adapter.SelectCommand.Parameters.AddWithValue("@kw", "%" + keyword + "%"); DataTable dt = new DataTable(); adapter.Fill(dt); return dt; } }

BLL 层加一个对应的方法透传参数:

public DataTable SearchStudents(string keyword) { return dal.SearchStudents(keyword); }

窗体上放一个文本框和一个“搜索”按钮,点击时把用户输入传进来:

private void btnSearch_Click(object sender, EventArgs e) { StudentBLL bll = new StudentBLL(); dataGridView1.DataSource = bll.SearchStudents(txtKeyword.Text.Trim()); }

这段代码跑通后,你其实已经掌握了三层架构里最核心的扩展模式:以后想加“按性别筛选”“按借阅数量排序”,全部按这个套路复制粘贴再改参数就行。验证改动是否成功,不要只看界面有没有数据,打开 SSMS 的 Profiler 或者简单地在 DAL 方法里加一个Console.WriteLine(sql),确认执行的 SQL 是不是预期的那句。从那以后我拿到任何新项目,第一件事都是用文本编辑器扫一遍源码里的 SQL 语句,确认没有字符串拼接,再去连库运行——这个习惯帮我省下了大量排查注入和乱码的时间。希望帮到你。

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

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

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

立即咨询