ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C#图书管理系统与SQL Server数据库配置实战:从连接到ADO.NET开发

C#图书管理系统与SQL Server数据库配置实战:从连接到ADO.NET开发 简介一份基于C#与SQL Server开发的图书管理系统课程设计项目适合正在完成数据库或C#课程大作业的计算机专业学生也适合希望了解WinForms与ADO.NET数据交互的初学者。资源共包含187个文件压缩包仅2.62MB核心为79个C#源码文件涵盖界面逻辑、业务处理与数据访问层另有数据库主文件.mdf与日志文件.ldf便于直接附加使用同时包含.resx资源文件、.xsd数据集定义及项目配置文件可快速还原Visual Studio开发环境。系统涵盖图书信息录入、查询、借还管理等典型功能并涉及SqlConnection、SqlCommand及DataTable等关键对象的用法。已有692人学习下载可作为课程设计参考模板也可在此基础上扩展分类管理、权限控制等模块对理解C#面向对象编程与SQL Server数据库设计均有实际帮助。1. 拿到这套 C# 图书管理系统先别急着双击 exe你大概率会卡在数据库连接上很多人在网上下载了“课程设计-基于C#的图书管理系统源码数据库.zip”解压后第一反应是找到.sln或.exe双击运行结果要么弹窗报错“建立与服务器的连接时出错”要么登录界面永远转圈。这套系统是典型的 VS SQL Server 课程设计项目核心不在界面而在数据访问层也就是项目里那个db.cs文件。你拿到手的第一件事不是编译而是先确认自己的 SQL Server 版本、实例名和认证方式然后把db.cs里的连接字符串改成你自己的环境配置最后再把数据库文件附加进去。这套项目适合两类人一类是做数据库课程设计或 C# 学期大作业的学生另一类是刚接触 ADO.NET 想找一份完整代码做参照的开发者。它能帮你搞明白 WinForms 界面怎么绑定数据、SqlConnection 和 SqlCommand 怎么配合、表之间的主外键关系在 UI 层怎么体现但前提是你愿意先花十分钟把环境搭对。2. 把数据库附加到 SQL Server在对象资源管理器里完成三步操作2.1 找到数据库文件.mdf 和 .ldf 都在项目目录里解压源码包之后你会看到一堆.cache文件、.csproj文件、app.config和db.cs其中真正重要的是那两个数据库文件——BooksDB.mdf和BooksDB_log.ldf。有些版本打包时只给了.mdf日志文件丢失也没关系SQL Server 在附加时会自动重建日志前提是mdf文件本身完整。这里要提醒一句不要把mdf文件直接复制到C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA目录下然后尝试用CREATE DATABASE ... FOR ATTACH附加这个目录的权限很容易出问题尤其是当你用学校机房或公司电脑时SQL Server 服务账户未必有写入权限。最常见的做法是在 SQL Server Management StudioSSMS里右键“数据库”节点选择“附加”然后在弹出的对话框里点击“添加”按钮选中BooksDB.mdf文件。SSMS 会自动识别同目录下的日志文件如果日志文件缺失或损坏它会弹警告但依然允许你附加。附加成功后左侧数据库列表里会出现 BooksDB 节点展开它你能看到表、视图、存储过程等对象确认一下有几张表——一般至少包含图书表、读者表、借阅表和用户表如果少表说明附加的是旧版本或备份不完整。如果你更喜欢用命令行也可以用sqlcmd。打开命令提示符输入以下命令sqlcmd -S localhost -E -Q EXEC sp_attach_db dbname NBooksDB, filename1 ND:\CourseDesign\BooksDB.mdf, filename2 ND:\CourseDesign\BooksDB_log.ldf这条命令用 Windows 身份认证连接本机默认实例然后把指定路径下的两个文件附加为 BooksDB 数据库。-E表示信任连接也就是用当前 Windows 用户身份登录 SQL Serverfilename1和filename2必须写成绝对路径且路径中不能有中文SQL Server 对中文路径的支持有时候会抽风。如果你不想动命令行直接用 SSMS 图形界面就够了——很多搞课程设计的同学第一次接触数据库图形界面更不容易出错。2.2 检查 SQL Server 登录模式Windows 认证还是混合认证图书管理系统的db.cs里写好的连接字符串决定了系统用什么方式登录 SQL Server而你 SQL Server 的认证模式必须能兼容它。最常见的连接字符串有两种写法一种是Server.;DatabaseBooksDB;Integrated SecurityTrue;这种是 Windows 身份认证不需要用户名密码另一种是Server.;DatabaseBooksDB;User Idsa;Password123456;这种用 SQL Server 账号登录。问题来了——如果你的 SQL Server 安装时只启用了 Windows 身份认证模式那么第二条连接字符串无论如何都连不上因为 SQL Server 根本不会接受sa账号的登录请求。查看和修改认证模式的方法是在 SSMS 中右键服务器实例点“属性”切到“安全性”选项卡你会看到“服务器身份验证”下面有两个单选按钮——Windows 身份验证模式和 SQL Server 身份验证模式。很多课程设计的系统默认是用sa账号连接的所以你需要勾选“SQL Server 身份验证模式”然后点确定。改完之后 SSMS 会提示你重启服务才能生效这一步别跳过不重启的话设置不会立即起作用。重启方式是在左侧“对象资源管理器”里右键服务器实例选择“重新启动”或者在 Windows 服务管理器里找到MSSQLSERVER服务右键重启。顺带检查一下sa账号是否被禁用。在 SSMS 安全性节点下展开“登录名”右键sa选“属性”在“状态”页里确认“登录”选项是“已启用”。因为 SQL Server 默认安装时sa账号是禁用的很多教程只说“设置成混合认证模式”却没说还要单独启用sa账号。如果你不想用sa也可以在 SSMS 里新建一个登录名授予它db_owner权限然后把db.cs里的连接字符串改成这个新账号的用户名和密码。这样更安全毕竟sa是数据库超级管理员泄露了影响面太大。2.3 实测连接用 .NET 自带工具验证连接字符串改完认证模式之后不要急着运行整个系统先用一个小工具验证连接字符串能不能通。在 VS 里随便新建一个控制台项目贴入以下代码using System; using System.Data.SqlClient; class Program { static void Main() { // 注意这里的 Server 要替换成你自己的实例名 string connStr Server.;DatabaseBooksDB;User Idsa;Password123456;; using (SqlConnection conn new SqlConnection(connStr)) { try { conn.Open(); Console.WriteLine(连接成功当前数据库 conn.Database); } catch (Exception ex) { Console.WriteLine(连接失败 ex.Message); } } } }这段代码的核心逻辑就三步构造SqlConnection对象、调用Open()方法建立连接、用完释放资源。Server.里的点代表本机默认实例如果你的 SQL Server 是命名实例比如SQLEXPRESS那这里要改成Server.\SQLEXPRESS。DatabaseBooksDB指定初始数据库如果你的库名不叫这个对应改成你自己的库名。User Id和Password是 SQL Server 账号登录方式下的凭证。如果这段代码报“找不到服务器或实例名”你先确认 SQL Server 服务是否在运行在 Windows 服务管理器里看MSSQLSERVER或MSSQL$SQLEXPRESS的状态没启动的话右键启动。如果报“无法连接到因为它是不可见的”大概率是 TCP/IP 协议没启用打开 SQL Server 配置管理器在“SQL Server 网络配置”里把 TCP/IP 协议状态改成已启用然后重启 SQL Server 服务。这个坑在课程设计现场很常见——代码明明没问题但就是连不上最后发现是协议没开。从那以后我每次都要先跑一遍这段测试代码确认连接通了才继续下一步。3. 读懂数据库表结构图书、读者、借阅三张表怎么关联3.1 从 .mdf 里导出建表语句看清主键和外键的关系数据库附加上去之后下一步是看表结构。这套系统的数据模型对课程设计来说比较标准——无非是图书表、读者表、借阅表和用户表。图书表一般命名为Books包含BookID、BookName、Author、Publisher、ISBN、CategoryID、Stock等字段读者表命名为Readers包含ReaderID、ReaderName、Phone、Email等字段借阅表BorrowRecords负责记录谁借了哪本书、借走日期、应还日期、实际还书日期。用户表通常是Users存放系统登录账号和密码。你可以在 SSMS 里右键某张表选择“编写脚本为 → CREATE 到 → 新查询编辑器窗口”这样就能看到完整的建表语句。以 Books 表为例常见的建表语句长这样CREATE TABLE [dbo].[Books] ( [BookID] INT IDENTITY (1, 1) NOT NULL, [BookName] NVARCHAR (50) NOT NULL, [Author] NVARCHAR (50) NULL, [Publisher] NVARCHAR (50) NULL, [ISBN] NVARCHAR (20) NULL, [CategoryID] INT NULL, [Stock] INT DEFAULT ((0)) NOT NULL, PRIMARY KEY CLUSTERED ([BookID] ASC), FOREIGN KEY ([CategoryID]) REFERENCES [dbo].[Category] ([CategoryID]) );这里有几个值得注意的细节。IDENTITY (1, 1)表示BookID是自增列从 1 开始每次加 1插入数据时不需要显式赋值。PRIMARY KEY CLUSTERED是聚集主键SQL Server 默认按这个字段排序存储数据。外键约束指向Category表的CategoryID字段意思是图书表里的CategoryID必须在分类表里存在否则插入会报外键冲突。DEFAULT ((0))给Stock字段设置默认值 0防止插数据时该字段为空。3.2 借阅记录表借书还书业务的核心逻辑借阅表是整个系统最核心的表因为它同时关联了图书表和读者表。借书时插入一条记录还书时更新这条记录的实还日期如果超期还能根据应还日期和实还日期算出罚款金额。典型的借阅表结构如下CREATE TABLE [dbo].[BorrowRecords] ( [RecordID] INT IDENTITY (1, 1) NOT NULL, [BookID] INT NOT NULL, [ReaderID] INT NOT NULL, [BorrowDate] DATETIME DEFAULT (getdate()) NOT NULL, [DueDate] DATETIME NOT NULL, [ReturnDate] DATETIME NULL, PRIMARY KEY CLUSTERED ([RecordID] ASC), FOREIGN KEY ([BookID]) REFERENCES [dbo].[Books] ([BookID]), FOREIGN KEY ([ReaderID]) REFERENCES [dbo].[Readers] ([ReaderID]) );DueDate一般是在BorrowDate基础上加 30 天这个由 C# 代码计算后传入也可以在存储过程里用DATEADD(day, 30, GETDATE())自动生成。ReturnDate初始为 NULL还书时用UPDATE语句把它更新成当前时间。这三张表的关系一看就明白——BorrowRecords是中间表通过BookID和ReaderID把 Books 和 Readers 连起来这就是典型的两表关联查询场景。查询某本书当前被谁借走SQL 语句要这样写SELECT r.ReaderName, b.BookName, br.BorrowDate, br.DueDate FROM BorrowRecords br INNER JOIN Readers r ON br.ReaderID r.ReaderID INNER JOIN Books b ON br.BookID b.BookID WHERE br.ReturnDate IS NULL AND b.BookID 1;这条语句用了两次INNER JOIN先把借阅记录关联到读者拿名字再关联到图书拿书名WHERE br.ReturnDate IS NULL只筛选没还的记录。理解了这个查询逻辑你在 C# 端写SqlCommand时就不会发懵——无非是把 SQL 字符串塞进命令对象然后把执行结果绑定到 DataGridView。3.3 数据库连接字符串存在哪db.cs 和 app.config 的优先级问题这套系统里数据库连接字符串最可能出现在两个地方db.cs和app.config编译后是.exe.config。db.cs里通常定义了一个公共类静态字段保存连接字符串或者提供一个GetConnection()方法返回SqlConnection实例。app.config则是 .NET 标准的配置方式在connectionStrings节里用add元素声明。常见的app.config配置长这样configuration connectionStrings add nameLibraryDB connectionStringServer.;DatabaseBooksDB;User Idsa;Password123456; providerNameSystem.Data.SqlClient / /connectionStrings /configuration然后在db.cs里这样读取public static SqlConnection GetConnection() { string connStr ConfigurationManager.ConnectionStrings[LibraryDB].ConnectionString; return new SqlConnection(connStr); }这里有个优先级问题需要注意代码里如果直接硬编码了连接字符串那app.config里的配置不会生效因为代码根本不读它。你要先搜索整个解决方案里SqlConnection出现的所有位置确认是不是统一通过GetConnection()获取连接。如果有些地方直接new SqlConnection(Server...)那你必须改代码而不是改配置文件。很多同学改了app.config里的密码发现系统还是连不上最后发现有的窗口代码自己硬编码了连接字符串血泪教训。4. 数据访问层实战ADO.NET 五件套在图书管理里的典型用法4.1 SqlConnection SqlCommand SqlDataReader最轻量的查询方式图书管理系统的数据访问层几乎全是 ADO.NET这套技术栈虽然看起来老但它能让你把 SQL 语句的执行过程看得很透彻。最简单的用法是SqlConnection打开连接SqlCommand执行 SQLSqlDataReader逐行读取结果。图书查询界面通常就是这种方式比如在文本框里输入书名关键字然后点击“查询”按钮代码逻辑如下private void btnSearch_Click(object sender, EventArgs e) { string keyword txtKeyword.Text.Trim(); if (keyword ) { MessageBox.Show(请输入查询关键字); return; } string sql SELECT BookID, BookName, Author, Publisher, ISBN, Stock FROM Books WHERE BookName LIKE kw; using (SqlConnection conn GetConnection()) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(kw, % keyword %); conn.Open(); DataTable dt new DataTable(); SqlDataAdapter adapter new SqlDataAdapter(cmd); adapter.Fill(dt); dataGridView1.DataSource dt; } }这段代码有两个地方值得关注。第一是用了kw参数而不是直接拼接字符串这样既防止 SQL 注入也避免书名里有单引号时把 SQL 语句搞崩。第二是填数据用了SqlDataAdapter而不是SqlDataReader因为DataGridView.DataSource需要一个DataTable对象用SqlDataAdapter.Fill()一步到位。有些人用SqlDataReader循环读取再手动加到DataGridView.Rows那是绕远路而且还要自己处理列映射。Parameters.AddWithValue这个方法有个小坑——如果keyword是空字符串LIKE %%会匹配所有记录所以代码里先用if拦了一下。还有一种更稳妥的做法是给AddWithValue传一个SqlDbType.NVarChar参数避免做隐式转换但课程设计项目里用AddWithValue就够用了不用过度设计。4.2 SqlParameter 防注入别把输入直接拼进 SQL 字符串很多早期的课程设计代码喜欢这样写SELECT * FROM Books WHERE BookName txtBookName.Text 这种写法在单机版的图书管理系统里可能运行正常但一旦系统被别人拿到在文本框里输入 OR 11这样的内容SQL 语句就会变成SELECT * FROM Books WHERE BookName OR 11把所有图书记录全部查出来。这是典型的 SQL 注入虽然课程设计答辩时未必有人攻击你的系统但代码评审老师一眼就能看出这是低级问题。写SqlParameter的标准姿势是这样string sql INSERT INTO Books (BookName, Author, Publisher, ISBN, CategoryID, Stock) VALUES (name, author, publisher, isbn, categoryId, stock); using (SqlConnection conn GetConnection()) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.Add(name, SqlDbType.NVarChar, 50).Value txtBookName.Text; cmd.Parameters.Add(author, SqlDbType.NVarChar, 50).Value txtAuthor.Text; cmd.Parameters.Add(publisher, SqlDbType.NVarChar, 50).Value txtPublisher.Text; cmd.Parameters.Add(isbn, SqlDbType.NVarChar, 20).Value txtISBN.Text; cmd.Parameters.Add(categoryId, SqlDbType.Int).Value int.Parse(cmbCategory.SelectedValue.ToString()); cmd.Parameters.Add(stock, SqlDbType.Int).Value int.Parse(txtStock.Text); conn.Open(); int rows cmd.ExecuteNonQuery(); if (rows 0) MessageBox.Show(图书添加成功); else MessageBox.Show(图书添加失败); }这里用cmd.Parameters.Add显式指定了参数类型和长度好处是 SQL Server 能准确预估执行计划而且数据长度超了会直接报错而不是静默截断。ExecuteNonQuery()适用于插入、更新、删除这类不返回结果集的操作返回的是受影响的行数。int.Parse把文本框内容转成整型但如果文本框里输入了非数字字符这里会抛异常严谨的做法是改用int.TryParse但课程设计里用Parse也可以接受毕竟业务场景很单纯。4.3 DataSet 和 DataTable给 DataGridView 绑数据时别用错对象查询结果绑定数据网格DataGridView时有两种常见写法。一种是用SqlDataAdapter.Fill(DataSet)然后设置dataGridView1.DataSource ds.Tables[0]另一种是直接Fill(DataTable)然后dataGridView1.DataSource dt。两种写法最终效果一样但DataSet内部可以装多个DataTable适合多表关联查询时分别存放在内存中供后续操作。对这个系统来说单表查询用DataTable就够了代码短逻辑清晰。private void LoadAllBooks() { string connStr ConfigurationManager.ConnectionStrings[LibraryDB].ConnectionString; string sql SELECT b.BookID, b.BookName, b.Author, b.Publisher, b.ISBN, c.CategoryName, b.Stock FROM Books b LEFT JOIN Category c ON b.CategoryID c.CategoryID; DataTable dt new DataTable(); using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) using (SqlDataAdapter adapter new SqlDataAdapter(cmd)) { adapter.Fill(dt); dataGridView1.DataSource dt; dataGridView1.AutoSizeColumnsMode DataGridViewAutoSizeColumnsMode.Fill; } }LEFT JOIN的作用是即使图书的CategoryID在分类表里找不到对应记录也能查出图书数据CategoryName字段显示为空白而不是整行消失。AutoSizeColumnsMode.Fill让列自动填满网格宽度避免出现大块空白区域。这段代码如果运行时报“DataGridView 中发生以下错误DataGridView 出现未被处理的异常”多半是数据源里某一列的值为 NULL 而DataGridView无法处理常见解决方法是让 SQL 查询里用ISNULL(CategoryName, 未分类)把 NULL 替换成空字符串。5. 避坑手册编译错误、登录失败、数据不显示的三类典型问题5.1 编译报错 CS1061控件命名冲突导致找不到属性现象编译时出现CS1061: DataGridView 未包含 DataSource 的定义之类的错误明明代码看起来完全没写错。原因项目文件夹里有一个叫DataGridView.cs的类文件或者using System.Windows.Forms;缺失导致编译器不认识这个控件类型。这个报错在 .NET Framework 的老项目里很常见——因为这套系统用 Visual Studio 的版本可能比较老项目文件里自动生成的设计器代码和你手写的using指令之间出现了命名冲突。解决先检查代码文件顶部using列表里有没有System.Windows.Forms和System.Data没有就补上然后检查项目里是否新建了名称和系统类重名的文件有就重命名。另一个常见原因是目标框架版本不匹配——DataGridView只在 .NET Framework 2.0 及以上才有如果你把项目改成了 .NET Client Profile部分控件会不可用。在项目属性里把目标框架改成 .NET Framework 4.0 或更高顺手把ConfigurationManager的引用System.Configuration.dll加上这两个问题经常一起出现。5.2 运行时错误 26远程主机强迫关闭了一个现有的连接现象程序在conn.Open()这行抛异常消息类似“在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。未找到或无法访问服务器。请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接”。原因不止一个但最常见两种情况一是 SQL Server 服务没启动二是 TCP/IP 协议被禁用。也有一种情况是连接字符串里的服务器名写成了其他机器的 IP 地址而你明明在单机运行。解决先敲services.msc打开服务管理器找到SQL Server (MSSQLSERVER)或SQL Server (SQLEXPRESS)看状态没启动就右键启动启动类型设为自动防止下次开机又停掉。再打开 SQL Server 配置管理器检查MSSQLSERVER 的协议下的 TCP/IP 是否已启用。如果这两步都做了还是报错用telnet 127.0.0.1 1433测试端口是不是通的不通说明防火墙把 1433 端口拦了在 Windows 防火墙入站规则里放行 1433 端口。有些同学用的是 SQL Server Express 版本它默认实例名是SQLEXPRESS监听端口可能是动态的此时连接字符串里写Server.\SQLEXPRESS而不是Server.。5.3 登录失败 18456sa 被禁用或密码策略问题现象程序运行到登录界面输入账号密码后报错 “用户 sa 登录失败 (Error 18456)”但在 SSMS 里用 Windows 身份认证能正常连接。原因SQL Server 安装时默认禁用了sa账号或者sa的密码为空但 SQL Server 启用了密码策略检查不允许空密码。解决分三步——先用 Windows 身份认证登录 SSMS展开安全性 → 登录名双击sa在“常规”页设置一个强密码至少 8 位包含字母数字和符号在“状态”页把登录改为启用然后右键服务器实例选“属性”在“安全性”页勾选“SQL Server 和 Windows 身份验证模式”最后重启 SQL Server 服务。这里有个细节如果你改了密码策略sa账号设置的密码太简单比如123456SQL Server 依然会拒绝登录虽然它报的错和账号禁用一模一样。所以一定要把密码设置得复杂一点比如Sa123456!。课程设计答辩时老师经常会现场要求打开db.cs看连接字符串如果你的密码明文写123456且sa启用老师会指出安全隐患虽然不会有真实攻击但评分印象分会差一些。从那以后我每次提交课程设计前都会把db.cs里的连接字符串改成从app.config读取源码里不留任何明文账号。5.4 下拉框不显示数据SelectedValue 和 DisplayMember 的匹配问题现象窗体加载后分类下拉框ComboBox点击展开是空的或者显示的是System.Data.DataRowView而不是具体的分类名称。原因ComboBox的DataSource绑定了一个DataTable但没有正确设置DisplayMember和ValueMember。DisplayMember是用户在下拉框里看到的文字ValueMember是选中项对应的实际值也就是插入图书时的CategoryID。解决在窗体Load事件里写如下代码DataTable dt new DataTable(); using (SqlConnection conn GetConnection()) using (SqlDataAdapter adapter new SqlDataAdapter(SELECT CategoryID, CategoryName FROM Category, conn)) { adapter.Fill(dt); } cmbCategory.DataSource dt; cmbCategory.DisplayMember CategoryName; cmbCategory.ValueMember CategoryID;设置完这三行下拉框会正常显示分类名称cmbCategory.SelectedValue返回的是选中分类的CategoryID。有一个隐藏坑如果你的Category表里CategoryID和CategoryName不是这两列名字你需要把DisplayMember和ValueMember改成实际的列名否则下拉框会显示成空。另外如果在ValueMember设置之前就访问SelectedValue会返回 null——因为绑定还没建立完成。你可以把绑定代码放在Load事件里访问代码放在按钮点击事件里顺序上一般不会出问题。5.5 中文乱码表字段用 varchar 而代码写入的是 Unicode现象图书管理系统里输入中文书名数据库中显示正常但在程序界面上显示成??或者乱码。原因数据库表字段定义成了varchar而不是nvarchar。varchar按单字节存储只能存 ASCII 字符和扩展字符集中文用varchar存会丢失信息nvarchar按双字节存储能完整保存中文。很多课程设计的建表脚本里写的是varchar因为默认模板结果运行起来中文显示就翻车了。解决在 SSMS 里执行以下修改语句ALTER TABLE [dbo].[Books] ALTER COLUMN [BookName] NVARCHAR (50) NOT NULL; ALTER TABLE [dbo].[Books] ALTER COLUMN [Author] NVARCHAR (50) NULL; ALTER TABLE [dbo].[Readers] ALTER COLUMN [ReaderName] NVARCHAR (50) NOT NULL;把字段类型从varchar改成nvarchar之后重新运行系统测试一下中文显示。这只是修改表结构不影响已有数据但如果已有数据已经是乱码你需要先删除乱码记录再重新插入。从那以后我做数据库设计凡是可能存中文的字段一律用nvarchar不管对方需求文档里怎么写的这是被课程设计现场答辩时中文乱码逼出来的教训。6. 把课程设计升级成能答辩的作品加登录验证、分页查询和一点代码规范6.1 给用户表加一条管理员记录登录验证从 SQL 查询开始很多课程设计系统在登录时只判断用户名密码是否匹配不做角色区分。如果你想让系统看起来更完整可以在Users表里加一个Role字段然后在登录按钮事件里把角色一起查出来。这样登录后主窗体可以根据角色决定显示哪些按钮——管理员能看到“用户管理”普通读者只有借书还书功能。修改后的登录查询语句如下string sql SELECT UserID, UserName, Role FROM Users WHERE UserName name AND Password pwd; using (SqlConnection conn GetConnection()) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(name, txtUserName.Text.Trim()); cmd.Parameters.AddWithValue(pwd, txtPassword.Text); conn.Open(); using (SqlDataReader reader cmd.ExecuteReader()) { if (reader.Read()) { string role reader[Role].ToString(); if (role Admin) { // 打开管理员主窗体 AdminMainForm mainForm new AdminMainForm(); mainForm.UserName txtUserName.Text.Trim(); mainForm.Show(); this.Hide(); } else { ReaderMainForm readerForm new ReaderMainForm(); readerForm.UserName txtUserName.Text.Trim(); readerForm.Show(); this.Hide(); } } else { MessageBox.Show(用户名或密码错误); } } }SqlDataReader.Read()每次调用只读取一行数据如果查询结果有记录返回 true没有则 false。reader[Role]是索引器语法返回 object 类型用ToString()转成字符串。这样做的好处是登录校验和角色分发在同一个using块里完成连接关闭后不会遗留未释放的资源。如果你不想用SqlDataReader也可以用SqlCommand.ExecuteScalar()返回第一行第一列的值但那样只能拿一个字段拿不到角色信息不够灵活。6.2 DataGridView 分页给自己的系统加个像样的列表页课程设计的图书列表如果一次查出几千条记录加载会卡顿答辩时老师会问你“数据量大怎么办”。这时候哪怕你不做真分页至少要用 SQL 的OFFSET FETCH或ROW_NUMBER()做一个假分页。SQL Server 2012 以上版本可以用OFFSET FETCH写法干净利落private void LoadPage(int pageIndex, int pageSize) { string sql SELECT BookID, BookName, Author, Publisher, ISBN, Stock FROM Books ORDER BY BookID OFFSET offset ROWS FETCH NEXT pageSize ROWS ONLY; DataTable dt new DataTable(); using (SqlConnection conn GetConnection()) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(offset, (pageIndex - 1) * pageSize); cmd.Parameters.AddWithValue(pageSize, pageSize); conn.Open(); SqlDataAdapter adapter new SqlDataAdapter(cmd); adapter.Fill(dt); dataGridView1.DataSource dt; } }OFFSET跳过前面若干行FETCH NEXT取后面的若干行两者配合完成分页。(pageIndex - 1) * pageSize是跳过多少行的公式第 1 页跳过 0 行第 2 页跳过 10 行。这里的参数类型要注意SQL Server 的OFFSET和FETCH后面的值必须是整数所以offset和pageSize用Add方法添加时指定SqlDbType.Int不要用AddWithValue因为AddWithValue有时候会把整数推断成 bigint 导致隐式转换问题。分页按钮的Click事件里调用LoadPage(currentPage, 10)即可每次翻页刷新网格数据。6.3 代码规范把连接字符串和 SQL 语句从窗体里挪走课程设计代码最容易被老师挑毛病的点是窗体代码里到处是SqlConnection、SqlCommand、SELECT *。哪怕功能全部正确老师也会觉得这不像一个规范的系统。解决办法是把数据访问逻辑抽到一个公共类里比如BookDAL窗体只调用它的方法。我给你一个最小重构示范——先建一个BookService类public class BookService { public DataTable GetAllBooks() { string sql SELECT BookID, BookName, Author, Publisher, ISBN, Stock FROM Books; return SqlHelper.ExecuteDataTable(sql); } public bool AddBook(string name, string author, string publisher, string isbn, int categoryId, int stock) { string sql INSERT INTO Books (BookName, Author, Publisher, ISBN, CategoryID, Stock) VALUES (name, author, publisher, isbn, categoryId, stock); SqlParameter[] parameters { new SqlParameter(name, SqlDbType.NVarChar, 50) { Value name }, new SqlParameter(author, SqlDbType.NVarChar, 50) { Value author }, new SqlParameter(publisher, SqlDbType.NVarChar, 50) { Value publisher }, new SqlParameter(isbn, SqlDbType.NVarChar, 20) { Value isbn }, new SqlParameter(categoryId, SqlDbType.Int) { Value categoryId }, new SqlParameter(stock, SqlDbType.Int) { Value stock } }; return SqlHelper.ExecuteNonQuery(sql, parameters) 0; } }窗体按钮事件里只需要一行代码dataGridView1.DataSource new BookService().GetAllBooks();。这样窗体代码不直接碰 SQL所有数据访问逻辑集中在服务类里。如果你时间充裕再抽一层SqlHelper静态类封装连接打开、命令执行、参数传递那就是标准的三层架构雏形。答辩时老师问“你这个项目分层了吗”你可以直接回答“界面层只负责展示业务逻辑在 Service数据访问封装在 SqlHelper”这个回答比空泛地说“我用了分层”有说服力得多。6.4 验收前最后一遍检查用 SQL Server Profiler 确认系统执行的语句我把系统要提交之前会花五分钟用 SQL Server Profiler 抓一遍系统实际执行的 SQL 语句。打开 SSMS 里的“工具 → SQL Server Profiler”新建跟踪勾选SQL:BatchCompleted和RPC:Completed两类事件然后回系统里操作几个功能——登录、查书、借书、还书、添加图书记录。回到 Profiler 看捕获的语句主要确认三件事有没有SELECT *查询、有没有字符串拼接进 SQL 语句、有没有连接泄漏每个操作的SqlConnection是否及时关闭。这个习惯帮我发现过很多隐蔽问题。比如有一次我的图书管理系统在查询时把整个 DataTable 拿回来然后在 C# 端循环过滤Profiler 里一看SQL 语句是SELECT * FROM Books根本没用WHERE过滤数据量大了系统肯定慢。后来改成把查询条件拼进 SQL 语句请求量降了一个数量级。从那以后我每次提交课程设计之前都强制自己走一遍 Profiler 检查确认没有SELECT *、没有裸奔的 SQL 拼接才敢交。希望帮到你。本文还有配套的精品资源点击获取
返回列表