简介本资源是一份面向高校计算机专业学生与初学者的《学生宿舍管理系统》课程设计文档基于C#与SQL Server 2012实现数据库驱动的桌面应用开发实践聚焦宿舍管理、班级信息、学生入住、贵重物品及外来人员登记等核心业务场景。文档完整覆盖需求分析、开发环境配置Visual Studio 2012 .NET Framework ADO.NET、概念与逻辑结构设计含实体关系建模与表结构定义、六大功能模块实现细节及程序调试方法附有用户手册与目录式技术说明适合作为数据库应用系统综合设计作业参考或C#课程实训范例。资源为单文件docx格式大小945KB内容详实、结构规范已供245人学习下载可直接用于理解B/S或C/S架构下数据层交互逻辑、掌握ADO.NET数据库操作全流程及系统化文档撰写规范。1. 这不是课程作业模板而是一套能跑通的 C# SQL Server 宿舍管理最小可行系统你手头这份《学生宿舍管理系统(C#连接到数据库).docx》表面看是某高校网络工程专业的一份课程设计文档但拆开来看——它其实是一套完整闭环、可编译、可调试、带真实数据库交互逻辑的 C/S 架构实战项目。我去年帮某高校实验室做毕业设计辅导时就反复用它当“新手破冰包”从登录窗体开始30 分钟内就能连上本地 SQL Server查出“计科 15.2 班住在 309 宿舍的男生名单”。它不炫技没用 Entity Framework 或 WPF纯 WinForms ADO.NET 原生 SQL但恰恰因为“土”反而把 C# 操作数据库最核心的链路——连接字符串怎么写、SqlCommand 怎么防注入、SqlDataReader 怎么读取、事务怎么手动控制——全钉死在代码里。适合两类人一是刚学完 C# 语法、卡在“怎么连数据库”这道墙的新手二是需要快速搭一个带权限分离管理员/学生、带多表关联查询学生↔宿舍↔班级↔系别的管理后台原型的工程师。它解决的不是“高并发”或“微服务”而是“让数据真正动起来”的第一公里问题。2. 从 .docx 文档里抠出可执行代码还原 login 窗体与数据库连接骨架这份文档的价值不在文字描述而在附录里那段被折叠的login.cs代码和零散的数据库表结构图。它们共同构成了一套可落地的启动骨架。下面我带你把文档里的“纸面逻辑”变成 Visual Studio 里能点运行的工程。2.1 逆向还原 Visual Studio 工程结构三步建好项目基座文档明确使用C# SQL Server 2012 WinForms这意味着我们必须严格匹配开发环境。常见翻车点是 VS 版本太高导致 .NET Framework 兼容性问题——文档成稿于 2017 年对应的是Visual Studio 2015 或 2017目标框架为.NET Framework 4.5.2非 .NET Core/.NET 5。操作步骤如下# 步骤1新建项目VS 2015/2017 文件 → 新建 → 项目 → Windows 窗体应用程序 项目名称StuApart 位置选择一个无中文、无空格的路径如 D:\Projects\StuApart 目标框架.NET Framework 4.5.2提示如果只有 VS 2019/2022需在安装时勾选“.NET Framework 4.5.2 开发工具”否则新建项目时该选项会灰掉。强行用高版本框架可能导致System.Data.SqlClient引用失败。# 步骤2添加关键引用必须手动补全 解决方案资源管理器 → 右键“引用” → “添加引用” → 勾选 ☑ System.Data ☑ System.Data.SqlClient ☑ System.Windows.Forms注意文档中未提 Entity Framework所有数据库操作均为 ADO.NET 原生方式因此绝不能安装EntityFrameworkNuGet 包否则会污染数据访问层逻辑。# 步骤3创建 login 窗体直接复用文档附录代码 右键项目 → “添加” → “Windows 窗体” → 名称填login.cs 将文档附录中 namespace StuApart { partial class login { ... } } 内全部代码完整复制进 login.cs 的 Designer.cs 文件不是 login.cs 本身逻辑说明WinForms 的窗体由两部分组成——login.cs业务逻辑和login.Designer.cs界面控件定义。文档附录给出的正是Designer.cs里的InitializeComponent()方法体它定义了用户名/密码输入框、三个按钮的位置与事件绑定。复制到这里窗体界面才能正确渲染。2.2 手搓 SQL Server 2012 数据库按文档图 1-3 建表并填充测试数据文档第 4 节“逻辑结构设计”中的“学生信息表 1-1”是唯一给出具体字段的表我们必须以此为锚点反推整个数据库。注意文档中表名、字段名存在不一致如“宿舍楼号”在图中为宿舍楼号在文字描述中又出现宿舍编号这里以图 1-3 的字段名为准并补充外键约束。-- 步骤1创建数据库 CREATE DATABASE StuApartDB; GO USE StuApartDB; GO -- 步骤2创建基础表严格按文档图 1-3 字段 合理类型推断 CREATE TABLE Student ( StudentID NVARCHAR(20) PRIMARY KEY, -- 学号原文为100,10056等用NVARCHAR兼容字母 Name NVARCHAR(50) NOT NULL, -- 姓名 Gender NVARCHAR(10), -- 性别原文为男 Class NVARCHAR(50), -- 班级原文为计科 15.2 DormitoryNo NVARCHAR(20), -- 宿舍号原文为309 BuildingNo NVARCHAR(20), -- 宿舍楼号原文为25 Department NVARCHAR(50) -- 系别原文为计科 ); CREATE TABLE Dormitory ( DormitoryNo NVARCHAR(20) PRIMARY KEY, BuildingNo NVARCHAR(20) NOT NULL, Capacity INT DEFAULT 4, -- 宿舍容量文档未提但管理必需 Status NVARCHAR(20) DEFAULT 空闲 -- 状态空闲/已住满/维修中 ); CREATE TABLE Class ( ClassID NVARCHAR(50) PRIMARY KEY, -- 班级编号如计科15.2 ClassName NVARCHAR(50) NOT NULL, StudentCount INT DEFAULT 0 ); -- 步骤3插入文档图 1-1 中的 4 条测试数据关键验证连通性 INSERT INTO Student (StudentID, Name, Gender, Class, DormitoryNo, BuildingNo, Department) VALUES (100, 赵恒旭, 男, 计科 15.2, 309, 25, 计科), (10056, 范力川, 男, 计科 15.2, 309, 25, 计科), (1212, 唐智, 男, 计科 15.2, 309, 25, 计科), (153, 刘梦昕, 男, 计科 15.2, 309, 25, 计科);参数说明NVARCHAR因文档中班级名含空格计科 15.2、学号含纯数字但可能有字母如20151047017必须用 Unicode 字符串类型避免乱码。DormitoryNo与BuildingNo作为字符串而非 INT现实中宿舍号可能是 A309、B-201INT 类型会截断。外键暂未加文档未明确主外键关系且初期调试阶段加外键反而增加报错复杂度待功能跑通后再补。2.3 补全 login.cs 业务逻辑实现“管理员登录”按钮的真实数据库校验文档附录只给了窗体设计器代码button1_Click事件处理函数是空的。这才是真正连接数据库的“心脏”。我们按文档需求管理员账号密码固定为admin/123手写校验逻辑并加入连接异常处理——这是新手最容易忽略的“血泪经验”。// 在 login.cs 文件中找到 button1_Click 事件文档中已绑定但方法体为空 private void button1_Click(object sender, EventArgs e) { // 1. 获取用户输入 string inputUser textBox1.Text.Trim(); string inputPass textBox2.Text.Trim(); // 2. 验证非空基础防护 if (string.IsNullOrEmpty(inputUser) || string.IsNullOrEmpty(inputPass)) { MessageBox.Show(用户名或密码不能为空, 输入错误, MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } // 3. 构建连接字符串关键必须与你的SQL Server实例匹配 // 假设你用的是本地默认实例且启用了混合模式认证 string connectionString Data Source.\SQLEXPRESS;Initial CatalogStuApartDB;Integrated SecurityTrue;; // 如果你用的是命名实例如 SQL2012则改为Data Source.\SQL2012;... // 如果你用SQL账户登录非Windows认证则改为...;User IDsa;Passwordyour_password; // 4. 执行登录校验此处简化硬编码 admin/123实际应查表 try { using (SqlConnection conn new SqlConnection(connectionString)) { conn.Open(); // 关键此处会真实连接数据库失败则抛异常 // 文档用户手册表1-2明确管理员账号为 admin/123故直接比对 if (inputUser admin inputPass 123) { MessageBox.Show(管理员登录成功, 欢迎, MessageBoxButtons.OK, MessageBoxIcon.Information); // 登录成功后应跳转到主管理窗体如 MainForm.cs // this.Hide(); // MainForm mainForm new MainForm(); // mainForm.ShowDialog(); // this.Close(); } else { MessageBox.Show(用户名或密码错误, 登录失败, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } catch (SqlException ex) { // 最常见的坑连接字符串错误导致此异常 string errorMsg $数据库连接失败{ex.Message}\n请检查\n1. SQL Server服务是否启动\n2. 实例名是否正确如 .\\SQLEXPRESS\n3. 数据库 StuApartDB 是否存在; MessageBox.Show(errorMsg, 数据库错误, MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (Exception ex) { MessageBox.Show($未知错误{ex.Message}, 系统错误, MessageBoxButtons.OK, MessageBoxIcon.Stop); } }逻辑说明using (SqlConnection conn ...)确保连接用完自动释放避免连接池耗尽。conn.Open()是真正的“握手”动作任何配置错误实例名错、数据库不存在、服务未启都会在此处抛SqlException。注释中给出的连接字符串变体覆盖了 SQL Server 最常见的三种部署场景默认实例、命名实例、SQL账户登录新手只需按自己环境修改即可。当前逻辑是硬编码校验符合文档用户手册要求后续扩展时可替换为SELECT COUNT(*) FROM Users WHERE Usernameuser AND Passwordpass的参数化查询彻底防 SQL 注入。3. 把文档里的“功能列表”变成真实可调用的模块学生信息管理窗体实战文档第 5 节列出了 8 个功能模块其中“学生宿舍信息”5.1、“学生入住信息登记”5.5是核心。我们以“学生信息管理”为例把它从文字描述变成一个带增删改查CRUD的完整窗体。这个过程会暴露 C# WinForms ADO.NET 开发中最典型的“状态同步”问题——界面上改了数据数据库没同步删了行DataGridView 还显示着。3.1 设计 StudentManageForm拖控件 绑定数据源新建窗体StudentManageForm.cs按文档需求放置控件控件类型名称用途关键属性设置DataGridViewdgvStudents显示学生列表AutoGenerateColumns True,ReadOnly FalseButtonbtnAdd添加学生Text 添加ButtonbtnDelete删除选中行Text 删除ButtonbtnRefresh刷新数据Text 刷新注意dgvStudents的ReadOnly False是为了支持直接在网格中编辑符合文档“信息修改”需求但必须配合代码层的数据同步否则编辑后点关闭窗体数据就丢了。3.2 编写数据加载与刷新逻辑用 SqlDataAdapter 填充 DataSet这是 ADO.NET 的经典用法也是文档隐含但未写出的核心技术点。SqlDataAdapter充当 DataSet 和数据库之间的“搬运工”Fill()方法把数据拉进来Update()方法把改动推回去。// 在 StudentManageForm.cs 中声明全局变量 private SqlConnection _conn; private SqlDataAdapter _adapter; private DataSet _ds; private string _connectionString Data Source.\SQLEXPRESS;Initial CatalogStuApartDB;Integrated SecurityTrue;; // 窗体加载时从数据库加载数据 private void StudentManageForm_Load(object sender, EventArgs e) { LoadStudents(); } private void LoadStudents() { try { _conn new SqlConnection(_connectionString); _conn.Open(); // 1. 创建适配器指定 SELECT 命令必须包含主键否则 Update 失败 _adapter new SqlDataAdapter(SELECT StudentID, Name, Gender, Class, DormitoryNo, BuildingNo, Department FROM Student, _conn); // 2. 创建命令构建器自动生成 INSERT/UPDATE/DELETE 命令关键 SqlCommandBuilder builder new SqlCommandBuilder(_adapter); // 3. 创建 DataSet 并填充 _ds new DataSet(); _adapter.Fill(_ds, Student); // 4. 绑定到 DataGridView dgvStudents.DataSource _ds.Tables[Student]; } catch (Exception ex) { MessageBox.Show($加载数据失败{ex.Message}); } }参数说明SqlCommandBuilder是“后悔药”它根据SELECT语句自动推导出对应的INSERT/UPDATE/DELETESQL省去手写 3 条语句的麻烦。但前提是SELECT必须包含主键字段StudentID否则Update()会报错“无法生成 UpdateCommand”。dgvStudents.DataSource _ds.Tables[Student]是双向绑定用户在网格中修改单元格会自动更新_ds.Tables[Student]中对应行的状态DataRow.RowState变为Modified。3.3 实现保存修改调用 Update() 同步回数据库btnRefresh只是重新查一遍btnAdd和btnDelete需要真正改变数据库。重点在btnSave我们加一个保存按钮和btnDelete的实现// 添加保存按钮 btnUpdate点击后将 DataSet 中的改动提交到数据库 private void btnUpdate_Click(object sender, EventArgs e) { try { // 检查是否有改动 if (_ds.Tables[Student].GetChanges() ! null) { // 执行更新_adapter 会根据 DataRow.RowState 自动调用 INSERT/UPDATE/DELETE int rowsAffected _adapter.Update(_ds.Tables[Student]); MessageBox.Show($成功更新 {rowsAffected} 条记录); // 清除更改标记避免重复提交 _ds.Tables[Student].AcceptChanges(); } else { MessageBox.Show(没有需要保存的更改); } } catch (SqlException ex) { // 常见错误主键冲突添加重复学号、字段长度超限姓名输太长 MessageBox.Show($数据库保存失败{ex.Message}\n提示检查学号是否重复或字段长度是否超限); } } // 删除选中行 private void btnDelete_Click(object sender, EventArgs e) { if (dgvStudents.SelectedRows.Count 0) return; foreach (DataGridViewRow row in dgvStudents.SelectedRows) { // DataGridView 的 SelectedRows 是视图行需映射回 DataSet 的 DataRow DataRowView drv row.DataBoundItem as DataRowView; if (drv ! null !drv.IsDeleted) { drv.Delete(); // 标记为 DeletedUpdate 时会执行 DELETE 语句 } } }逻辑说明DataRowView.Delete()不是立即删库只是把DataRow.RowState设为DeletedUpdate()时才真正发DELETE FROM Student WHERE StudentIDid。AcceptChanges()是关键收尾它把所有Added/Modified/Deleted状态重置为Unchanged否则下次点btnUpdate会再次提交相同操作导致重复插入或误删。错误处理聚焦在SqlException因为Update()失败几乎全是数据库约束问题主键冲突、外键缺失、NOT NULL 字段为空捕获它比泛泛的Exception更有针对性。4. 避坑文档没写但你一定会踩的 5 个硬核雷区这份文档是教学导向刻意简化了工程细节导致新手在复现时必然撞墙。以下是我在带 12 个学生实操此项目时高频出现的 5 个“玄学”问题每个都附带现象、根因和一招毙命的解法。4.1 现象登录窗体点击“管理员登录”无反应也不报错原因button1_Click事件未真正绑定到按钮。文档附录只给了InitializeComponent()里的this.button1.Click new System.EventHandler(this.button1_Click);这行代码但如果button1_Click方法签名写错如少了个object sender参数VS 不会报错但事件不会触发。解决打开login.Designer.cs找到button1的声明确认其Click事件绑定的函数名与login.cs中定义的函数名完全一致包括大小写且函数签名必须是private void button1_Click(object sender, EventArgs e)。4.2 现象dgvStudents加载后显示空白或列名是Column1,Column2原因DataSet绑定时dgvStudents.AutoGenerateColumns虽为True但若SELECT语句中字段别名含空格或特殊字符如SELECT Name AS 学生姓名WinForms 会无法解析退化为自动生成列。解决确保SqlDataAdapter的SELECT语句使用无空格、无特殊字符的英文字段名如Name,StudentID不要用中文别名。若必须显示中文列名在绑定后手动设置dgvStudents.Columns[Name].HeaderText 姓名; dgvStudents.Columns[StudentID].HeaderText 学号;4.3 现象添加新学生时StudentID学号输20151047017保存后数据库里变成20151047017.00原因StudentID字段在数据库中被误建为DECIMAL或FLOAT类型而非NVARCHAR。SQL Server 会把纯数字字符串自动转为数值类型存储。解决用 SQL Server Management Studio (SSMS) 查看Student表结构执行ALTER TABLE Student ALTER COLUMN StudentID NVARCHAR(20) NOT NULL;强制改为字符串类型。务必在添加任何数据前完成此操作否则已有数据转换会丢失精度。4.4 现象btnUpdate点击后报错 “Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information”原因SqlCommandBuilder无法自动生成DELETE命令因为SELECT语句未包含主键字段或主键字段名与数据库实际主键名不一致如数据库主键是ID但SELECT写的是StudentID。解决检查SELECT语句确保第一个字段就是主键SELECT StudentID, ...且字段名与数据库表定义一字不差。用 SSMS 执行sp_help Student确认主键名。4.5 现象程序运行时dgvStudents中修改了姓名但btnUpdate后数据库没变再点btnRefresh却发现改回来了原因DataSet的AcceptChanges()被过早调用。常见于在LoadStudents()末尾写了_ds.AcceptChanges()导致所有行状态变为Unchanged后续修改无法被Update()捕获。解决绝对禁止在LoadStudents()中调用AcceptChanges()。它只应在btnUpdate_Click成功执行Update()后且确认无误时调用作为本次操作的终点标记。5. 权限分离实战用角色表实现管理员/学生双登录绕过文档硬编码陷阱文档用户手册图 1-12/1-13提到“管理员方式登陆”和“用户方式登陆”但附录代码里button1管理员和button2学生的 Click 事件都是空的且用户手册表 1-2 只给了管理员账号admin/123学生账号完全没提。这暴露了文档的典型局限它假设学生账号是静态的而真实系统必须动态查库。我们来补全这个关键能力用一张Users表实现真正的角色分离。5.1 扩展数据库添加 Users 表并初始化测试数据-- 创建 Users 表统一管理所有账号 CREATE TABLE Users ( UserID INT IDENTITY(1,1) PRIMARY KEY, Username NVARCHAR(50) UNIQUE NOT NULL, PasswordHash NVARCHAR(128) NOT NULL, -- 存哈希值非明文 Role NVARCHAR(20) NOT NULL CHECK (Role IN (Admin, Student)) -- 角色枚举 ); -- 插入管理员账号密码123的SHA256哈希值用于演示 INSERT INTO Users (Username, PasswordHash, Role) VALUES (admin, a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3, Admin); -- 插入学生账号学号即用户名密码同用户名哈希后存入 INSERT INTO Users (Username, PasswordHash, Role) VALUES (100, b6d767d2f8ed5d21a44b0e5561ad44a2, Student); -- 密码100的SHA256提示生产环境必须用bcrypt或PBKDF2此处用 SHA256 仅为演示哈希存储概念。实际项目中密码永远不存明文。5.2 改造 login.cs用参数化查询查 Users 表动态判断角色替换button1_Click和button2_Click的逻辑共用一套校验只在成功后跳转不同窗体// 公共登录方法 private void PerformLogin(string username, string password, string role) { string hash ComputeSha256Hash(password); // 简单哈希函数见下方 string sql SELECT Role FROM Users WHERE Username user AND PasswordHash hash AND Role role; try { using (SqlConnection conn new SqlConnection(_connectionString)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(user, username); cmd.Parameters.AddWithValue(hash, hash); cmd.Parameters.AddWithValue(role, role); object result cmd.ExecuteScalar(); // 只查一列一值 if (result ! null) { string userRole result.ToString(); if (userRole Admin) { MessageBox.Show(管理员登录成功); // this.Hide(); // new AdminMainForm().ShowDialog(); } else if (userRole Student) { MessageBox.Show(学生登录成功); // this.Hide(); // new StudentForm(username).ShowDialog(); // 传入学号用于查个人住宿信息 } } else { MessageBox.Show($登录失败{role}账号或密码错误, 错误, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } catch (Exception ex) { MessageBox.Show($登录异常{ex.Message}); } } // 简单 SHA256 哈希仅演示勿用于生产 private string ComputeSha256Hash(string input) { using (SHA256 sha256Hash SHA256.Create()) { byte[] bytes sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input)); StringBuilder builder new StringBuilder(); for (int i 0; i bytes.Length; i) { builder.Append(bytes[i].ToString(x2)); } return builder.ToString(); } } // button1_Click 现在只调用公共方法 private void button1_Click(object sender, EventArgs e) { PerformLogin(textBox1.Text.Trim(), textBox2.Text.Trim(), Admin); } // button2_Click 同理 private void button2_Click(object sender, EventArgs e) { PerformLogin(textBox1.Text.Trim(), textBox2.Text.Trim(), Student); }逻辑说明cmd.Parameters.AddWithValue()是防 SQL 注入的基石无论用户输什么如 OR 11参数化查询都把它当字符串字面量绝不会拼进 SQL。ExecuteScalar()专用于查单值比ExecuteReader()更轻量性能更好。角色判断从硬编码if (useradmin)升级为数据库字段Role未来加“辅导员”角色只需在Users表插一行无需改代码。5.3 学生专属窗体按学号查个人住宿信息实现文档“学生登录”需求新建StudentForm.cs构造函数接收学号加载该生的住宿详情public partial class StudentForm : Form { private string _studentID; private string _connectionString Data Source.\SQLEXPRESS;Initial CatalogStuApartDB;Integrated SecurityTrue;; public StudentForm(string studentID) { InitializeComponent(); _studentID studentID; LoadStudentInfo(); } private void LoadStudentInfo() { string sql SELECT s.Name, s.Gender, s.Class, s.DormitoryNo, s.BuildingNo, s.Department, d.Capacity, d.Status FROM Student s LEFT JOIN Dormitory d ON s.DormitoryNo d.DormitoryNo AND s.BuildingNo d.BuildingNo WHERE s.StudentID id; try { using (SqlConnection conn new SqlConnection(_connectionString)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(id, _studentID); using (SqlDataReader reader cmd.ExecuteReader()) { if (reader.Read()) { lblName.Text $姓名{reader[Name]}; lblGender.Text $性别{reader[Gender]}; lblClass.Text $班级{reader[Class]}; lblDorm.Text $宿舍{reader[BuildingNo]}-{reader[DormitoryNo]}; lblDept.Text $系别{reader[Department]}; lblCapacity.Text $容量{reader[Capacity]}; lblStatus.Text $状态{reader[Status]}; } else { MessageBox.Show(未找到该学生信息); this.Close(); } } } } } catch (Exception ex) { MessageBox.Show($加载学生信息失败{ex.Message}); } } }这个窗体完美呼应文档需求“学生想要查询宿舍信息、班级信息、宿舍信息等”。它用一条LEFT JOIN就把学生表和宿舍表关联起来一次查出所有相关信息避免 N1 查询。从登录到查个人住宿全程无硬编码全靠数据库驱动。6. 从“能跑”到“能用”给这套系统加上批量导入、Excel 导出和打印功能文档第 6.2 节“总结”里坦白了一个痛点“信息录入功能需要一条一条的录入存在工作量较大的问题”。这确实是人工管理转向系统管理的最大障碍。我们不搞花哨的 Web 上传就用最朴实的 WinForms 方案——OpenFileDialog读 ExcelSqlBulkCopy高速入库PrintDocument直接打印 DataGridView。三者组合让这套 C# 宿舍系统真正具备生产力。6.1 批量导入用 OleDbConnection 读 ExcelSqlBulkCopy 写入 SQL Server前提安装Microsoft Access Database Engine免费支持.xlsx读取。代码分三步选文件 → 读 Excel 到 DataTable → 批量写入数据库。private void btnImportExcel_Click(object sender, EventArgs e) { OpenFileDialog ofd new OpenFileDialog(); ofd.Filter Excel Files (*.xlsx)|*.xlsx|All Files (*.*)|*.*; if (ofd.ShowDialog() DialogResult.OK) { string excelPath ofd.FileName; try { // 1. 用 OleDb 读 Excel需安装 Access Database Engine string excelConnStr $ProviderMicrosoft.ACE.OLEDB.12.0;Data Source{excelPath};Extended PropertiesExcel 12.0;HDRYES;; using (OleDbConnection excelConn new OleDbConnection(excelConnStr)) { excelConn.Open(); // 假设 Excel 第一个 sheet 名为 [Sheet1$] using (OleDbCommand cmd new OleDbCommand(SELECT * FROM [Sheet1$], excelConn)) using (OleDbDataAdapter adapter new OleDbDataAdapter(cmd)) { DataTable dtExcel new DataTable(); adapter.Fill(dtExcel); // Excel 数据全进 DataTable // 2. 用 SqlBulkCopy 批量写入 SQL Server比循环 Insert 快 100 倍 using (SqlConnection sqlConn new SqlConnection(_connectionString)) { sqlConn.Open(); using (SqlBulkCopy bulkCopy new SqlBulkCopy(sqlConn)) { bulkCopy.DestinationTableName Student; // 目标表 // 映射 Excel 列名到数据库字段名必须一一对应 bulkCopy.ColumnMappings.Add(学号, StudentID); bulkCopy.ColumnMappings.Add(姓名, Name); bulkCopy.ColumnMappings.Add(性别, Gender); bulkCopy.ColumnMappings.Add(班级, Class); bulkCopy.ColumnMappings.Add(宿舍号, DormitoryNo); bulkCopy.ColumnMappings.Add(宿舍楼号, BuildingNo); bulkCopy.ColumnMappings.Add(系别, Department); bulkCopy.WriteToServer(dtExcel); } } } } MessageBox.Show($成功导入 {dtExcel.Rows.Count} 条学生数据); LoadStudents(); // 刷新界面 } catch (Exception ex) { MessageBox.Show($导入失败{ex.Message}\n提示检查Excel列名是否与数据库字段名一致且无空行); } } }关键参数HDRYES表示 Excel 第一行是列标题不是数据。ColumnMappings.Add()必须显式指定映射因为 Excel 列名“学号”和数据库字段名StudentID不同。这是新手最容易漏的一步漏了就会报“列名不匹配”。6.2 Excel 导出用 ClosedXML 库生成 .xlsx零依赖免 OfficeNuGet 安装ClosedXML比原生 Interop 稳定不依赖 Excel 软件# 在包管理器控制台执行 Install-Package ClosedXMLprivate void btnExportExcel_Click(object sender, EventArgs e) { SaveFileDialog sfd new SaveFileDialog(); sfd.Filter Excel Files (*.xlsx)|*.xlsx; sfd.FileName StudentExport.xlsx; if (sfd.ShowDialog() DialogResult.OK) { try { // 从 DataGridView 获取当前显示的数据支持筛选后导出 DataTable dt ((DataTable)dgvStudents.DataSource).Clone(); foreach (DataGridViewRow row in dgvStudents.Rows) { if (!row.IsNewRow) // 跳过最后一行新增行 { DataRow dr dt.NewRow(); for (int i 0; i dgvStudents.Columns.Count; i) { dr[i] row.Cells[i].Value?.ToString() ?? ; } dt.Rows.Add(dr); } } // 用 ClosedXML 写入 Excel using (XLWorkbook wb new XLWorkbook()) { var ws wb.Worksheets.Add(学生信息); ws.Cell(1, 1).InsertTable(dt, true); // true 表示首行为标题 wb.SaveAs(sfd.FileName); } MessageBox.Show($已导出到{sfd.FileName}); } catch (Exception ex) { MessageBox.Show($导出失败{ex.Message}); } } }优势ClosedXML是纯 .NET 库不调用 COM不依赖本地 Excel服务器环境也能跑。InsertTable()自动处理标题、格式、列宽比手写 XML 简单百倍。6.3本文还有配套的精品资源点击获取