简介:本资源是一份面向高校计算机专业学生与初学者的《学生宿舍管理系统》课程设计文档,基于C#与SQL Server 2012实现数据库驱动的桌面应用开发实践,聚焦宿舍管理、班级信息、学生入住、贵重物品及外来人员登记等核心业务场景。文档完整覆盖需求分析、开发环境配置(Visual Studio 2012 + .NET Framework + ADO.NET)、概念与逻辑结构设计(含实体关系建模与表结构定义)、六大功能模块实现细节及程序调试方法,附有用户手册与目录式技术说明,适合作为数据库应用系统综合设计作业参考或C#课程实训范例。资源为单文件docx格式,大小945KB,内容详实、结构规范,已供245人学习下载,可直接用于理解B/S或C/S架构下数据层交互逻辑、掌握ADO.NET数据库操作全流程及系统化文档撰写规范。
1. 学生宿舍管理系统(C#连接到数据库):不是写个WinForm界面就叫“系统”,它得真能管人、管房、管状态、管权限
你见过那种“学生宿舍管理系统”——点开是三个按钮:【添加学生】、【查询宿舍】、【导出Excel】,双击运行后弹出一个空白窗体,点按钮就报错“无法连接到服务器”,连数据库名都硬编码在SqlConnection字符串里?这不是系统,这是教学演示PPT的配套幻灯片。真正的学生宿舍管理系统(C#连接到数据库),核心不在窗体有多漂亮,而在于它能否稳定承载多角色并发操作(宿管员批量调宿、辅导员实时查寝、学生自助报修)、支撑状态强一致性校验(同一间4人间不能录入第5人、退宿未办结前禁止新入住)、并经受住真实业务流压力(开学周每日300+条入住变更、期末集中退宿时20人同时提交申请)。它本质是一个以C#为前端胶水、SQL Server(或兼容数据库)为数据中枢、围绕“人-房-床-状态-流程”五元关系建模的轻量级事务型应用。适合高校信息化部门快速落地、二级学院自主维护、或计算机专业课程设计中要求“可部署、可验证、可扩展”的实战项目。如果你正卡在“连上数据库但改不了数据”“多人操作时记录错乱”“一加学生宿舍就显示已满却查不到谁占着”,那这篇笔记就是为你写的——我们不讲ADO.NET基础语法,只拆解从VS新建项目到上线运行这之间,那些没人告诉你、但每一步都决定成败的实操断点。
2. 用C# + SQL Server搭建可运行的最小闭环:从创建数据库到WinForm窗体联动
2.1 设计符合宿舍管理逻辑的数据库结构:别再用单表存一切
很多初学者一上来就建一个Students表,字段塞满“姓名、学号、学院、专业、宿舍楼、楼层、房间号、床位号、入住日期、状态”…… 这看着省事,实则埋下所有后续翻车的种子:无法约束“同一房间床位数固定为4”,无法追溯“张三6月1日住301-1,9月1日调至402-3”的历史轨迹,更无法实现“某宿舍楼整层维修期间禁止分配”。正确做法是拆解为四张主表 + 一张关联表,用外键和约束兜住业务规则:
-- 1. 宿舍楼表:定义物理空间单元 CREATE TABLE DormBuildings ( BuildingID INT PRIMARY KEY IDENTITY(1,1), BuildingName NVARCHAR(20) NOT NULL UNIQUE, -- 如"东苑A栋" TotalFloors TINYINT NOT NULL CHECK (TotalFloors BETWEEN 1 AND 30) ); -- 2. 宿舍房间表:绑定楼栋与房间号,明确床位容量 CREATE TABLE DormRooms ( RoomID INT PRIMARY KEY IDENTITY(1,1), BuildingID INT NOT NULL FOREIGN KEY REFERENCES DormBuildings(BuildingID), RoomNumber NVARCHAR(10) NOT NULL, -- 如"301" BedCapacity TINYINT NOT NULL DEFAULT 4 CHECK (BedCapacity IN (2,4,6)), Status NVARCHAR(10) NOT NULL DEFAULT '空闲' CHECK (Status IN ('空闲','维修中','已满','待清理')), CONSTRAINT UQ_BuildingRoom UNIQUE (BuildingID, RoomNumber) ); -- 3. 学生信息表:仅存核心身份属性,不存宿舍信息 CREATE TABLE Students ( StudentID NVARCHAR(12) PRIMARY KEY, -- 学号作为主键,避免自增ID暴露数量 Name NVARCHAR(20) NOT NULL, Gender NCHAR(1) CHECK (Gender IN ('男','女')), College NVARCHAR(30), Major NVARCHAR(30), EnrollmentDate DATE ); -- 4. 入住记录表:关键!记录“谁在何时入住了哪张床”,支持历史追溯与状态流转 CREATE TABLE DormAssignments ( AssignmentID INT PRIMARY KEY IDENTITY(1,1), StudentID NVARCHAR(12) NOT NULL FOREIGN KEY REFERENCES Students(StudentID), RoomID INT NOT NULL FOREIGN KEY REFERENCES DormRooms(RoomID), BedNumber TINYINT NOT NULL CHECK (BedNumber BETWEEN 1 AND 6), -- 床位号1-6 AssignDate DATE NOT NULL DEFAULT GETDATE(), Status NVARCHAR(10) NOT NULL DEFAULT '入住中' CHECK (Status IN ('入住中','已调宿','已退宿','异常离宿')), -- 关键约束:同一房间同一床位在同一时间只能有一条“入住中”记录 CONSTRAINT UQ_RoomBedActive UNIQUE (RoomID, BedNumber, Status) WHERE Status = '入住中' );提示:
UQ_RoomBedActive是SQL Server 2008+支持的“筛选唯一索引”,它只对Status='入住中'的行生效,完美解决“一张床不能同时被两人占用”的硬性业务规则。这是比在C#代码里查重更可靠、更高效的方式——把校验交给数据库引擎。
2.2 C#项目中安全连接数据库:不用拼接字符串,也不用明文存密码
在Visual Studio中新建一个Windows Forms App (.NET Framework 或 .NET 6+) 项目。绝对不要在代码里写:
string connStr = "server=.;database=MyDorm;uid=sa;pwd=123456;";这等于把大门钥匙贴在门框上。正确路径是:
使用
app.config管理连接字符串(.NET Framework)或appsettings.json(.NET Core/6+)
在项目根目录右键 → “添加” → “新建项” → 选择“应用程序配置文件”(.NET Framework)或“JSON 文件”(.NET Core/6+,命名为appsettings.json)。对于 .NET Framework (
app.config):<?xml version="1.0" encoding="utf-8"?> <configuration> <connectionStrings> <!-- 使用 Integrated Security=true 表示 Windows 身份验证,最安全 --> <add name="DormDB" connectionString="Data Source=.;Initial Catalog=StudentDormDB;Integrated Security=true;" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration>对于 .NET 6+ (
appsettings.json):{ "ConnectionStrings": { "DormDB": "Data Source=.;Initial Catalog=StudentDormDB;Integrated Security=true;" } }注意:
Integrated Security=true表示使用当前Windows登录用户的身份连接SQL Server。你需要确保该用户(如你的电脑账户)在SQL Server中已被授予对StudentDormDB数据库的db_datareader和db_datawriter角色。这是开发阶段最安全、最免配置的方式。在C#代码中读取并使用连接字符串
在需要访问数据库的类(如DormService.cs)中:using System.Configuration; // .NET Framework // 或 using Microsoft.Extensions.Configuration; // .NET 6+, 需安装 Microsoft.Extensions.Configuration.Json 包 public class DormService { private readonly string _connStr; // .NET Framework 构造函数 public DormService() { _connStr = ConfigurationManager.ConnectionStrings["DormDB"].ConnectionString; } // .NET 6+ 构造函数(需注入 IConfiguration) // public DormService(IConfiguration configuration) // { // _connStr = configuration.GetConnectionString("DormDB"); // } public List<Student> GetAllStudents() { var students = new List<Student>(); // 使用 using 确保连接自动释放,即使发生异常 using (var conn = new SqlConnection(_connStr)) { conn.Open(); // 此处若失败,会抛出 SqlException,需捕获 using (var cmd = new SqlCommand("SELECT StudentID, Name, Gender FROM Students", conn)) { using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { students.Add(new Student { StudentID = reader["StudentID"].ToString(), Name = reader["Name"].ToString(), Gender = reader["Gender"].ToString() }); } } } } return students; } }逻辑说明:
using语句块是C#资源管理的黄金法则。它保证SqlConnection、SqlCommand、SqlDataReader在作用域结束时(无论是否发生异常)都会被正确Dispose(),释放底层网络连接和内存。这是避免“连接数耗尽”这类生产环境高频问题的根本。
2.3 WinForm窗体与数据库联动:用DataGridView展示学生列表,并支持双击查看详情
创建一个名为FrmStudentList.cs的窗体。拖入一个DataGridView控件(dgvStudents)和一个Button(btnRefresh)。
public partial class FrmStudentList : Form { private readonly DormService _dormService; public FrmStudentList() { InitializeComponent(); _dormService = new DormService(); // 实例化服务类 LoadStudentData(); // 初始化加载 } private void btnRefresh_Click(object sender, EventArgs e) { LoadStudentData(); } private void LoadStudentData() { try { var students = _dormService.GetAllStudents(); // 直接赋值给DataSource,DataGridView会自动根据Student类的属性生成列 dgvStudents.DataSource = students; // 可选:美化列头 dgvStudents.Columns["StudentID"].HeaderText = "学号"; dgvStudents.Columns["Name"].HeaderText = "姓名"; dgvStudents.Columns["Gender"].HeaderText = "性别"; } catch (SqlException ex) { MessageBox.Show($"数据库错误:{ex.Message}\n请检查SQL Server服务是否启动,数据库是否存在", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception ex) { MessageBox.Show($"未知错误:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // 双击某行,弹出详情窗体 private void dgvStudents_CellDoubleClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex >= 0 && dgvStudents.SelectedRows.Count > 0) { var selectedStudent = dgvStudents.SelectedRows[0].DataBoundItem as Student; if (selectedStudent != null) { // 假设你有一个详情窗体 FrmStudentDetail using (var detailForm = new FrmStudentDetail(selectedStudent.StudentID)) { detailForm.ShowDialog(); // 模态显示,阻塞当前窗体 } } } } }参数说明:
DataBoundItem是DataGridViewRow的一个属性,它返回绑定到该行的数据源对象(即Student实例)。这是实现“点击即查”的最直接方式,无需手动解析Cells内容。ShowDialog()确保用户必须处理完详情窗体才能回到列表页,避免操作混乱。
3. 实现核心业务功能:入住、调宿、退宿的事务化操作与状态校验
3.1 入住操作:如何确保“床位不超员”且“学生不重复入住”
入住不是简单地往DormAssignments表里插一条记录。它必须是一个原子性事务,包含三步:1) 校验目标房间是否有空床位;2) 校验该学生当前是否已入住其他房间;3) 同时插入新记录并更新房间状态。任何一步失败,整个操作回滚。
// 在 DormService.cs 中添加方法 public bool AssignStudentToRoom(string studentId, int roomId, byte bedNumber) { // 使用事务确保一致性 using (var conn = new SqlConnection(_connStr)) { conn.Open(); using (var transaction = conn.BeginTransaction()) { try { using (var cmd = new SqlCommand()) { cmd.Connection = conn; cmd.Transaction = transaction; // 步骤1:检查该学生是否已入住(状态为'入住中') cmd.CommandText = @" SELECT COUNT(*) FROM DormAssignments WHERE StudentID = @studentId AND Status = '入住中'"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@studentId", studentId); int existingCount = (int)cmd.ExecuteScalar(); if (existingCount > 0) { throw new InvalidOperationException($"学生 {studentId} 当前已有有效入住记录,无法重复入住。"); } // 步骤2:检查目标房间该床位是否空闲(利用之前建的筛选唯一索引) cmd.CommandText = @" SELECT COUNT(*) FROM DormAssignments WHERE RoomID = @roomId AND BedNumber = @bedNumber AND Status = '入住中'"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@roomId", roomId); cmd.Parameters.AddWithValue("@bedNumber", bedNumber); int occupiedCount = (int)cmd.ExecuteScalar(); if (occupiedCount > 0) { throw new InvalidOperationException($"房间 {roomId} 的床位 {bedNumber} 当前已被占用。"); } // 步骤3:执行入住(插入新记录) cmd.CommandText = @" INSERT INTO DormAssignments (StudentID, RoomID, BedNumber, Status) VALUES (@studentId, @roomId, @bedNumber, '入住中')"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@studentId", studentId); cmd.Parameters.AddWithValue("@roomId", roomId); cmd.Parameters.AddWithValue("@bedNumber", bedNumber); cmd.ExecuteNonQuery(); // 步骤4(可选):更新房间状态为'已满'(如果已满员) cmd.CommandText = @" UPDATE DormRooms SET Status = '已满' WHERE RoomID = @roomId AND (SELECT COUNT(*) FROM DormAssignments WHERE RoomID = @roomId AND Status = '入住中') = (SELECT BedCapacity FROM DormRooms WHERE RoomID = @roomId)"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@roomId", roomId); cmd.ExecuteNonQuery(); // 所有步骤成功,提交事务 transaction.Commit(); return true; } } catch { // 发生异常,回滚事务 transaction.Rollback(); throw; // 重新抛出,让上层捕获 } } } }逻辑说明:
BeginTransaction()开启一个数据库事务。transaction.Commit()表示所有SQL命令都成功执行,数据永久写入;transaction.Rollback()则撤销所有已执行但未提交的更改。这是保证“要么全成功,要么全失败”的唯一可靠方式。AddWithValue是便捷方法,但生产环境建议用Add指定精确类型(如Add("@bedNumber", SqlDbType.TinyInt).Value = bedNumber),避免隐式转换错误。
3.2 调宿操作:如何安全地“先退后住”,并保留历史记录
调宿的本质是:1) 将学生当前的入住记录Status更新为'已调宿';2) 为其在新房间创建一条新的'入住中'记录。这两步必须在同一个事务中完成,且要确保原记录存在且状态为'入住中'。
public bool TransferStudent(string studentId, int newRoomId, byte newBedNumber) { using (var conn = new SqlConnection(_connStr)) { conn.Open(); using (var transaction = conn.BeginTransaction()) { try { using (var cmd = new SqlCommand()) { cmd.Connection = conn; cmd.Transaction = transaction; // 步骤1:查找学生当前有效的入住记录(Status='入住中') cmd.CommandText = @" SELECT AssignmentID, RoomID FROM DormAssignments WHERE StudentID = @studentId AND Status = '入住中'"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@studentId", studentId); var currentRecord = cmd.ExecuteReader(); if (!currentRecord.Read()) { throw new InvalidOperationException($"学生 {studentId} 当前无有效入住记录,无法调宿。"); } int currentAssignmentId = (int)currentRecord["AssignmentID"]; int currentRoomId = (int)currentRecord["RoomID"]; currentRecord.Close(); // 步骤2:将原记录状态更新为'已调宿' cmd.CommandText = @" UPDATE DormAssignments SET Status = '已调宿' WHERE AssignmentID = @assignmentId"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@assignmentId", currentAssignmentId); cmd.ExecuteNonQuery(); // 步骤3:执行新房间的入住(复用AssignStudentToRoom的核心逻辑,但跳过学生重复检查) // (此处为简化,实际应提取为独立方法) cmd.CommandText = @" INSERT INTO DormAssignments (StudentID, RoomID, BedNumber, Status) VALUES (@studentId, @roomId, @bedNumber, '入住中')"; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@studentId", studentId); cmd.Parameters.AddWithValue("@roomId", newRoomId); cmd.Parameters.AddWithValue("@bedNumber", newBedNumber); cmd.ExecuteNonQuery(); // 步骤4:更新新旧房间状态(略,同入住逻辑) transaction.Commit(); return true; } } catch { transaction.Rollback(); throw; } } } }玄学提醒:
ExecuteReader()后必须调用Close()或使用using包裹,否则连接会被占用,导致后续命令超时。这是新手最容易忽略的“黑匣子”资源泄漏点。
3.3 退宿操作:软删除还是硬删除?为什么推荐“状态标记”
直接DELETE FROM DormAssignments WHERE ...是危险的。它抹去了所有历史,无法回答“张三去年住哪?”、“某房间过去半年的入住率是多少?”。正确做法是状态标记:
public bool CheckOutStudent(string studentId) { using (var conn = new SqlConnection(_connStr)) { conn.Open(); using (var cmd = new SqlCommand()) { cmd.Connection = conn; // 查找并更新其当前入住记录 cmd.CommandText = @" UPDATE DormAssignments SET Status = '已退宿', AssignDate = GETDATE() -- 记录退宿时间 WHERE StudentID = @studentId AND Status = '入住中'"; cmd.Parameters.AddWithValue("@studentId", studentId); int rowsAffected = cmd.ExecuteNonQuery(); return rowsAffected > 0; // 返回是否成功找到并更新了记录 } } }血泪经验:
rowsAffected是ExecuteNonQuery()的返回值,它告诉你有多少行被修改。如果为0,说明该学生没有'入住中'的记录,操作无效。这是判断业务逻辑是否成功的最直接依据,比捕获异常更优雅。
4. 避坑指南:C#连接数据库时最常见的5个翻车现场与后悔药
4.1 现象:程序启动就报错“在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误”
原因:SQL Server服务根本没启动,或者连接字符串中的Data Source(服务器名)写错了。常见错误包括:Data Source=.(本机)但SQL Server Express未安装;Data Source=localhost但SQL Server配置管理器中TCP/IP协议被禁用;Data Source=MyPC\SQLEXPRESS但实例名实际是MSSQLSERVER(默认实例)。
解决:
- 打开“SQL Server 配置管理器” → “SQL Server 服务”,确认目标实例(如
SQL Server (MSSQLSERVER))状态为“正在运行”。 - 在“SQL Server 网络配置” → “MSSQLSERVER 的协议”中,确保“TCP/IP”已启用。双击它,在“IP地址”选项卡中,找到
IPAll,清空TCP端口和TCP动态端口(留空),重启服务。 - 在连接字符串中,用
Data Source=127.0.0.1代替Data Source=.或localhost,绕过DNS解析问题。
4.2 现象:能连上数据库,但执行INSERT后查不到新数据,重启程序又出现了
原因:忘记调用conn.Open(),或者using语句块提前结束了连接生命周期,导致INSERT命令在关闭的连接上执行(此时ExecuteNonQuery()会静默失败,不抛异常)。
解决:
- 永远在
using (var conn = new SqlConnection(...))内部,显式调用conn.Open()。 - 在
ExecuteNonQuery()后,加一行Console.WriteLine($"影响行数: {rowsAffected}");(调试时)或记录日志,确认返回值非零。
4.3 现象:多人同时操作时,两个管理员给同一张床分配了不同学生,数据库没报错,但数据错了
原因:缺乏并发控制。两个线程几乎同时执行“查床位是否空闲”(都返回0),然后都执行INSERT,违反了业务规则。
解决:
- 根本方案:依靠数据库层面的约束,即前面提到的
UQ_RoomBedActive筛选唯一索引。当第二个INSERT尝试插入相同(RoomID, BedNumber, '入住中')时,SQL Server会立即抛出违反唯一约束的异常(SqlException.Number == 2601或2627),C#捕获后友好提示用户。 - 辅助方案:在C#代码中,
INSERT后立即SELECT刚插入的记录进行二次校验(不推荐,增加IO,且不如数据库约束可靠)。
4.4 现象:DataGridView显示中文是乱码(如“????”),但SQL Server Management Studio里看是好的
原因:数据库的排序规则(Collation)不是中文兼容的,或者C#连接字符串缺少字符集声明。
解决:
- 创建数据库时,显式指定中文排序规则:
CREATE DATABASE StudentDormDB COLLATE Chinese_PRC_CI_AS; - 在连接字符串中,添加
Charset=utf8;(对MySQL)或确保SQL Server使用Chinese_PRC_*系列排序规则(对SQL Server,Charset参数无效,必须靠数据库级设置)。
4.5 现象:程序发布后(Release模式)运行报错“未能加载文件或程序集 'System.Data.SqlClient'”,但Debug模式正常
原因:.NET Framework项目默认引用的是GAC(全局程序集缓存)中的System.Data.SqlClient,而发布时未将其复制到输出目录。.NET Core/6+项目则可能因NuGet包版本冲突导致。
解决:
- .NET Framework:在解决方案资源管理器中,右键引用
System.Data.SqlClient→ “属性”,将复制本地(Copy Local)设为True。 - .NET Core/6+:确保在
.csproj文件中,<PackageReference>指向的是Microsoft.Data.SqlClient(微软官方维护的新版),而非已废弃的System.Data.SqlClient,并确认其版本>=5.1.5。
5. 进阶技巧:用存储过程封装复杂逻辑,让C#代码更清爽、性能更高
5.1 为什么要把入住逻辑搬到数据库里?
目前AssignStudentToRoom方法在C#中写了大量SQL语句,每次调用都要编译执行计划,且业务规则(如床位校验)分散在代码里,难以统一管理和审计。将其封装为存储过程(Stored Procedure),好处有三:
- 性能:SQL Server对存储过程的执行计划会缓存,重复调用更快;
- 安全:应用只需
EXEC sp_AssignStudent ...权限,无需对底层表有INSERT/UPDATE权限,最小权限原则; - 可维护:业务规则集中在数据库,修改逻辑无需重新编译和发布C#程序。
5.2 创建入住存储过程:把所有校验和操作打包
在SQL Server Management Studio中,为StudentDormDB数据库执行以下脚本:
CREATE PROCEDURE sp_AssignStudentToRoom @StudentID NVARCHAR(12), @RoomID INT, @BedNumber TINYINT, @ResultCode INT OUTPUT, -- 输出参数,0=成功,-1=学生已入住,-2=床位已占,-3=房间不存在等 @ResultMsg NVARCHAR(200) OUTPUT -- 输出参数,返回具体提示信息 AS BEGIN SET NOCOUNT ON; -- 避免返回影响行数的消息,减少网络流量 BEGIN TRY BEGIN TRANSACTION; -- 1. 检查学生是否存在 IF NOT EXISTS (SELECT 1 FROM Students WHERE StudentID = @StudentID) BEGIN SET @ResultCode = -3; SET @ResultMsg = '学生学号不存在'; ROLLBACK TRANSACTION; RETURN; END -- 2. 检查学生是否已入住 IF EXISTS (SELECT 1 FROM DormAssignments WHERE StudentID = @StudentID AND Status = '入住中') BEGIN SET @ResultCode = -1; SET @ResultMsg = '该学生当前已有有效入住记录'; ROLLBACK TRANSACTION; RETURN; END -- 3. 检查房间是否存在且状态允许入住(非'维修中') IF NOT EXISTS (SELECT 1 FROM DormRooms WHERE RoomID = @RoomID AND Status <> '维修中') BEGIN SET @ResultCode = -4; SET @ResultMsg = '目标房间不存在或处于维修状态'; ROLLBACK TRANSACTION; RETURN; END -- 4. 检查床位是否空闲(利用唯一索引,此步可省略,但加上更清晰) IF EXISTS (SELECT 1 FROM DormAssignments WHERE RoomID = @RoomID AND BedNumber = @BedNumber AND Status = '入住中') BEGIN SET @ResultCode = -2; SET @ResultMsg = '该床位当前已被占用'; ROLLBACK TRANSACTION; RETURN; END -- 5. 执行入住 INSERT INTO DormAssignments (StudentID, RoomID, BedNumber, Status) VALUES (@StudentID, @RoomID, @BedNumber, '入住中'); -- 6. 更新房间状态(如果已满) UPDATE DormRooms SET Status = '已满' WHERE RoomID = @RoomID AND (SELECT COUNT(*) FROM DormAssignments WHERE RoomID = @RoomID AND Status = '入住中') = (SELECT BedCapacity FROM DormRooms WHERE RoomID = @RoomID); COMMIT TRANSACTION; SET @ResultCode = 0; SET @ResultMsg = '入住成功'; END TRY BEGIN CATCH ROLLBACK TRANSACTION; SET @ResultCode = -99; SET @ResultMsg = ERROR_MESSAGE(); END CATCH END5.3 在C#中调用存储过程:代码瞬间变干净
修改DormService.cs中的AssignStudentToRoom方法:
public (bool success, string message) AssignStudentToRoom(string studentId, int roomId, byte bedNumber) { int resultCode = 0; string resultMsg = ""; using (var conn = new SqlConnection(_connStr)) { conn.Open(); using (var cmd = new SqlCommand("sp_AssignStudentToRoom", conn)) { cmd.CommandType = CommandType.StoredProcedure; // 关键!声明为存储过程 // 输入参数 cmd.Parameters.AddWithValue("@StudentID", studentId); cmd.Parameters.AddWithValue("@RoomID", roomId); cmd.Parameters.AddWithValue("@BedNumber", bedNumber); // 输出参数 var pResultCode = cmd.Parameters.Add("@ResultCode", SqlDbType.Int); pResultCode.Direction = ParameterDirection.Output; var pResultMsg = cmd.Parameters.Add("@ResultMsg", SqlDbType.NVarChar, 200); pResultMsg.Direction = ParameterDirection.Output; cmd.ExecuteNonQuery(); resultCode = (int)pResultCode.Value; resultMsg = pResultMsg.Value.ToString(); } } return (resultCode == 0, resultMsg); } // 在窗体中调用 private void btnAssign_Click(object sender, EventArgs e) { var (success, msg) = _dormService.AssignStudentToRoom(txtStudentID.Text, (int)numRoomID.Value, (byte)numBedNumber.Value); if (success) { MessageBox.Show("操作成功!" + msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); LoadStudentData(); // 刷新列表 } else { MessageBox.Show("操作失败:" + msg, "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }表格:存储过程 vs C#代码逻辑对比
对比项 C#代码实现 存储过程实现 业务规则位置 分散在C#类中,易遗漏、难审计 集中在数据库,DBA可统一管控 性能 每次调用需编译SQL,有开销 执行计划缓存,首次编译后极快 安全性 应用需对多张表有读写权限 应用只需对存储过程有EXEC权限 调试难度 需启程序、设断点、看变量 SSMS中可直接 EXEC sp_...测试部署灵活性 修改逻辑需重编译、发新版客户端 修改存储过程,所有客户端即时生效
我一般会在项目初期就规划好核心业务的存储过程骨架,哪怕先写个空壳。因为后期补上,远比在C#里堆砌一堆if-else和SqlCommand要清晰得多。当你的系统开始接入更多模块(比如报修、缴费、门禁联动),你会发现,数据库才是那个最冷静、最可靠的“中央大脑”,而C#窗体,只是它的一个友好、可定制的“操作面板”。希望帮到你。
本文还有配套的精品资源,点击获取