news 2026/10/19 6:11:13

C# WinForm停车场收费系统实战:数据库附加、计费逻辑与二次开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C# WinForm停车场收费系统实战:数据库附加、计费逻辑与二次开发

简介:这是一套面向C#初学者与WinForm桌面开发入门者的实践型源码项目,聚焦停车场收费管理这一典型业务场景,帮助学习者掌握数据库驱动的GUI应用开发全流程。资源包含95个文件,主体为37个C#业务逻辑与界面代码文件(.cs)、13个本地化资源文件(.resx)、13个编译资源(.resources)、1个SQL Server数据库脚本(.sql)及1个完整数据库文件(.mdf),辅以解决方案(.sln)、项目配置(.csproj)和运行截图等,整体压缩包仅1.32MB,轻量易部署。已有1323人下载学习,适合通过真实模块(如车辆入场登记、按停留时间自动计费、出库结算、收费记录查询及管理员权限配置)理解数据绑定、事件驱动、SQL Server增删改查及多窗体协作等核心技能。源码结构清晰,含完整可运行exe程序与附加数据库脚本,开箱即用,是巩固C#语法、WinForm控件使用与三层逻辑分离思想的优质练手项目。

1. 这不是个“练手小Demo”,而是一套能直接插进小商场地库跑通的WinForm收费系统

上周帮某高校实验室调试一个毕业设计项目,学生拿来的就是这个「C#停车场收费系统」源码包——没文档、没README、只有几个png截图和一个.sln。我本以为又是那种“窗体拖一拖、按钮点一点、数据库连一连”的教学玩具,结果双击 Parking.sln 启动后,真把模拟车辆从进场、计时、出库、打印小票全走了一遍,连SQL Server本地实例自动附加.mdf/.ldf的逻辑都写好了。它不炫技,但每一步都踩在真实业务的痛点上:比如车型分A/B/C三类对应不同单价、临时车按小时阶梯计费(前30分钟免费,之后每15分钟1元)、月租车自动识别免收当日费用、甚至支持手动补录离场时间来处理断电重启场景。如果你正卡在“学完WinForm控件却不知道怎么串成完整流程”,或者想拿个有真实数据流、有状态管理、有异常兜底的C#桌面项目练手改写,这个压缩包里藏着的,是比教科书更硬核的落地经验。


2. 项目结构与技术栈:从 Parking.sln 到 Parking.mdf 的完整链路

2.1 解压即见真章:文件清单与职责划分

拿到停车场收费系统.zip后,解压目录结构非常干净,没有冗余文件:

Parking/ ├── Parking.sln # Visual Studio 解决方案入口 ├── Parking/ # 主项目文件夹(WinForm项目) │ ├── Properties/ │ ├── obj/ │ ├── bin/ │ ├── Parking.csproj # C#项目定义,TargetFramework="net472" │ ├── FormMain.cs # 主窗体:含菜单栏、状态栏、Tab页容器 │ ├── FrmAddCar.cs # 添加车辆窗体(进场登记) │ ├── FrmOutCar.cs # 出库结算窗体(核心计费逻辑在此) │ ├── FrmQuery.cs # 查询统计窗体(支持车牌模糊+时间范围) │ ├── FrmConfig.cs # 管理配置窗体(收费标准、车位总数等) │ ├── DBHelper.cs # 数据库操作封装类(关键!用SqlClient直连) │ ├── CarInfo.cs # 车辆实体类(含车牌、车型、入场时间等属性) │ └── FeeCalculator.cs # 收费计算器(独立类,解耦业务与UI) ├── Parking.mdf # SQL Server 主数据库文件(含表结构+初始数据) ├── Parking_log.ldf # 日志文件 ├── 附加脚本.sql # 建库建表SQL(CREATE DATABASE + CREATE TABLE) └── *.png # UI界面截图(非运行必需,仅参考布局)

提示:.mdf和.ldf是SQL Server Express LocalDB或SQL Server Desktop Engine的本地数据库文件,无需单独安装SQL Server服务端——VS自带LocalDB即可挂载。这是很多初学者卡住的第一步,后面会专门讲怎么让它们“活”起来。

2.2 技术选型为什么是 WinForm + SQL Server?

这不是历史包袱,而是精准匹配:

  • WinForm:轻量、启动快、无依赖、适合内网单机部署。停车场管理终端常是老旧工控机,WinForm的.NET Framework 4.7.2兼容性远胜WPF或MAUI;
  • SQL Server LocalDB:比SQLite强在事务支持和并发控制(多窗口同时查/改记录不锁死),比完整SQL Server弱在无需服务进程——.mdf双击就能附加,开发零配置;
  • C# 语言特性:项目中大量使用using (var conn = new SqlConnection(...))确保连接释放;DataTable+DataAdapter做离线数据缓存(查询页加载快);BackgroundWorker处理长耗时操作(如导出Excel)避免UI假死。

这种组合不是“过时”,而是在资源受限场景下对稳定性和可维护性的务实选择。你不会在这里看到Entity Framework的花哨映射,但每一行SqlCommand都带着参数化防注入的注释。

2.3 数据库设计:三张表撑起全部业务

打开附加脚本.sql,核心建表语句如下(已精简注释):

-- 车辆基本信息表(车牌为主键,唯一约束) CREATE TABLE CarInfo ( PlateNumber NVARCHAR(10) PRIMARY KEY, -- 车牌号,如"粤B12345" CarType INT NOT NULL, -- 车型:1=小型车, 2=中型车, 3=大型车 InTime DATETIME NOT NULL, -- 入场时间,精确到秒 OutTime DATETIME NULL, -- 出场时间,NULL表示未出场 IsMonthly BIT DEFAULT 0 -- 是否月租车(1=是,0=否) ); -- 收费标准配置表(单条记录,供FrmConfig修改) CREATE TABLE FeeConfig ( Id INT PRIMARY KEY IDENTITY(1,1), SmallCarFee DECIMAL(6,2) DEFAULT 2.00, -- 小型车每小时费用 MediumCarFee DECIMAL(6,2) DEFAULT 3.00, -- 中型车每小时费用 LargeCarFee DECIMAL(6,2) DEFAULT 5.00, -- 大型车每小时费用 FreeMinutes INT DEFAULT 30, -- 免费时长(分钟) DailyCap DECIMAL(6,2) DEFAULT 50.00 -- 日封顶金额 ); -- 停车记录流水表(每次出场生成一条,用于对账) CREATE TABLE ParkingLog ( Id INT PRIMARY KEY IDENTITY(1,1), PlateNumber NVARCHAR(10) NOT NULL, InTime DATETIME NOT NULL, OutTime DATETIME NOT NULL, DurationMinutes INT NOT NULL, FeeAmount DECIMAL(6,2) NOT NULL, PayStatus TINYINT DEFAULT 0, -- 0=未支付, 1=现金, 2=扫码, 3=月结 CreateTime DATETIME DEFAULT GETDATE() );

注意:CarInfo表用PlateNumber当主键而非自增ID,是因为业务上车牌号天然唯一且需高频查询;OutTime允许为NULL,直观表达“当前仍在场内”状态;ParkingLog是只增表,不出现在UI编辑中,所有出库操作先更新CarInfo.OutTime,再插入ParkingLog一条新记录——这是典型的状态分离设计,避免单表字段语义混乱。

2.4 核心模块通信机制:事件驱动 + 窗体间传参

WinForm项目最怕“全局变量污染”。本项目采用两种安全传参方式:

  1. 构造函数传参(推荐用于初始化)
    FrmOutCar打开时,必须传入待结算的车牌号:

    // 在 FrmAddCar 或主窗体中调用 var outFrm = new FrmOutCar("粤B12345"); outFrm.ShowDialog();
  2. 自定义事件回调(推荐用于结果回传)
    FrmOutCar结算完成后,触发OnFeeCalculated事件通知主窗体刷新列表:

    // FrmOutCar.cs 内部 public event Action<string, decimal> OnFeeCalculated; private void btnConfirm_Click(object sender, EventArgs e) { decimal fee = calculator.Calculate(plate, inTime, outTime); OnFeeCalculated?.Invoke(plate, fee); // 通知主窗体 this.Close(); } // FormMain.cs 中订阅 var outFrm = new FrmOutCar(plate); outFrm.OnFeeCalculated += (p, f) => { RefreshCarList(); // 刷新主列表 ShowToast($"车牌{p}结算完成,费用{f:C}"); }; outFrm.ShowDialog();

这种模式让窗体职责清晰:FrmOutCar只管计算和确认,不碰数据库;FormMain负责数据加载和界面联动。改需求时,只需动一个窗体,不牵一发而动全身。


3. 运行前必做三件事:数据库附加、连接字符串修正、权限检查

3.1 让 Parking.mdf “活”起来:LocalDB附加实战

现象:双击Parking.sln→ 编译通过 → 运行报错System.Data.SqlClient.SqlException: 无法打开登录所请求的数据库 "Parking"。

原因:Visual Studio 默认 LocalDB 实例名是(localdb)\mssqllocaldb,但项目中连接字符串可能指向.\SQLEXPRESS或其他名称。

解决步骤(以 VS 2019/2022 为例):

  1. 确认 LocalDB 实例是否存在
    打开命令提示符(管理员),执行:

    sqllocaldb info

    若输出包含mssqllocaldb,说明已就绪;若无,运行sqllocaldb create mssqllocaldb创建。

  2. 附加数据库文件
    在 VS 中:

    • 视图 → SQL Server 对象资源管理器
    • 右键(localdb)\mssqllocaldb→ “附加数据库”
    • 点击“添加”,选择解压目录下的Parking.mdf
    • 确认路径中.ldf文件自动关联(若未出现,手动添加Parking_log.ldf)
    • 点击确定,数据库Parking即出现在资源管理器中。
  3. 验证附加成功
    展开Parking→ 表,应能看到CarInfo、FeeConfig、ParkingLog三张表,右键“查看顶部200行”可确认有测试数据(如车牌“粤A00001”已存在)。

提示:如果提示“数据库正在使用”,说明之前有程序占用了.mdf。关闭所有 VS 实例,任务管理器结束sqlservr.exe进程,再重试。

3.2 修改连接字符串:从 App.config 到实际环境

项目连接字符串藏在Parking/App.config的<connectionStrings>节点中:

<add name="ParkingDB" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Parking;Integrated Security=True" providerName="System.Data.SqlClient" />

你需要根据上一步附加的实例名修改Data Source:

  • 若用 LocalDB:改为Data Source=(localdb)\mssqllocaldb
  • 若用 SQL Server Express:保持.\SQLEXPRESS(需确保 SQL Server Browser 服务开启)
  • 若用远程服务器:填 IP+端口,如192.168.1.100,1433

修改后,必须重启 VS才能使配置生效(App.config 在编译时被复制到bin\Debug\Parking.exe.config,运行时读取的是后者)。

3.3 权限检查:Windows账户能否访问 LocalDB?

现象:附加数据库时报错无法打开数据库 "master",错误 948或登录失败。

原因:LocalDB 默认只允许 Windows 当前用户访问,若你用管理员账户安装 VS,但日常用标准用户登录系统,就会权限不匹配。

解决方法(二选一):

  • ✅ 推荐:用当前登录用户启动 VS(右键 VS 快捷方式 → “以管理员身份运行” → 不勾选,确保是普通用户权限)
  • ⚠️ 次选:给 LocalDB 添加登录用户(需 DBA 权限,不推荐教学环境)

验证方式:在 SQL Server 对象资源管理器中,展开(localdb)\mssqllocaldb→ 安全性 → 登录名,确认你的 Windows 用户(如DESKTOP-XXX\YourName)已存在且状态为“启用”。


4. 避坑指南:新手跑通必踩的5个坑与血泪解法

4.1 坑1:运行时报错“未能加载文件或程序集 System.Data.SqlClient”

  • 现象:启动即崩溃,错误信息含System.Data.SqlClient, Version=4.6.1.0
  • 原因:项目目标框架为 .NET Framework 4.7.2,但新版 VS 默认安装System.Data.SqlClient5.x(.NET Core 专用),版本不兼容。
  • 解决:
    1. 在解决方案资源管理器中,右键项目 → “管理 NuGet 包”
    2. 切换到“已安装”选项卡,找到System.Data.SqlClient
    3. 卸载它 → 返回“浏览”选项卡,搜索System.Data.SqlClient
    4. 选择4.8.5 版本(最后支持 .NET Framework 的稳定版)并安装
    5. 清理解决方案 → 重新生成

血泪经验:别信 NuGet 默认推荐的最高版本,.NET Framework 项目必须锁死 4.x 分支。

4.2 坑2:添加车辆时提示“违反了PRIMARY KEY约束”

  • 现象:输入车牌“粤B12345”点击保存,弹窗报错“不能将值 NULL 插入列 'PlateNumber'”
  • 原因:CarInfo表主键是PlateNumber,但代码中未给该字段赋值,或 TextBox 控件名写错导致txtPlateNumber.Text为空。
  • 排查:在FrmAddCar.cs的保存按钮事件中,加断点观察car.PlateNumber值;检查设计器中 TextBox 的Name属性是否真是txtPlateNumber(常见误写为txtPlateNum)。
  • 解决:确保CarInfo实体类构造时校验车牌非空,并在 UI 层加前端提示:
    if (string.IsNullOrWhiteSpace(txtPlateNumber.Text.Trim())) { MessageBox.Show("请输入车牌号!", "输入错误", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; }

4.3 坑3:出库结算金额始终为0

  • 现象:车辆停留2小时,应收4元,但FrmOutCar显示“费用:¥0.00”
  • 原因:FeeCalculator.Calculate()方法中,DurationMinutes计算逻辑错误。原代码用outTime.Subtract(inTime).TotalHours直接取整,导致30分钟返回0小时。
  • 定位:打开FeeCalculator.cs,找到Calculate方法,检查时间差计算段。
  • 修复:改用分钟级计算,再按阶梯规则判断:
    int totalMinutes = (int)(outTime - inTime).TotalMinutes; if (totalMinutes <= config.FreeMinutes) return 0; // 后续按每15分钟计费逻辑...

4.4 坑4:查询页面加载极慢,卡顿3秒以上

  • 现象:点击“查询”按钮,界面假死,3秒后才显示数据
  • 原因:FrmQuery.cs中LoadData()方法直接执行SELECT * FROM CarInfo,未加WHERE条件,且未用BackgroundWorker异步加载。
  • 优化:
    1. 给查询加默认时间范围(如近7天):
      SELECT * FROM CarInfo WHERE InTime >= DATEADD(day, -7, GETDATE())
    2. 将数据加载逻辑移入BackgroundWorker.DoWork事件,UI线程不阻塞。
    3. 加载中显示Loading...Label,提升用户体验。

4.5 坑5:修改收费标准后,下次启动仍用旧值

  • 现象:在FrmConfig中把小型车费用改成3元,点保存,重启程序又变回2元
  • 原因:FeeConfig表只有一条记录(Id=1),但FrmConfig的保存逻辑是INSERT INTO FeeConfig ...,导致插入第二条,而读取时SELECT TOP 1 * FROM FeeConfig随机取一条。
  • 修复:将保存逻辑改为UPDATE:
    string sql = "UPDATE FeeConfig SET SmallCarFee=@fee WHERE Id=1"; using (var cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@fee", txtSmallFee.Text); cmd.ExecuteNonQuery(); }

5. 核心业务逻辑拆解:从进场到出库的完整计费链路

5.1 进场登记:CarInfo 表的插入与状态初始化

FrmAddCar.cs是整个流程的起点。关键逻辑在btnSave_Click事件中:

private void btnSave_Click(object sender, EventArgs e) { // 1. 实体映射(严格校验) var car = new CarInfo { PlateNumber = txtPlateNumber.Text.Trim(), CarType = cmbCarType.SelectedIndex + 1, // ComboBox索引0→车型1 InTime = DateTime.Now, IsMonthly = chkMonthly.Checked }; // 2. 数据库插入(参数化防注入) string sql = @"INSERT INTO CarInfo (PlateNumber, CarType, InTime, IsMonthly) VALUES (@plate, @type, @inTime, @isMonthly)"; using (var conn = DBHelper.GetConnection()) { using (var cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@plate", car.PlateNumber); cmd.Parameters.AddWithValue("@type", car.CarType); cmd.Parameters.AddWithValue("@inTime", car.InTime); cmd.Parameters.AddWithValue("@isMonthly", car.IsMonthly ? 1 : 0); conn.Open(); int rows = cmd.ExecuteNonQuery(); if (rows > 0) { MessageBox.Show("入场登记成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); } } } }

参数说明:

  • @plate:强制Trim()去除首尾空格,避免“粤B12345 ”和“粤B12345”被当两个车牌;
  • @type:ComboBox的SelectedIndex从0开始,车型值+1对齐数据库定义(1/2/3);
  • @inTime:用DateTime.Now而非DateTime.Today,确保秒级精度,为后续计费提供依据;
  • @isMonthly:布尔转整数(1/0),SQL ServerBIT类型只接受0或1。

5.2 出库结算:FeeCalculator 的阶梯计费算法实现

计费逻辑全部封装在FeeCalculator.cs中,这是项目最值得细读的类。核心方法Calculate如下:

public decimal Calculate(string plate, DateTime inTime, DateTime outTime, FeeConfig config) { // 1. 计算总停留分钟数(向上取整到15分钟) int totalMinutes = (int)Math.Ceiling((outTime - inTime).TotalMinutes); // 2. 获取对应车型单价 decimal baseFee = config.SmallCarFee; if (carType == 2) baseFee = config.MediumCarFee; else if (carType == 3) baseFee = config.LargeCarFee; // 3. 阶梯计费:前freeMinutes免费,之后每15分钟baseFee if (totalMinutes <= config.FreeMinutes) return 0; int chargeableMinutes = totalMinutes - config.FreeMinutes; int quarterHours = (int)Math.Ceiling((double)chargeableMinutes / 15); decimal fee = quarterHours * baseFee; // 4. 日封顶(防止长期占位) if (fee > config.DailyCap) fee = config.DailyCap; return Math.Round(fee, 2); // 保留两位小数 }

关键细节:

  • Math.Ceiling确保31分钟按3个15分钟计(即45分钟),符合“不满15分钟按15分钟算”的行业惯例;
  • baseFee动态获取,支持不同车型差异化定价;
  • DailyCap是硬性封顶,避免用户故意不离场薅羊毛;
  • Math.Round(fee, 2)防止浮点误差(如1.999999999显示为2.00)。

5.3 出库确认:CarInfo 更新 + ParkingLog 插入的事务保障

FrmOutCar.cs的结算确认是原子操作,必须保证CarInfo.OutTime更新和ParkingLog插入同时成功或同时失败:

private void btnConfirm_Click(object sender, EventArgs e) { using (var conn = DBHelper.GetConnection()) { conn.Open(); using (var trans = conn.BeginTransaction()) // 开启事务 { try { // 步骤1:更新车辆出场时间 string updateSql = "UPDATE CarInfo SET OutTime=@outTime WHERE PlateNumber=@plate"; using (var cmd = new SqlCommand(updateSql, conn, trans)) { cmd.Parameters.AddWithValue("@outTime", outTime); cmd.Parameters.AddWithValue("@plate", plate); cmd.ExecuteNonQuery(); } // 步骤2:插入停车流水 string insertSql = @"INSERT INTO ParkingLog (PlateNumber, InTime, OutTime, DurationMinutes, FeeAmount, PayStatus) VALUES (@plate, @in, @out, @dur, @fee, @pay)"; using (var cmd = new SqlCommand(insertSql, conn, trans)) { cmd.Parameters.AddWithValue("@plate", plate); cmd.Parameters.AddWithValue("@in", inTime); cmd.Parameters.AddWithValue("@out", outTime); cmd.Parameters.AddWithValue("@dur", durationMinutes); cmd.Parameters.AddWithValue("@fee", feeAmount); cmd.Parameters.AddWithValue("@pay", (byte)payStatus); cmd.ExecuteNonQuery(); } trans.Commit(); // 提交事务 MessageBox.Show($"结算成功!费用:{feeAmount:C}", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); this.DialogResult = DialogResult.OK; } catch (Exception ex) { trans.Rollback(); // 回滚事务 MessageBox.Show($"结算失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } }

为什么必须用事务?
假设只执行了UPDATE CarInfo,但INSERT ParkingLog因磁盘满失败,则数据库中车辆状态为“已出场”,但无流水记录,财务对不上账。事务确保二者要么全成功,要么全不发生,数据一致性有保障。


6. 二次开发实战:给系统加上微信支付回调与导出Excel功能

6.1 微信支付对接:在出库流程中嵌入扫码支付能力

原系统只支持现金和月结,要加微信支付,核心是两步:生成支付二维码、轮询支付结果。

步骤1:扩展PayStatus枚举与数据库

修改ParkingLog.PayStatus字段类型为TINYINT(已支持),新增值:

  • 0=未支付(原值)
  • 1=现金
  • 2=微信(新增)
  • 3=月结(原值)

并在FrmOutCar.cs中,cmbPayType下拉框添加“微信支付”选项。

步骤2:生成微信预支付订单(伪代码,需对接微信API)

在btnConfirm_Click中,当选择微信支付时:

if (payStatus == 2) // 微信支付 { // 1. 调用微信统一下单接口(此处用HttpClient模拟) var wxOrder = await GenerateWxOrderAsync(plate, feeAmount); if (wxOrder.IsSuccess) { // 2. 生成二维码(用QRCoder库) var qrCode = new QRCoder.QRCodeGenerator().CreateQrCode(wxOrder.CodeUrl, QRCoder.QRCodeGenerator.ECCLevel.Q); var bitmap = new QRCoder.QRCode(qrCode).GetGraphic(20); // 3. 显示二维码窗体(FrmWxPay.cs),内置定时器每5秒查一次支付结果 var payFrm = new FrmWxPay(bitmap, wxOrder.TradeNo); if (payFrm.ShowDialog() == DialogResult.OK) { // 支付成功,更新ParkingLog.PayStatus=2 UpdatePayStatus(plate, 2, wxOrder.TradeNo); } } }

关键点:

  • GenerateWxOrderAsync需实现微信官方unifiedorder接口,传入商户号、密钥、body(车牌号)、total_fee(单位:分);
  • FrmWxPay窗体必须禁用关闭按钮(ControlBox=false),防止用户误关;
  • 支付结果轮询用Timer,调用微信orderquery接口,查到trade_state=SUCCESS即回调主窗体。
步骤3:导出Excel报表(用 ClosedXML 库)

用户常需导出月度报表给财务。在FrmQuery.cs中添加“导出Excel”按钮:

private void btnExportExcel_Click(object sender, EventArgs e) { var dt = GetQueryResult(); // 执行查询,返回DataTable if (dt.Rows.Count == 0) return; // 使用 ClosedXML 生成Excel using (var wb = new XLWorkbook()) { var ws = wb.Worksheets.Add("停车记录"); ws.Cell(1, 1).Value = "车牌号"; ws.Cell(1, 2).Value = "入场时间"; ws.Cell(1, 3).Value = "出场时间"; ws.Cell(1, 4).Value = "停留分钟"; ws.Cell(1, 5).Value = "费用"; for (int i = 0; i < dt.Rows.Count; i++) { ws.Cell(i + 2, 1).Value = dt.Rows[i]["PlateNumber"].ToString(); ws.Cell(i + 2, 2).Value = Convert.ToDateTime(dt.Rows[i]["InTime"]); ws.Cell(i + 2, 3).Value = Convert.ToDateTime(dt.Rows[i]["OutTime"]); ws.Cell(i + 2, 4).Value = Convert.ToInt32(dt.Rows[i]["DurationMinutes"]); ws.Cell(i + 2, 5).Value = Convert.ToDecimal(dt.Rows[i]["FeeAmount"]); } // 自动列宽 ws.Columns().AdjustToContents(); // 保存文件 var saveDialog = new SaveFileDialog { Filter = "Excel文件|*.xlsx", FileName = $"停车记录_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx" }; if (saveDialog.ShowDialog() == DialogResult.OK) { wb.SaveAs(saveDialog.FileName); MessageBox.Show("导出成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } }

注意:需通过 NuGet 安装ClosedXML(v0.95.4,兼容 .NET Framework 4.7.2),不要装最新版(要求 .NET 5+)。

6.2 我的二次开发习惯:每次改完必做三件事

  1. 改数据库必写迁移脚本
    比如加微信支付字段,我不直接在 SSMS 里ALTER TABLE,而是新建V2_Add_WxPay_Column.sql:

    ALTER TABLE ParkingLog ADD WxTradeNo NVARCHAR(32) NULL; UPDATE ParkingLog SET WxTradeNo = '' WHERE WxTradeNo IS NULL;

    这样下次部署到客户机器,直接跑脚本,不担心漏改。

  2. 改UI必同步更新资源文件
    FrmOutCar.resx里存着所有按钮文字。我改了“确认结算”为“微信扫码支付”,就立刻打开资源编辑器,把btnConfirm.Text的中文和英文(若有)都更新,避免中英文混杂。

  3. 加新功能必补单元测试桩
    FeeCalculator这种纯逻辑类,我用 NUnit 写测试用例:

    [Test] public void Calculate_小型车_停留45分钟_应返回2元() { var config = new FeeConfig { SmallCarFee = 2, FreeMinutes = 30 }; var fee = calculator.Calculate("粤B12345", DateTime.Now.AddMinutes(-45), DateTime.Now, config); Assert.AreEqual(2.00m, fee); }

    从那以后我每次重构计费逻辑,都强制走一遍这些测试用例,再敢动核心算法。

希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 6:10:27

jadx-gui免安装版:开箱即用的安卓反编译工作台

简介&#xff1a;这是一份开箱即用的APK反编译工具包&#xff0c;专为Android开发者、安全研究员及逆向学习者设计&#xff0c;解决无环境依赖快速分析APK源码与资源的核心需求。压缩包共154个文件&#xff0c;含核心可执行文件jadx-gui.bat&#xff08;Windows启动脚本&#x…

作者头像 李华
网站建设 2026/10/19 6:10:04

毕业论文如何有效降低AI检测率?五类工具实战指南

2026届的同学现在应该已经陆续进入毕业论文的最紧张阶段了。这一年和往年最大的不同在于&#xff0c;很多人在搞定查重之后&#xff0c;又撞上了“AI率检测”这道新墙。查重率有标准答案&#xff0c;AI率越查越高&#xff0c;改写来改写去还是被标红&#xff0c;朋友圈和群里天…

作者头像 李华
网站建设 2026/10/19 6:10:03

SSMS创建与管理数据库:从建库、文件组到备份还原的完整实验指南

1. 从零开始&#xff1a;我在实验课上是这样搞懂数据库创建的相信不少同学第一次打开 SQL Server Management Studio 的时候&#xff0c;跟我的感觉一模一样&#xff1a;满屏幕的菜单、对象资源管理器里一层套一层、各种英文术语&#xff0c;压根不知道从哪儿下手。我们这门实验…

作者头像 李华
网站建设 2026/10/19 6:08:11

AI论文写作工具哪款好?按写作阶段匹配效率最高

四款AI论文写作工具&#xff0c;具体该选哪一款&#xff1f;说实话&#xff0c;这是个很容易让人纠结的问题。我写论文那几年&#xff0c;从第一个字憋不出来&#xff0c;到后来能一天理清一版完整初稿&#xff0c;靠的并不是某个“神级工具”&#xff0c;而是把AI论文写作工具…

作者头像 李华
网站建设 2026/10/19 6:07:26

测试工程师转型AI教育:能力迁移与实操路径

去年底我参加了一个业内的线上交流&#xff0c;主题是测试工程师的技能树更新。会场上有位同行问了一个很现实的问题&#xff1a;如果自动化测试工具越来越智能&#xff0c;测试用例可以自动生成、自动维护&#xff0c;那我们这批人还剩下什么&#xff1f;当时我没法给出一个让…

作者头像 李华
网站建设 2026/10/19 6:07:08

STM32 WiFi远程温控风扇系统:硬件选型、固件开发与调试实战

毕设题目里出现"STM32 WiFi远程温控风扇系统"这种名字&#xff0c;第一反应通常是温度加风扇加WiFi&#xff0c;三个东西一拼&#xff0c;好像没什么难的。真从零开始搭一套能演示、能答辩、能连续跑几个小时的系统时&#xff0c;你会发现每个环节都有坑&#xff1a;…

作者头像 李华