简介:这份资源是面向计算机相关专业学生与初学开发者的数据库课程设计完整项目,以C#与Windows Forms构建人事管理桌面应用,并与关系型数据库深度集成,可解决课程设计选题、数据库建模与桌面端开发练手等需求。压缩包共91个文件,约4.1MB,以cs源码、resx与resources界面资源、exe与dll可执行组件、config配置、mdf与ldf数据库文件以及docx文档为主,另含sln与csproj工程文件,覆盖登录、员工、部门、考勤、薪资、改密等模块,导入数据库即可运行。目前已有5723人学习下载。读者可获得可运行的完整源码与数据库文件、课程设计报告和系统运行说明书,便于理解表结构设计、SQL查询与事务处理思路,并对照窗体代码学习C#界面与数据联动实现,适合作为课程设计参考或二次开发起点。
1. 从一份课程设计说起:为什么我建议你亲手跑一遍这套人事管理系统
很多同学做数据库课程设计,最后交上去的是一堆 SQL 脚本加几张截图,答辩时被问“你这个系统到底怎么跑起来”就卡住了。我手里这份 C# 人事管理系统不一样,它是一个能编译、能连库、能点按钮看到数据的完整桌面应用。技术栈是 C# WinForms + SQL Server,典型的课程设计选题,但代码结构比大多数“能跑就行”的作业干净得多。它覆盖了员工信息录入、部门管理、薪资查询、考勤记录这几个核心模块,适合正在做数据库课设、想找一个能改能扩的参考项目的同学。如果你已经受够了“只有 ER 图没有界面”的课设,这套东西值得你花一个下午跑通它。
2. 项目结构与环境搭建:从零把项目跑起来
2.1 目录结构长什么样
拿到源码包后,先别急着双击 .sln 文件。我一般会先扫一遍目录,搞清楚哪些是代码、哪些是资源、哪些是数据库脚本。这套项目的结构大致如下:
PersonnelManagement/ ├── PersonnelManagement.sln # VS 解决方案文件 ├── PersonnelManagement/ │ ├── App.config # 数据库连接字符串配置 │ ├── Program.cs # 程序入口 │ ├── Forms/ # 所有窗体文件 │ │ ├── LoginForm.cs # 登录窗体 │ │ ├── MainForm.cs # 主界面(导航容器) │ │ ├── EmployeeForm.cs # 员工信息管理 │ │ ├── DepartmentForm.cs # 部门管理 │ │ ├── SalaryForm.cs # 薪资查询 │ │ └── AttendanceForm.cs # 考勤记录 │ ├── DAL/ # 数据访问层 │ │ ├── SqlHelper.cs # 数据库操作公共类 │ │ ├── EmployeeDAL.cs │ │ ├── DepartmentDAL.cs │ │ └── ... │ ├── Models/ # 实体类 │ │ ├── Employee.cs │ │ ├── Department.cs │ │ └── ... │ └── Utils/ # 工具类 │ └── MD5Helper.cs # 密码加密 ├── Database/ │ └── InitDatabase.sql # 建库建表脚本 └── README.md这个分层方式在课程设计里算规范的:Forms 负责界面,DAL 负责数据库操作,Models 是实体类。你二次开发时,加一个新模块基本就是“加一个 Model → 加一个 DAL → 加一个 Form”三步走。
2.2 环境准备与数据库初始化
跑这个项目需要两样东西:Visual Studio(2019 或 2022 社区版都行)和 SQL Server(Express 版就够)。我踩过的坑是 SQL Server 装完后忘了开 TCP/IP 协议,导致连接字符串怎么写都连不上。
先执行数据库脚本。打开 SSMS,新建查询,把Database/InitDatabase.sql的内容贴进去执行:
-- 创建数据库 CREATE DATABASE PersonnelDB; GO USE PersonnelDB; GO -- 员工表 CREATE TABLE Employees ( EmpID INT IDENTITY(1,1) PRIMARY KEY, EmpName NVARCHAR(50) NOT NULL, Gender NVARCHAR(4) DEFAULT '男', BirthDate DATE, DeptID INT FOREIGN KEY REFERENCES Departments(DeptID), Position NVARCHAR(50), HireDate DATE DEFAULT GETDATE(), Phone NVARCHAR(20), Password NVARCHAR(64) -- 存 MD5 值 ); -- 部门表 CREATE TABLE Departments ( DeptID INT IDENTITY(1,1) PRIMARY KEY, DeptName NVARCHAR(50) NOT NULL UNIQUE, Manager NVARCHAR(50) ); -- 薪资表 CREATE TABLE Salaries ( SalaryID INT IDENTITY(1,1) PRIMARY KEY, EmpID INT FOREIGN KEY REFERENCES Employees(EmpID), BaseSalary DECIMAL(10,2), Bonus DECIMAL(10,2) DEFAULT 0, PayMonth NVARCHAR(7) -- 格式:2024-01 );脚本里几个关键点:IDENTITY(1,1)让主键自增,省得手动维护 ID;FOREIGN KEY保证员工必须属于一个已存在的部门;Password字段长度给到 64,因为 MD5 是 32 位十六进制字符串,留一倍余量。执行完后用SELECT * FROM Employees确认表建好了。
2.3 连接字符串配置与首次运行
数据库建好后,打开App.config,改连接字符串:
<connectionStrings> <add name="PersonnelDB" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=PersonnelDB;Integrated Security=True" providerName="System.Data.SqlClient"/> </connectionStrings>Data Source写你的 SQL Server 实例名。如果你用的是默认实例,写localhost或.;Express 版写.\SQLEXPRESS。Integrated Security=True表示用 Windows 身份验证,不用填账号密码,本地开发最省事。
改完配置,在 Visual Studio 里按 F5 运行。如果一切正常,会先弹出登录窗体。初始账号一般在脚本末尾有 INSERT 语句,通常是 admin / 123456。登录成功后进入主界面,左侧是导航按钮,右侧是内容面板。
提示:如果运行时报“未找到连接字符串”,检查 App.config 里的 name 属性是否和代码中
ConfigurationManager.ConnectionStrings["PersonnelDB"]里的 key 完全一致,大小写敏感。
3. 核心模块拆解:数据访问层与窗体交互的完整链路
3.1 SqlHelper:所有数据库操作的入口
这个项目没有用 Entity Framework,而是手写 ADO.NET。所有 DAL 类都通过一个SqlHelper来执行 SQL,这样连接管理、参数化查询都集中在一处。先看核心代码:
public static class SqlHelper { // 从配置文件读取连接字符串 private static readonly string connStr = ConfigurationManager.ConnectionStrings["PersonnelDB"].ConnectionString; // 执行增删改,返回受影响行数 public static int ExecuteNonQuery(string sql, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteNonQuery(); } } } // 执行查询,返回 DataTable public static DataTable ExecuteQuery(string sql, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataTable dt = new DataTable(); adapter.Fill(dt); return dt; } } } // 执行查询,返回首行首列(用于 COUNT、MAX 等) public static object ExecuteScalar(string sql, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteScalar(); } } } }三个方法覆盖了绝大多数场景:ExecuteNonQuery用于 INSERT、UPDATE、DELETE;ExecuteQuery返回 DataTable,直接绑定到 DataGridView;ExecuteScalar用于查总数或判断是否存在。
using语句保证连接和命令对象用完就释放,不会因为忘记 Close 导致连接池耗尽。params SqlParameter[]这个写法让调用方可以传任意数量的参数,同时强制使用参数化查询,避免 SQL 注入。我见过不少课设直接拼字符串,输入框里打个' OR '1'='1就能登录,这套代码从根上堵住了这个问题。
3.2 员工管理模块:从界面到数据库的完整链路
以员工信息管理为例,走一遍“点击查询按钮 → 数据从数据库到界面”的完整流程。
先看 DAL 层的查询方法:
public class EmployeeDAL { // 按姓名模糊查询员工,返回 DataTable 供界面绑定 public DataTable GetEmployeesByName(string name) { string sql = @"SELECT e.EmpID, e.EmpName, e.Gender, e.BirthDate, d.DeptName, e.Position, e.HireDate, e.Phone FROM Employees e LEFT JOIN Departments d ON e.DeptID = d.DeptID WHERE e.EmpName LIKE @name ORDER BY e.EmpID DESC"; SqlParameter[] paras = { new SqlParameter("@name", "%" + name + "%") }; return SqlHelper.ExecuteQuery(sql, paras); } // 新增员工 public int AddEmployee(Employee emp) { string sql = @"INSERT INTO Employees (EmpName, Gender, BirthDate, DeptID, Position, HireDate, Phone, Password) VALUES (@name, @gender, @birth, @deptId, @position, @hire, @phone, @pwd)"; SqlParameter[] paras = { new SqlParameter("@name", emp.EmpName), new SqlParameter("@gender", emp.Gender), new SqlParameter("@birth", emp.BirthDate), new SqlParameter("@deptId", emp.DeptID), new SqlParameter("@position", emp.Position), new SqlParameter("@hire", emp.HireDate), new SqlParameter("@phone", emp.Phone), new SqlParameter("@pwd", MD5Helper.Encrypt("123456")) // 默认密码 }; return SqlHelper.ExecuteNonQuery(sql, paras); } }查询用了LEFT JOIN,因为员工可能还没分配部门,用 INNER JOIN 会漏掉这些人。LIKE @name配合%通配符实现模糊搜索,参数化写法让%作为参数值传入,而不是拼在 SQL 里。
新增员工时,密码字段用MD5Helper.Encrypt加密后存入。这里默认给新员工设123456作为初始密码,实际项目中应该让管理员手动设置或发邮件通知。
再看窗体层怎么调用:
// EmployeeForm.cs 中的查询按钮事件 private void btnSearch_Click(object sender, EventArgs e) { string keyword = txtSearch.Text.Trim(); EmployeeDAL dal = new EmployeeDAL(); DataTable dt = dal.GetEmployeesByName(keyword); dgvEmployees.DataSource = dt; // 隐藏不需要显示的列 if (dgvEmployees.Columns["EmpID"] != null) dgvEmployees.Columns["EmpID"].Visible = false; }dgvEmployees.DataSource = dt这一行就把 DataTable 绑定到了 DataGridView,列名自动映射。后面手动隐藏 EmpID 列,因为自增主键对用户没意义。这种“DAL 返回 DataTable → 界面直接绑定”的模式在 WinForms 里非常高效,不需要定义复杂的 ViewModel。
3.3 登录验证与 MD5 加密
登录模块是理解这个项目权限设计的入口。看代码:
// LoginForm.cs private void btnLogin_Click(object sender, EventArgs e) { string username = txtUsername.Text.Trim(); string password = txtPassword.Text.Trim(); if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { MessageBox.Show("用户名和密码不能为空"); return; } string encryptedPwd = MD5Helper.Encrypt(password); string sql = "SELECT COUNT(*) FROM Employees WHERE EmpName = @name AND Password = @pwd"; SqlParameter[] paras = { new SqlParameter("@name", username), new SqlParameter("@pwd", encryptedPwd) }; int count = Convert.ToInt32(SqlHelper.ExecuteScalar(sql, paras)); if (count > 0) { MainForm main = new MainForm(username); main.Show(); this.Hide(); } else { MessageBox.Show("用户名或密码错误"); } }MD5Helper 的实现很简单:
public static class MD5Helper { public static string Encrypt(string input) { using (MD5 md5 = MD5.Create()) { byte[] bytes = Encoding.UTF8.GetBytes(input); byte[] hash = md5.ComputeHash(bytes); StringBuilder sb = new StringBuilder(); foreach (byte b in hash) sb.Append(b.ToString("x2")); // 转成小写十六进制 return sb.ToString(); } } }ExecuteScalar返回匹配的行数,大于 0 就放行。密码在存入和验证时都走 MD5,数据库里看不到明文。需要说明的是,MD5 在现在来看已经不够安全了,但课程设计场景下用来演示“密码不能明文存储”这个知识点是够的。如果你要改成生产级,换成 BCrypt 或 PBKDF2 更合适。
注意:MD5Helper 里
b.ToString("x2")的x2表示两位小写十六进制,如果你写成X2就是大写,验证时两边必须一致,否则永远登录失败。
4. 避坑与常见问题:跑不起来时先查这几条
4.1 连接数据库失败:80% 是实例名或协议没开
现象:程序启动后点登录,弹窗报“在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误”。
原因:连接字符串里的Data Source写错了,或者 SQL Server 的 TCP/IP 协议没启用。Express 版默认是命名实例,必须写.\SQLEXPRESS,不能只写.。
解决:打开 SQL Server Configuration Manager,找到“SQL Server 网络配置 → 实例的协议”,把 TCP/IP 状态改为“已启用”,然后重启 SQL Server 服务。连接字符串改成Data Source=.\SQLEXPRESS;...。
4.2 登录时提示“对象名 'Employees' 无效”
现象:编译通过,但一登录就报 SQL 错误,说找不到表。
原因:建表脚本执行时没有先USE PersonnelDB,表建到了 master 或其他数据库里。或者连接字符串里的Initial Catalog写成了别的库名。
解决:在 SSMS 里执行SELECT * FROM PersonnelDB.sys.tables确认表在正确的库里。如果不在,删掉重建,确保脚本开头有USE PersonnelDB;。
4.3 DataGridView 绑定后列顺序乱、显示英文列名
现象:查询结果出来了,但列头显示的是EmpName、DeptName这种英文,而且顺序和 SQL 里的 SELECT 不一致。
原因:DataGridView 自动生成列时按 DataTable 的列顺序来,但如果你在界面设计器里手动加过列,就会冲突。
解决:在窗体构造函数里设置dgvEmployees.AutoGenerateColumns = true;,然后在绑定后手动调整列头文字:
dgvEmployees.Columns["EmpName"].HeaderText = "姓名"; dgvEmployees.Columns["DeptName"].HeaderText = "部门"; dgvEmployees.Columns["Position"].HeaderText = "职位";4.4 新增员工后界面不刷新
现象:点“新增”填完信息保存,数据库里确实多了一条,但 DataGridView 里看不到。
原因:保存成功后没有重新调用查询方法刷新数据源。
解决:在新增按钮的事件处理最后,重新执行一次查询并绑定:
private void btnAdd_Click(object sender, EventArgs e) { // ... 弹出新增对话框并保存 ... if (dialogResult == DialogResult.OK) { // 重新加载数据 dgvEmployees.DataSource = dal.GetEmployeesByName(txtSearch.Text.Trim()); } }4.5 中文乱码:数据库字段类型用了 varchar
现象:员工姓名存进去变成问号,或者查询时匹配不到。
原因:建表时用了VARCHAR而不是NVARCHAR。VARCHAR 按字节存储,中文占两个字节,容易出问题。
解决:把所有存中文的字段改成NVARCHAR,连接字符串不需要改。如果表已经建好了,用ALTER TABLE Employees ALTER COLUMN EmpName NVARCHAR(50) NOT NULL;修改。
5. 二次开发上手:加一个“考勤统计”模块的完整思路
假设你要在这个项目基础上加一个考勤统计功能,需求是:按月份统计每个员工的出勤天数、迟到次数、请假次数。我一般会按下面这个顺序改,不容易乱。
第一步:加数据库表。在 InitDatabase.sql 里追加:
CREATE TABLE Attendance ( AttID INT IDENTITY(1,1) PRIMARY KEY, EmpID INT FOREIGN KEY REFERENCES Employees(EmpID), AttDate DATE NOT NULL, Status NVARCHAR(10) -- 正常/迟到/请假/缺勤 );第二步:加实体类。在 Models 文件夹下新建Attendance.cs,字段和表列对应即可,不需要写方法。
第三步:加 DAL 类。新建AttendanceDAL.cs,核心是一个统计查询:
public DataTable GetMonthlyStats(string month) // month 格式 "2024-01" { string sql = @" SELECT e.EmpName, SUM(CASE WHEN a.Status = '正常' THEN 1 ELSE 0 END) AS 出勤天数, SUM(CASE WHEN a.Status = '迟到' THEN 1 ELSE 0 END) AS 迟到次数, SUM(CASE WHEN a.Status = '请假' THEN 1 ELSE 0 END) AS 请假次数 FROM Employees e LEFT JOIN Attendance a ON e.EmpID = a.EmpID AND FORMAT(a.AttDate, 'yyyy-MM') = @month GROUP BY e.EmpName ORDER BY e.EmpName"; SqlParameter[] paras = { new SqlParameter("@month", month) }; return SqlHelper.ExecuteQuery(sql, paras); }这里用FORMAT(a.AttDate, 'yyyy-MM')把日期转成月份字符串来匹配。CASE WHEN做条件计数,比写多个子查询简洁。LEFT JOIN保证没有考勤记录的员工也会出现在结果里,显示为 0。
第四步:加窗体。在 Forms 下新建AttendanceForm.cs,放一个 DateTimePicker 选月份、一个 DataGridView 显示结果、一个查询按钮。按钮事件里调用AttendanceDAL.GetMonthlyStats并绑定。
第五步:挂到主界面。在 MainForm 的导航栏加一个按钮,点击时new AttendanceForm().ShowDialog()。
改完之后,验证方法很简单:先在 Attendance 表里手动 INSERT 几条不同状态的数据,然后跑程序选对应月份,看统计数字对不对。重点检查“请假”和“迟到”是否被正确分类,以及没有考勤记录的员工是否显示为 0 而不是消失。
从那以后我每次给课设加新模块,都强制自己先写 SQL 验证数据,再写 DAL,最后画界面。顺序反了的话,界面调半天发现是 SQL 写错了,时间全浪费在排查上。希望这套思路能帮到你,源码包里的注释比我这里写的还细,跑通之后对着改就行。
本文还有配套的精品资源,点击获取