ARTICLE DETAIL

资讯详情

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

C#三层架构学生管理系统实战:SQL Server连接与CRUD全链路

C#三层架构学生管理系统实战:SQL Server连接与CRUD全链路 简介本资源是一套基于C# Windows Forms与SQL Server实现的三层架构学生信息管理系统面向.NET初学者及课程设计实践者聚焦数据库应用开发核心能力训练。系统完整覆盖用户登录注册、学生信息增删查改等典型业务功能支持学号/姓名精确与模糊查询代码结构清晰体现UI层、业务逻辑层BLL与数据访问层DAL职责分离便于理解分层设计思想与实际落地。压缩包共127个文件含38个C#源码文件.cs、4个项目文件.csproj、1个解决方案.sln、3个可执行程序.exe及配套配置文件.config、资源文件.resx和调试符号.pdb总大小2.26MB轻量易部署。目前已有367人学习下载附带操作演示视频链接代码注释充分、模块划分明确适合用于教学参考、课程设计复现或三层架构入门实战。1. 这不是又一个“Hello World”窗体程序它是一套能直接跑通、带真实 SQL Server 数据库连接、三层架构分层清晰、增删查改全链路可调试的 C# Windows 桌面学生管理系统——适合刚学完 ADO.NET 想落地练手、或需要快速交付教学/实训项目的开发者不是 Demo是能改、能扩、能上线局域网内的最小可行系统。你可能已经下载过几十个叫“学生管理系统”的 C# 项目双击运行报错“无法连接到服务器”打开 App.config 发现连接字符串里写着(local)或.\SQLEXPRESS而你的机器根本没装 SQL Server Express或者 BLL 层和 DAL 层代码混在同一个文件夹里所谓“三层”只是文件夹名字又或者登录界面点注册就崩调试发现密码没加盐、没校验长度、SQL 拼接明文插入……这套 StudentMG 不是那样。它包含四个独立 .csproj 工程Models / DAL / BLL / UI每个工程引用关系明确DAL 层只认 SqlConnection 和 SqlCommandBLL 层只调用 DAL 接口不碰数据库UI 层只跟 BLL 打交道数据库脚本已内置建表初始管理员账号admin/123456所有 CRUD 操作都经过 try-catch 事务封装关键路径有日志占位可快速接入 NLog视频演示里能看到登录后主界面加载学生列表、模糊搜索响应毫秒级、修改学号后二次查询立刻生效——这不是截图拼接是真实跑起来的流程。如果你正卡在“学了语法但写不出完整业务系统”或者要给实训课交一份“学生能自己编译、自己改数据库、自己加字段”的作业底板它就是那个少走三天弯路的起点。2. 从零还原四步搭建可运行环境重点不是“装软件”而是让每一层都真正解耦、可单独测试2.1 环境准备SQL Server 版本、端口、身份验证模式必须匹配项目默认配置这套系统默认适配SQL Server 2016 及以上版本含 Express且要求启用SQL Server 身份验证模式混合模式。很多新手翻车第一关就在这里装了最新版 SQL Server Management StudioSSMS但本地没装 SQL Server 数据库引擎或者装了却用 Windows 身份验证登录导致 App.config 里的User IDsa;Password123456根本连不上。提示不要跳过这一步右键“此电脑”→“管理”→“服务和应用程序”→“服务”确认SQL Server (MSSQLSERVER)或SQL Server (SQLEXPRESS)状态为“正在运行”。若未安装请从微软官网下载 SQL Server 2019 Express免费安装时务必勾选“SQL Server 和 Windows 身份验证模式”并记住 sa 密码项目默认设为123456。安装完成后用 SSMS 以 sa 账号登录新建数据库StudentDB大小写敏感不能写成studentdb然后执行项目根目录下的CreateDatabase.sql脚本如果没找到从StudentMG.DAL/Scripts/目录提取。该脚本会创建Users管理员表和Students学生主表两张表并插入一条管理员记录-- CreateDatabase.sql 关键片段实际文件含完整建表约束索引 CREATE DATABASE StudentDB; GO USE StudentDB; GO CREATE TABLE Users ( Id INT PRIMARY KEY IDENTITY(1,1), Username NVARCHAR(50) NOT NULL UNIQUE, PasswordHash NVARCHAR(128) NOT NULL, -- 注意密码是 SHA2_256 加密存储非明文 CreatedTime DATETIME2 DEFAULT GETDATE() ); INSERT INTO Users (Username, PasswordHash) VALUES (admin, 2d7e5a9b8c1f2e4d6a0b3c5e7f9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8); -- 123456 的 SHA2_256 值2.2 工程结构解析为什么 Models/DAL/BLL/UI 必须分开——看懂引用链才能改得安心项目包含 4 个独立 Visual Studio 工程它们的关系不是“文件夹分类”而是编译时的强依赖工程名作用关键引用典型文件StudentMG.Models定义实体类POCO无任何数据库或 UI 依赖无外部引用Student.cs,User.csStudentMG.DAL数据访问层只负责与 SQL Server 通信引用ModelsStudentDAO.cs,UserDAO.cs,DbHelper.csStudentMG.BLL业务逻辑层处理规则如用户名不能重复、学号必须为8位数字引用ModelsDALStudentService.cs,UserService.csStudentMGUI 层WinForms 界面只调用 BLL 方法不出现 SqlConnection引用ModelsBLLLoginForm.cs,MainForm.cs,StudentAddForm.cs这种分层意味着你想把 SQL Server 换成 SQLite只需重写DAL工程里的 DAO 类BLL 和 UI 层代码一行不用动你要加“按班级统计人数”功能在BLL/StudentService.cs里新增GetStudentCountByClass(string className)方法UI 层调用即可学生表要加“入学日期”字段先在Models/Student.cs添加public DateTime EnrollmentDate { get; set; }再在DAL/StudentDAO.cs的Insert()和Update()方法里补上参数和 SQL 占位符最后在MainForm.cs的 DataGridView 列绑定中加一列——全程不碰其他模块。2.3 连接字符串配置App.config 不是摆设改错一个字符就全链路失败所有数据库连接信息集中在 UI 层的App.config文件中路径StudentMG/App.config。不要直接改connectionStrings节点里的值先确认你的 SQL Server 实例名如果是默认实例MSSQLSERVER连接字符串为Data Source.;Initial CatalogStudentDB;User IDsa;Password123456;如果是命名实例如 SQLEXPRESS则改为Data SourceYOUR-PC\\SQLEXPRESS;Initial CatalogStudentDB;User IDsa;Password123456;注意Data Source后面的.表示本地默认实例YOUR-PC\\SQLEXPRESS中的\\是转义写法VS 里必须写成双反斜杠。如果 SQL Server 启用了 TCP/IP 且端口不是默认 1433需显式指定例如Data Source127.0.0.1,1434;...DbHelper.cs位于DAL工程是整个数据访问的基石它封装了连接获取、命令执行、异常统一处理// StudentMG.DAL/DbHelper.cs public static class DbHelper { private static readonly string _connStr ConfigurationManager.ConnectionStrings[StudentDB].ConnectionString; public static SqlConnection GetConnection() { var conn new SqlConnection(_connStr); try { conn.Open(); // 关键这里会真实触发连接错误在此抛出 } catch (SqlException ex) { throw new Exception($数据库连接失败{ex.Message}请检查 SQL Server 是否运行、连接字符串是否正确, ex); } return conn; } }这段代码的价值在于所有 DAO 方法都复用这个GetConnection()一旦连接失败错误堆栈会精准指向DbHelper.cs第 12 行而不是散落在十几个new SqlConnection(...)里。这是三层架构对错误隔离的实际收益。2.4 编译与首次运行不是点“启动”而是验证每一层的输出是否被正确引用在 Visual Studio 中右键解决方案 → “设为启动项目” → 选择StudentMGUI 层。但别急着按 F5——先做三件事检查引用完整性展开StudentMG工程的“引用”确认StudentMG.BLL、StudentMG.Models已存在同理BLL工程下应有DAL和Models引用。如果显示黄色感叹号右键 → “重新生成”对应工程确认输出路径一致右键每个工程 → “属性” → “生成” → “输出路径”确保都是bin\Debug\默认值否则 DLL 无法被自动复制到 UI 层的输出目录清理并重建菜单栏 → “生成” → “清理解决方案”再“重新生成解决方案”。这一步强制 VS 重新解析所有引用关系避免缓存导致的“找不到类型”错误。成功重建后F5 启动。首次运行会弹出登录窗口输入admin/123456即可进入主界面。此时观察任务栏StudentMG.exe进程正常运行无崩溃弹窗——说明四层引用链、数据库连接、基础 CRUD 都已贯通。3. 增删查改全链路实操从 UI 点击到 SQL 执行每一步都可断点追踪3.1 登录流程BLL 层如何校验密码SHA2_256 加密不是摆设登录按钮点击事件在LoginForm.cs中// StudentMG/LoginForm.cs private void btnLogin_Click(object sender, EventArgs e) { string username txtUsername.Text.Trim(); string password txtPassword.Text; if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { MessageBox.Show(用户名和密码不能为空); return; } // 关键调用 BLL 层 UserService.Login不碰数据库细节 var user _userService.Login(username, password); if (user ! null) { MessageBox.Show(登录成功); this.Hide(); new MainForm().ShowDialog(); // 主界面 } else { MessageBox.Show(用户名或密码错误); } }_userService是UserService的实例其Login方法在BLL/UserService.cs中// StudentMG.BLL/UserService.cs public User Login(string username, string password) { // 1. 密码加密使用 SHA2_256与数据库存储格式严格一致 string passwordHash ComputeSha256Hash(password); // 2. 调用 DAL 层查询返回 User 实体非 DataTable 或 DataSet return _userDao.GetByUsernameAndPassword(username, passwordHash); } private string ComputeSha256Hash(string input) { using (var sha256 SHA256.Create()) { var bytes Encoding.UTF8.GetBytes(input); var hash sha256.ComputeHash(bytes); return BitConverter.ToString(hash).Replace(-, ).ToLower(); } }_userDao.GetByUsernameAndPassword最终执行 SQL-- StudentMG.DAL/UserDAO.cs 中的 SQL 片段 SELECT Id, Username, PasswordHash, CreatedTime FROM Users WHERE Username username AND PasswordHash passwordHash参数说明username和passwordHash通过SqlCommand.Parameters.AddWithValue()传入彻底杜绝 SQL 注入。密码哈希值在注册时UserService.Register已计算并存入数据库登录时仅比对哈希值原始密码永不落地。3.2 添加学生UI 层如何把表单数据安全传递到数据库添加学生入口在MainForm.cs的btnAdd_Click事件中。它不直接构造 SQL而是组装Student实体// StudentMG/MainForm.cs private void btnAdd_Click(object sender, EventArgs e) { var addForm new StudentAddForm(); if (addForm.ShowDialog() DialogResult.OK) // 模态窗体OK 才继续 { // 从窗体获取数据封装为 Student 实体 var student new Student { StudentId addForm.StudentId, Name addForm.Name, Gender addForm.Gender, Class addForm.Class, Phone addForm.Phone, Email addForm.Email }; // 调用 BLL 层服务传入实体 bool success _studentService.AddStudent(student); if (success) { MessageBox.Show(添加成功); LoadStudentList(); // 刷新 DataGridView } else { MessageBox.Show(添加失败请检查学号是否重复或网络是否正常); } } }_studentService.AddStudent在BLL/StudentService.cs中// StudentMG.BLL/StudentService.cs public bool AddStudent(Student student) { // 业务规则校验学号必须为8位数字 if (!Regex.IsMatch(student.StudentId, ^\d{8}$)) { throw new ArgumentException(学号必须为8位纯数字); } // 调用 DAL 层插入 return _studentDao.Insert(student) 0; // 返回影响行数 }_studentDao.Insert执行参数化 SQL-- StudentMG.DAL/StudentDAO.cs INSERT INTO Students (StudentId, Name, Gender, Class, Phone, Email, CreatedTime) VALUES (studentId, name, gender, class, phone, email, GETDATE())关键细节CreatedTime字段由GETDATE()数据库函数自动生成UI 层无需传值gender参数类型为int男1女0Student.cs中定义为public int Gender { get; set; }WinForms 下拉框绑定时做了DataSource映射避免字符串比较错误。3.3 模糊查询LIKE 语句怎么写才安全参数化不是可选项主界面的搜索框支持两种模式精确查询学号/姓名全匹配txtSearch.Text直接作为参数模糊查询姓名包含关键词在 SQL 中用LIKE但必须用%包裹参数且参数本身不拼接%StudentDAO.cs中的查询方法// StudentMG.DAL/StudentDAO.cs public ListStudent SearchStudents(string keyword, bool isFuzzy) { var students new ListStudent(); string sql isFuzzy ? SELECT * FROM Students WHERE Name LIKE keyword ORDER BY CreatedTime DESC : SELECT * FROM Students WHERE StudentId keyword OR Name keyword ORDER BY CreatedTime DESC; using (var conn DbHelper.GetConnection()) using (var cmd new SqlCommand(sql, conn)) { // 安全写法参数值由代码控制不拼接 SQL 字符串 cmd.Parameters.AddWithValue(keyword, isFuzzy ? $%{keyword}% : keyword); using (var reader cmd.ExecuteReader()) { while (reader.Read()) { students.Add(new Student { Id (int)reader[Id], StudentId reader[StudentId].ToString(), Name reader[Name].ToString(), Gender Convert.ToInt32(reader[Gender]), Class reader[Class].ToString(), Phone reader[Phone].ToString(), Email reader[Email].ToString(), CreatedTime (DateTime)reader[CreatedTime] }); } } } return students; }为什么cmd.Parameters.AddWithValue(keyword, $%{keyword}%)是安全的因为AddWithValue会将整个字符串作为参数值传给 SQL Server服务器内部处理LIKE匹配不会解析%为 SQL 语法。如果写成sql WHERE Name LIKE % keyword %用户输入 OR 11就会变成WHERE Name LIKE % OR 11%导致全表扫描甚至注入。3.4 删除与修改事务如何保证数据一致性删除学生时系统默认不物理删除而是软删除IsDeleted BIT DEFAULT 0但本项目采用硬删除DELETE FROM Students WHERE Id id。为防误操作BLL/StudentService.cs中增加了二次确认和事务包裹// StudentMG.BLL/StudentService.cs public bool DeleteStudent(int id) { if (MessageBox.Show(确定要删除该学生此操作不可撤销, 确认删除, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) ! DialogResult.Yes) return false; try { // 使用事务确保删除操作原子性 using (var scope new TransactionScope()) { bool deleted _studentDao.DeleteById(id); if (deleted) { scope.Complete(); // 提交事务 return true; } return false; } } catch (Exception ex) { // 记录日志此处可接入 NLog throw new Exception($删除学生失败{ex.Message}, ex); } }修改学生信息同理在StudentService.UpdateStudent中先SELECT读取原记录做比对防并发覆盖再UPDATE提交全程在事务内完成。4. 避坑指南那些让你调试到凌晨三点、却只因一个配置项的血泪经验4.1 现象启动时报错 “System.Data.SqlClient.SqlException: 用户 sa 登录失败”原因SQL Server 未启用 SQL Server 身份验证模式或 sa 账户被禁用。Windows 默认安装只开启 Windows 身份验证。解决SSMS 中右键服务器 → “属性” → “安全性” → 将“服务器身份验证”改为“SQL Server 和 Windows 身份验证模式”展开“安全性” → “登录名” → 右键sa→ “属性” → 勾选“启用”并设置密码重启 SQL Server 服务服务管理器中右键 → “重新启动”。4.2 现象DataGridView 显示空白但数据库里有数据原因MainForm.cs中LoadStudentList()方法调用时机错误或AutoGenerateColumns属性被设为false但未手动绑定列。解决检查LoadStudentList()是否在MainForm_Load事件中调用而非构造函数确认dataGridView1.AutoGenerateColumns true;默认值若设为false需在设计器中手动添加DataGridViewTextBoxColumn并设置DataPropertyName与Student类属性名完全一致如StudentId对应DataPropertyNameStudentId。4.3 现象添加学生后界面上看不到新记录刷新才出现原因LoadStudentList()方法中未清空BindingSource或DataTable导致新数据追加到旧数据末尾但 DataGridView 未重新绑定。解决// 正确写法先 Clear 再 Fill private void LoadStudentList() { var students _studentService.GetAllStudents(); bindingSource1.Clear(); // 关键清空旧数据 foreach (var s in students) { bindingSource1.Add(s); } dataGridView1.DataSource bindingSource1; // 重新绑定 }4.4 现象修改学生姓名后再次搜索仍显示旧名字原因StudentDAO.Update()方法中 SQL 语句的WHERE条件写错例如WHERE Name oldName应为WHERE Id id导致更新了错误的行。解决打开StudentDAO.cs定位Update()方法确认 SQL 为UPDATE Students SET Namename,... WHERE Idid在cmd.Parameters.AddWithValue(id, student.Id)前加断点运行时检查student.Id是否为预期值。4.5 现象注册新管理员时密码明文存入数据库原因UserService.Register()方法中调用了ComputeSha256Hash()但UserDAO.Insert()方法里却把原始密码字符串传给了PasswordHash字段。解决检查UserDAO.Insert()的 SQL 参数必须是passwordHash而非password确认UserService.Register()中user.PasswordHash ComputeSha256Hash(password);且user.Password字段如果存在未被插入。5. 进阶技巧让这套系统真正成为你的“可扩展基座”而不是一次性 Demo5.1 快速接入日志用 NLog 替代 Console.WriteLine生产环境必备当前项目日志仅靠Console.WriteLine输出无法留存。接入 NLog 只需三步安装 NuGet 包在StudentMGUI 层和StudentMG.BLL工程中分别执行Install-Package NLog添加配置文件在StudentMG工程根目录新建NLog.configXML 格式内容如下?xml version1.0 encodingutf-8 ? nlog xmlnshttp://www.nlog-project.org/schemas/NLog.xsd xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance targets target xsi:typeFile namelogfile fileName${basedir}/logs/${shortdate}.log layout${longdate} ${level:uppercasetrue} ${logger} ${message} ${exception:formattostring} / /targets rules logger name* minlevelInfo writeTologfile / /rules /nlog在 BLL 层注入日志器修改StudentService.cs构造函数private readonly ILogger _logger LogManager.GetCurrentClassLogger(); public StudentService(IStudentDAO studentDao) { _studentDao studentDao; _logger.Info(StudentService 初始化完成); } public bool AddStudent(Student student) { try { _logger.Debug($开始添加学生{student.Name}({student.StudentId})); var result _studentDao.Insert(student) 0; _logger.Info($添加学生结果{result}学号{student.StudentId}); return result; } catch (Exception ex) { _logger.Error(ex, 添加学生时发生异常); throw; } }效果运行后会在StudentMG/bin/Debug/logs/下生成按日期命名的日志文件记录每次 CRUD 的时间、参数、结果及异常堆栈。这是排查线上问题的第一手证据。5.2 数据库迁移从 SQL Server 迁移到 SQLite只需重写 DAL 层假设你需要部署到无 SQL Server 的客户机上SQLite 是最佳选择。步骤如下在StudentMG.DAL工程中安装System.Data.SQLiteNuGet 包新建StudentDAO_SQLite.cs实现IStudentDAO接口与StudentDAO.cs同名接口修改StudentService构造函数根据配置动态注入不同 DAO 实例// StudentMG.BLL/StudentService.cs public StudentService(IStudentDAO studentDao) { _studentDao studentDao ?? throw new ArgumentNullException(nameof(studentDao)); }在Program.csUI 层入口中// StudentMG/Program.cs static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 根据配置决定使用哪个 DAL string dbType ConfigurationManager.AppSettings[DatabaseType] ?? SqlServer; IStudentDAO studentDao dbType switch { SQLite new StudentDAO_SQLite(), _ new StudentDAO() // 默认 SQL Server }; Application.Run(new LoginForm(new UserService(), new StudentService(studentDao))); }关键点IStudentDAO接口定义了所有数据操作契约Insert,Update,DeleteById,GetAll等DAL 层具体实现可自由替换BLL 和 UI 层代码零修改。这就是抽象接口的价值。5.3 UI 层解耦用 MVVM 模式替代 WinForms 事件驱动可选升级当前 UI 层大量使用button1_Click事件和textBox1.Text直接读取难以单元测试。升级为 MVVM 模式只需在StudentMG工程中安装CommunityToolkit.Mvvm创建MainViewModel.cs继承ObservableObject暴露StudentsObservableCollectionStudent、SelectedStudent、AddCommand等属性在MainForm.xaml需将 WinForms 改为 WPF 项目中DataContext绑定到 ViewModelItemsSource{Binding Students}命令执行时AddCommand调用StudentService.AddStudent成功后Students.Add(newStudent)自动刷新界面。这不是必须项但当你需要写自动化测试如用 NUnit 测试MainViewModel.AddCommand.Execute(null)是否真调用了StudentServiceMVVM 是唯一可靠路径。我一般会在交付前给客户加这一层因为后续加报表、导出 Excel 功能时ViewModel 复用率极高。从那以后我每次接手新项目只要涉及 Windows 桌面数据库都会先搭三层骨架Models 定义实体、DAL 封装连接、BLL 写业务规则、UI 只管展示。哪怕客户临时说“明天要加个导出功能”我也能 20 分钟在 BLL 里写好ExportToExcel()UI 层加个按钮绑定命令——因为分层不是为了炫技是让每一次需求变更都只在一个小盒子里发生。希望帮到你。本文还有配套的精品资源点击获取
返回列表