资讯动态

C#停车场管理系统课设全攻略:数据库设计、连接与计费逻辑

发布时间:2026/10/14 19:45:50 来源:尧图企业网站定制
简介基于C#的停车场管理系统课程设计完整包面向计算机相关专业学生及需要完成同类型项目的开发者涵盖管理员登录、车辆进场登记、出场计费、大小车分区收费等核心业务可直接用于学习或二次开发。压缩包共45个文件整体约2.19MB以C#源码cs、SQL Server数据库文件mdf/ldf、Word版设计报告docx为主并包含sln工程、resx界面资源与可执行程序便于还原运行环境和查看实现细节。系统支持管理员登录进场时填写车牌、选择车辆类型与停放类型即可确认入场出场时从列表选取车辆可选用优惠券并自动计费确认后放行所有进出场记录保存至后台数据库长期停放车辆仅记录进出情况、不收取长租费用大小车分开计费使收费更透明。已有126人学习/下载适合作为课程设计参考、数据库大作业或毕业设计预研资料源码、数据库与报告相互配套可快速理解整体架构与关键代码。1. 基于C#的停车场管理系统一份课设源码包怎么跑、怎么改、怎么交答辩前两天才从压缩包里解压出这份“课程设计-基于C#的停车场管理系统(源码数据库报告).zip”的人大概率正对着 Visual Studio 里的报错发呆。这个方向几乎是软件工程和计算机专业课程设计里出现频率最高的题目之一C# WinForms 做界面SQL Server 做存储中间用 ADO.NET 或者封装好的数据库工具类完成增删改查。它的价值在于能把 C# 语法、数据库表设计、UI 事件绑定、简单报表串成一条完整的项目线适合零基础想完成课设的同学也适合想快速二开练手的初级开发。但这类源码包有个通病报告写得像正规软件工程代码却是拼 SQL 字符串数据库文件在原作者机器上跑得好好的换到你机器上就连接失败车位、计费、会员的逻辑各有各的写法。所以这篇不做科普直接按“建库 → 连库 → 业务 → 排错 → 验收”的顺序把从拿到压缩包到能演示答辩的全过程讲清楚。2. 先建库再写代码停车场管理系统的六张表与 SQL Server 建库脚本2.1 六张表的设计分工车辆、车位、记录、计费规则、月卡与用户拿到源码包后别急着点开 .exe先把数据库脚本跑通。停车场管理系统无论界面做成什么样底层都逃不开六张表车辆档案、车位、进出场记录、计费规则、月卡、系统用户。这个划分不是拍脑袋而是按照业务流程推出来的——车要进场得先知道有没有空车位车要出场得算停了多久、按什么规则收费月卡用户要能识别出来不能按临时车收。表名作用关键字段VehicleInfo车辆档案LicensePlate 车牌、CarType 车型、OwnerName 车主Carport车位信息CarportNo 车位号、Area 区域、Status 状态ParkingRecord进出场记录InTime 入场、OutTime 出场、Fee 金额PriceRule计费规则UnitPrice 单价、FreeMinutes 免费分钟、DailyCeiling 日封顶Member月卡用户MemberCardNo 卡号、BeginDate、EndDate 有效期SysUser登录用户UserName、Password我的建议是让 ParkingRecord 表站 C 位它既记录车辆从进场到出场的完整生命周期也是报表统计的数据源。其他五张表都是它的“参照物”。VehicleInfo 管车辆主数据Carport 管车位状态PriceRule 管钱怎么算Member 管特殊车辆SysUser 管谁能登录系统。这样的结构做 ER 图和数据库设计说明时也容易画清楚。2.2 建库建表 SQL主键、外键约束与金额字段为什么必须用 decimal建表脚本是整套系统的地基。以下脚本按 SQL Server 语法写可以直接在 SSMS 里执行CREATE DATABASE ParkingDB; GO USE ParkingDB; GO CREATE TABLE Carport ( CarportNo varchar(10) PRIMARY KEY, Area varchar(10) NOT NULL, Status int NOT NULL DEFAULT 0 ); CREATE TABLE PriceRule ( RuleId int IDENTITY(1,1) PRIMARY KEY, RuleName nvarchar(20) NOT NULL, UnitPrice decimal(10,2) NOT NULL, FreeMinutes int NOT NULL DEFAULT 0, DailyCeiling decimal(10,2) NULL, IsActive bit NOT NULL DEFAULT 1 ); CREATE TABLE VehicleInfo ( CarId int IDENTITY(1,1) PRIMARY KEY, LicensePlate nvarchar(20) NOT NULL UNIQUE, CarType int NOT NULL DEFAULT 1, OwnerName nvarchar(30) NULL, Phone varchar(11) NULL, MemberCardNo varchar(20) NULL ); CREATE TABLE ParkingRecord ( RecordId int IDENTITY(1,1) PRIMARY KEY, LicensePlate nvarchar(20) NOT NULL, CarportNo varchar(10) NULL, InTime datetime NOT NULL, OutTime datetime NULL, TotalMinutes int NULL, Fee decimal(10,2) NULL, RuleId int NULL ); CREATE TABLE Member ( MemberCardNo varchar(20) PRIMARY KEY, LicensePlate nvarchar(20) NOT NULL, BeginDate datetime NOT NULL, EndDate datetime NOT NULL ); CREATE TABLE SysUser ( UserId int IDENTITY(1,1) PRIMARY KEY, UserName nvarchar(20) NOT NULL, Password nvarchar(50) NOT NULL );这段脚本里有两个细节值得说。第一货币字段和费率字段全部用decimal(10,2)千万别用 float 或 double。0.1 0.2在二进制浮点里算出来是0.30000000000000004这在计费系统里是必须杜绝的。第二车牌字段用nvarchar(20)而不是varchar(20)因为国内车牌里有汉字省份简称nvarchar在存储 Unicode 字符时不会出现乱码隐患。关于表的关联我的常见做法是给 ParkingRecord 的 RuleId 建外键指向 PriceRule而 CarportNo 不建外键——历史记录里的车位可能已删除外键约束反而会在做数据清理时添乱。2.3 初始化数据车位和计费规则直接决定演示效果空表是跑不起来程序的车库得有车位系统得知道怎么收费。车位我一般用循环语句一次性插 20 个A 区 10 个、B 区 10 个DECLARE i int 1; WHILE i 10 BEGIN INSERT INTO Carport (CarportNo, Area, Status) VALUES (A RIGHT(00 CAST(i AS varchar(2)), 2), A区, 0); SET i i 1; END;这段脚本的逻辑是CAST(i AS varchar(2))把数字转成字符串00 ...在前面补零再用RIGHT(..., 2)截取后两位这样1会变成01车位号就是A01。十位以内能正常显示超过十位也不会串格式。把 i 从 1 改到 10 就是 A 区改成 B 区同理再跑一遍即可。初始Status设为 0表示全部空闲。计费规则也要预置一条否则出场结算时查不到单价。我通常会放一条符合大多数停车场习惯的规则前 15 分钟免费超过 15 分钟后按小时计费每小时 5 元单日封顶 30 元。INSERT INTO PriceRule (RuleName, UnitPrice, FreeMinutes, DailyCeiling) VALUES (临时车-小车, 5.00, 15, 30);这里把“免费 15 分钟”设计成独立字段而不是写死在 C# 代码里是个很重要的取舍。课程设计答辩时如果老师问“免费时长怎么改”你可以直接说改表和界面里的配置项而不是改代码重新编译。这是一眼就能看出的设计意识。注意数据库脚本文件执行时检查一下文件编码。用 SSMS 打开 .sql 文件后直接执行通常没问题但如果用记事本编辑过另存时选择“带 BOM 的 UTF-8”或 ANSI 编码避免脚本里注释和字符串常量变乱码。3. 把数据库接进 C# 界面连接字符串、SqlHelper 封装、DataGridView 绑定3.1 连接字符串放在 App.config实例名与登录方式是第一个分叉口数据库建好只是第一步。源码包里最常见的翻车点就是连接字符串写的是原作者机器上的 SQL Server 实例名到你这边根本对不上。连接字符串应该统一放在 App.config 的 connectionStrings 节点里而不是散落在各个窗体的代码中?xml version1.0 encodingutf-8? configuration connectionStrings add nameParkingDb connectionStringServer.;DatabaseParkingDB;User Idsa;Password123456;MultipleActiveResultSetstrue providerNameSystem.Data.SqlClient / /connectionStrings /configurationServer.表示连接本机默认 SQL Server 实例。如果装的是 SQL Server Express实例名通常是SQLEXPRESS这时要写成Server.\SQLEXPRESS。这是课设里著名的“玄学问题”——明明数据库就在自己电脑上程序就是连不上九成是实例名写错了。连接字符串里还加了MultipleActiveResultSetstrue它的作用是允许同一个连接对象上同时存在多个活动结果集后续在 DataReader 读取过程中再执行其他查询时不会报错。至于登录方式强烈建议不要用 Windows 身份验证做课设演示因为换到答辩机器上账户一换就崩。改成 SQL Server 身份验证固定用 sa 账号密码写在配置里虽然不算是工程实践的标准姿势但课程设计答辩场景下这是最省事的方案。3.2 用 SqlHelper 封装数据库增删改查参数化是底线整个停车场管理系统的所有窗体数据操作不外乎四种查询、插入、更新、删除。把它们收敛到一个静态工具类里后续所有界面都调用它这是最直观的 C# 数据库增删改查封装方式using System.Data; using System.Data.SqlClient; using System.Configuration; public static class SqlHelper { private static readonly string ConnStr ConfigurationManager.ConnectionStrings[ParkingDb].ConnectionString; // 查询返回 DataTable适合绑定 DataGridView、下拉框 public static DataTable Query(string sql, params SqlParameter[] ps) { using (SqlConnection conn new SqlConnection(ConnStr)) using (SqlDataAdapter da new SqlDataAdapter(sql, conn)) { da.SelectCommand.Parameters.AddRange(ps); DataTable dt new DataTable(); da.Fill(dt); return dt; } } // 增删改返回受影响行数 public static int Execute(string sql, params SqlParameter[] ps) { using (SqlConnection conn new SqlConnection(ConnStr)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddRange(ps); return cmd.ExecuteNonQuery(); } } } // 查询单个值例如统计在场车辆数 public static object ExecuteScalar(string sql, params SqlParameter[] ps) { using (SqlConnection conn new SqlConnection(ConnStr)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddRange(ps); return cmd.ExecuteScalar(); } } } }这段代码把连接对象的创建、打开、释放全部用using包住写完这一处十个窗体都不用再自己管连接生命周期。逻辑说明里有两点值得注意一是Query方法用SqlDataAdapter.Fill()它会自己打开连接、填充数据、再关闭连接不需要手动conn.Open()二是所有方法都支持params SqlParameter[]这是参数化查询的入口。为什么要强调参数化很多课设代码喜欢写SELECT * FROM ParkingRecord WHERE LicensePlate txtLicense.Text 一旦车牌里出现单引号这类特殊字符SQL 语句直接语法错误严重时还能被构造出额外条件。用SqlParameter传参相当于把用户输入和数据语句隔离开这是数据库操作里不允许妥协的底线。3.3 查询结果绑定 DataGridView别手动去 new 连接界面层最常用的操作是把车位列表或进出场记录塞进 DataGridView。用上面封装好的Query方法代码可以精简到很短private void FrmMain_Load(object sender, EventArgs e) { string sql SELECT CarportNo AS 车位, Area AS 区域, CASE Status WHEN 0 THEN 空闲 WHEN 1 THEN 占用 END AS 状态 FROM Carport ORDER BY CarportNo; dataGridView1.DataSource SqlHelper.Query(sql); }这里直接在 SQL 里用别名把列头改成中文配合CASE语句把整型状态翻译成“空闲/占用”界面代码里就不需要再写一长串列名映射。这是课设中能明显提升完成度的习惯。dataGridView1.DataSource一旦被赋值为 DataTableDataGridView 会自动按列渲染不用手动设置每个列的DataPropertyName。有个容易被忽略的点DataSource赋的是一个新 DataTable 对象的引用。如果你中途执行了dataGridView1.DataSource null或者对同一个 DataTable 对象做修改后再赋值界面可能不刷新。正确做法是重新查询一次再重新赋值。另外在 DataGridView 上做增删改按钮时千万不要在按钮事件里重新写一套SqlConnection访问数据库直接调SqlHelper.Execute这样代码风格统一排查问题时也只用盯一个工具类。4. 进出场与计费停车场管理系统里最容易翻车的一段业务逻辑4.1 进场登记先查一次在场状态再写记录进场业务的完整流程是判断车牌是否已经在场→ 选择一个空闲车位 → 插入一条入场记录 → 把车位状态改为占用。看似三步省略任何一步都会出 bug。尤其是“判断是否已在场”很多初版代码直接跳过导致同一辆车连续进场两次出场时计费记录一团乱。private void BtnIn_Click(object sender, EventArgs e) { string lp txtLicensePlate.Text.Trim(); if (lp ) { MessageBox.Show(请输入车牌号); return; } string countSql SELECT COUNT(*) FROM ParkingRecord WHERE LicensePlatelp AND OutTime IS NULL; int online Convert.ToInt32( SqlHelper.ExecuteScalar(countSql, new SqlParameter(lp, lp))); if (online 0) { MessageBox.Show(该车牌已在场内不能重复进场); return; } string cp cboCarport.SelectedItem.ToString(); string insertSql INSERT INTO ParkingRecord(LicensePlate, CarportNo, InTime) VALUES(lp, cp, now); SqlHelper.Execute(insertSql, new SqlParameter(lp, lp), new SqlParameter(cp, cp), new SqlParameter(now, DateTime.Now)); string updateSql UPDATE Carport SET Status1 WHERE CarportNocp; SqlHelper.Execute(updateSql, new SqlParameter(cp, cp)); MessageBox.Show(进场成功); RefreshCarportList(); }这个逻辑里核心判断条件是OutTime IS NULL一个正在场内的车它的出场时间必然是空。只要有一条这样的记录存在就说明车辆还没走。这是一个非常典型的业务状态判断方法熟练工程师一眼就知道但在课设源码里经常被写成WHERE OutTime 对 datetime 字段用空字符串去比较结果永远匹配不上这个坑要特别留意。4.2 出场计费免费时长、向上取整与封顶规则怎么定出场业务的核心是算钱。按第 2 章预置的规则15 分钟内免费超过后按整小时向上取整每小时 5 元单日封顶 30 元。先读入场时间再算当前时间和入场时间差DataTable dt SqlHelper.Query( SELECT * FROM ParkingRecord WHERE LicensePlatelp AND OutTime IS NULL, new SqlParameter(lp, lp)); if (dt.Rows.Count 0) { MessageBox.Show(该车辆不在场内或已经出场); return; } DataRow row dt.Rows[0]; DateTime inTime Convert.ToDateTime(row[InTime]); double totalMinutes (DateTime.Now - inTime).TotalMinutes; decimal fee 0; if (totalMinutes 15) { fee 0; // 免费时长 } else { int hours (int)Math.Ceiling(totalMinutes / 60.0); fee hours * 5.0m; // 不满一小时按一小时计 } if (fee 30.0m) { fee 30.0m; // 单日封顶 }这里值得展开说两点。第一Math.Ceiling(totalMinutes / 60.0)是向上取整停 1 小时 1 分钟会按 2 小时收费这是国内停车场最常见的取整方式和数据库里的“按分钟计费再四舍五入”是两种完全不同的业务规则写之前先和需求对齐。第二单日封顶的“日”怎么定义有的停车场按自然日00:00 到 24:00有的按入场时间起 24 小时为一个计费周期。课设里通常按自然日处理但如果车辆跨天停放比如 23:00 进场、次日 02:00 出场用一个简单的全局封顶会少收钱这个边界最好在报告里明确写清楚答辩时才不会被问住。4.3 用事务把“车位释放 写计费记录”绑成一步出场操作实际要执行两条 SQL更新停车场记录写入出场时间、时长、金额同时把车位状态改回 0空闲。很多课设代码就是在按钮事件里顺序写两个SqlHelper.Execute调用看起来没毛病但万一第二条失败车已出场车位还显示占用下一个车就停不进去。这两个操作必须放进同一个数据库事务using (SqlConnection conn new SqlConnection(ConnStr)) { conn.Open(); SqlTransaction tran conn.BeginTransaction(); try { SqlCommand cmdUpdateRecord new SqlCommand( UPDATE ParkingRecord SET OutTimeout, TotalMinutesmin, Feefee WHERE RecordIdid, conn, tran); cmdUpdateRecord.Parameters.AddWithValue(out, DateTime.Now); cmdUpdateRecord.Parameters.AddWithValue(min, (int)totalMinutes); cmdUpdateRecord.Parameters.AddWithValue(fee, fee); cmdUpdateRecord.Parameters.AddWithValue(id, row[RecordId]); cmdUpdateRecord.ExecuteNonQuery(); SqlCommand cmdUpdateCarport new SqlCommand( UPDATE Carport SET Status0 WHERE CarportNocp, conn, tran); cmdUpdateCarport.Parameters.AddWithValue(cp, row[CarportNo]); cmdUpdateCarport.ExecuteNonQuery(); tran.Commit(); } catch { tran.Rollback(); MessageBox.Show(出场结算失败数据已回滚); } }事务的核心特征是SqlTransaction对象被同时传给两个 SqlCommand 的构造函数。这意味着两条 update 语句共享同一个连接和同一个事务上下文要么一起提交要么一起回滚。课程设计里能把这一步写出来并且答辩时讲清楚“为什么需要事务”往往是能拉开分数差距的地方。提示如果后续想让计费规则表真正参与出场结算不要在这里写死 5 元和 30 元而是查PriceRule表里IsActive1的那条规则用字段值参与计算。这样换费率时不用改代码和第 2 章的初始化设计保持一致。5. 停车场管理系统避坑手册5 个让答辩演示当场失败的高频问题5.1 报 A network-related error先检查的不该是代码现象程序一运行连接数据库的地方直接弹出 “A network-related or instance-specific error occurred while establishing a connection to SQL Server”。很多人第一反应是代码写错了其实这个报错九成指向环境。原因依次排查SQL Server 服务没启动、连接字符串实例名写错、SQL Server 身份验证没开启。解决先在命令行或 PowerShell 里确认服务状态Get-Service -Name MSSQLSERVER,MSSQL$SQLEXPRESS能看到Running状态说明服务没问题。接着用 SSMS 测试登录服务器名填.或.\SQLEXPRESS登录名填 sa密码填配置里的那个。能连上就说明连接字符串没问题连不上就要去 SQL Server 配置管理器里启用 TCP/IP并在服务里重启一次 SQL Server 服务。这个流程要背下来答辩前一天再查一遍大概率能救一场演示。5.2 mdf 附加失败日志文件缺失不是绝症现象源码包里的数据库文件拷贝到新电脑后SSMS 附加时报“找不到日志文件”或“版本不兼容”。原因打包的人只发了 .mdf没发 .ldf或者数据库文件来自更高版本的 SQL Server低版本实例无法加载。解决如果只是缺日志文件可以尝试用重建日志的方式附加CREATE DATABASE ParkingDB ON (FILENAME ND:\ParkingDB.mdf) FOR ATTACH_REBUILD_LOG;FOR ATTACH_REBUILD_LOG会让 SQL Server 根据数据文件重新生成缺失的日志文件。前提是数据库之前是正常关闭过的。如果报版本不兼容说明本机 SQL Server 版本太低去装一个对应或更高版本的实例再附加。另外数据库文件路径里尽量不要出现中文目录这属于 SQL Server 的老毛病能绕就绕。5.3 界面上中文变问号编码问题在脚本阶段就埋下了现象程序界面里按钮上的中文正常但从数据库读出来的车牌、车主姓名显示成???或乱码。原因通常在两个地方建表时字符类型用了varchar而不是nvarchar或者插入数据时直接在 SQL 语句里拼了中文常量而脚本文件本身编码不对。解决如果字段已经建成varchar在表设计里统一改成nvarchar。代码侧坚决不用字符串拼接插中文全部走SqlParameter这样传入的值会被 ADO.NET 按 Unicode 处理。数据库列已经存了乱码先把表结构改对再用 SQL 更新对应行为正确数据。另外要记住 SqlClient 连接字符串里没有 Charset 这个概念那是 MySQL 驱动才有的参数别在 SQL Server 的配置里乱加。5.4 点查询界面假死C# 线程被慢 SQL 卡住的解法现象点击“查询记录”按钮后整个窗体变成白屏标题栏出现“未响应”过几秒又自己恢复。原因查询操作直接跑在 UI 线程上数据量大时 UI 消息循环被阻塞用户的所有点击都被卡住。这在单机课设里看似能忍但答辩演示时特别掉价。解决用async/await把查询挪到后台线程拿结果后再回到 UI 线程绑定控件private async void BtnQuery_Click(object sender, EventArgs e) { string sql SELECT * FROM ParkingRecord ORDER BY InTime DESC; DataTable dt await Task.Run(() SqlHelper.Query(sql)); dataGridView1.DataSource dt; }await之后的代码默认会回到 UI 线程所以不需要额外写委托。这个方法只改一小段代码就能明显提升程序的响应质感。5.5 报告和代码对不上补报告比补代码更花时间现象源码能跑但报告里的 ER 图、流程图和实际表结构、界面按钮对不上。老师翻报告的频率比你想的高特别是表名字段名对不上时会被直接质疑代码是不是抄的。原因包里的报告是别人的你只改了界面没改文档。解决按这个顺序快速对齐——先打开第一章需求分析对照当前界面功能删掉多余描述再打开数据库设计对照 ParkingRecord、Carport、PriceRule 等实际表名重画 ER 图最后把主要业务流程进场、出场、月卡续费各画一张流程图和代码里的分支保持一致。报告的截图必须现场重新截用别人的截图一是分辨率不对二是界面都不一样一眼假。这个“对齐”过程通常比改代码还耗时间所以我会在拿到压缩包的第一时间先过报告而不是先调界面。6. 交付前做三组验证再顺手加一个备份按钮6.1 三组边界数据把计费规则测到无死角演示前至少手工走一遍以下场景把结果记下来测试场景操作步骤预期结果重复进场车 A 进场后不结算再次输入车 A 进场提示“已在场内”不允许重复入场免费时长边界车 A 进场后停 14 分钟结算金额为 0超时取整车 A 停 1 小时 1 分钟结算按 2 小时收费金额 10 元封顶规则构造超长停车时间或临时改小封顶值金额不超过封顶值 30 元最后一行的封顶测试如果不方便真的等一天可以把 PriceRule 表里的DailyCeiling临时改成 1 元停 20 分钟就能验证封顶逻辑。测完记得改回来。这个过程能帮你提前发现计算逻辑里Math.Ceiling用错、封顶比较符号反了之类的问题。6.2 备份数据库按钮与导出 CSV两个答辩加分的小功能课程设计做到“能跑”是及格做到“有工程意识”才是优秀。我给自己的课设加过一个数据库备份按钮核心代码就一行string path D:\backup\ParkingDB_ DateTime.Now.ToString(yyyyMMdd_HHmmss) .bak; string sql BACKUP DATABASE ParkingDB TO DISKpath; SqlHelper.Execute(sql, new SqlParameter(path, path)); MessageBox.Show(备份完成 path);执行前确保 D 盘 backup 目录存在这是 SQL Server 写备份文件的硬性要求。这个功能在答辩时被老师问到的概率很高因为你相当于把“运维意识”放进了课程设计里。导出报表是另一个性价比高的功能。用 CSV 格式导出进出场记录不需要引用 Office COM 组件跨机器演示也不会翻车DataTable dt SqlHelper.Query(SELECT LicensePlate, InTime, OutTime, TotalMinutes, Fee FROM ParkingRecord); StringBuilder sb new StringBuilder(); sb.AppendLine(车牌,入场时间,出场时间,时长分钟,金额); foreach (DataRow r in dt.Rows) { sb.AppendLine(${r[LicensePlate]},{r[InTime]},{r[OutTime]},{r[TotalMinutes]},{r[Fee]}); } File.WriteAllText(D:\export.csv, sb.ToString(), Encoding.UTF8);这里用Encoding.UTF8而不是默认编码是因为带 BOM 的 UTF-8 文件在 Excel 里打开才不会乱码。车牌号里一般不含逗号直接按逗号拼接即可够用且稳。我拿到任何一份课设源码包第一件事永远是重跑数据库脚本、改连接字符串、用一条最小查询确认通路然后才去看界面和业务代码。这个顺序帮我避免过太多次“代码看着没问题一运行全崩”的尴尬。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑