
简介使用C#与SQL Server开发的火车信息管理系统基于Visual Studio与ADO.NET构建面向需要完成课程设计或毕业设计的计算机专业学生以及希望掌握桌面应用与数据库联动开发的C#初学者。系统覆盖车次信息管理、时刻表维护、模拟售票、乘客信息管理、多条件查询、报表统计与权限控制等模块能够完整体验从界面搭建到数据落地的开发流程。压缩包共186个文件以C#源文件和窗体设计文件实现业务与界面逻辑资源文件存储界面元素与图标并包含Visual Studio解决方案与数据库脚本用于初始化数据表整体大小仅3.4MB结构清晰便于查看源码和导入数据库。目前已有188人学习下载适合对照源码理解DataGridView、SqlConnection等核心控件的实际用法。内含可直接运行的源码和数据库脚本修改连接字符串后即可运行附带窗体资源与图标素材能为二次开发或功能扩展提供完整起点。1. 一套 C# SQL Server 的火车信息管理系统能跑、能改、能复用打开这个压缩包之前你大概率已经在某个课程设计或毕业设计的截止日期前反复横跳了。这套基于 C# 和 SQL Server 的火车信息管理系统本质是一个标准 WinForms 数据库应用两个业务窗体、几张关联表、一条连接字符串不依赖任何第三方控件拿到源码就能编译。它解决的是最典型的那类需求——车次信息的增删改查、查询界面按条件筛数据、游客和管理员看到的界面不一样。适合正在看 C# 教程准备交作业的学生也适合想快速套用 WinForms 接 SQL Server 的从业者。拆开看核心就三板斧SqlConnection 连库、SqlDataAdapter 填 DataSet、DataGridView 绑定展示。把它跑通后同类小系统的源码你都能照这个套路拆。2. 系统骨架WinForms 窗体、ADO.NET 与数据库表结构2.1 从 Designer.cs 反推窗体职责FrmTheUser 与 FrmTheSearch 各自负责什么打开压缩包第一眼被一堆 .cache 文件晃到是正常的。ResolveAssemblyReference.cache、DesignTimeResolveAssemblyReferencesInput.cache、GenerateResource.Cache 这些都是 VS 编译期生成的中间产物跟项目运行没关系直接忽略。真正要研究的是两个 .Designer.cs 文件——FrmTheUser.Designer.cs 和 FrmTheSearch.Designer.cs。在 WinForms 项目里每个窗体拆成三个文件主逻辑文件FrmTheUser.cs、控件定义文件FrmTheUser.Designer.cs和资源文件FrmTheUser.resx。Designer.cs 由 VS 设计器自动维护InitializeComponent 方法里完成所有控件的实例化和布局。反推一个窗体干了什么最省事的办法是直接搜 Designer.cs 里的控件类型声明// FrmTheUser.Designer.cs 中反推控件清单 private System.Windows.Forms.DataGridView dgvTrain; // 车次列表 private System.Windows.Forms.TextBox txtTrainNo; // 车次号输入 private System.Windows.Forms.ComboBox cboTrainType; // 车型下拉 private System.Windows.Forms.Button btnAdd; // 新增车次 private System.Windows.Forms.Button btnDelete; // 删除车次 private System.Windows.Forms.Button btnModify; // 修改车次 private System.Windows.Forms.Timer timerStatus; // 底部状态栏刷新从控件组合能直接判断角色出现 btnAdd、btnDelete、btnModify 这类写操作按钮的是管理员操作台只有搜索框和查询按钮的是普通用户查询窗口。这个项目里 FrmTheUser 是主操作界面承载车次信息的增删改查FrmTheSearch 是面向乘客的查询窗口输入出发站和到达站就能筛选车次。这种从 Designer.cs 反推职责的方法适合任何拿到的 WinForms 源码。不用先把整个项目编译跑通只要扫一遍控件声明就能知道每个窗体是干嘛的、数据流向哪里。尤其是课程设计类源码窗体命名习惯通常是 FrmLogin、FrmMain、FrmSearch 这种直观前缀配合控件名基本能还原整个界面交互逻辑。2.2 SQL Server 表结构设计车次、时刻表、乘客、订单的关联火车信息管理系统的数据库一般拆四张核心表TrainInfo 存车次基本信息Schedule 存发车和到站时刻Passenger 存乘客档案OrderInfo 存购票订单。TrainInfo 与 Schedule 是 1:N 关系——一个车次可以有多个途经站和对应时刻OrderInfo 通过 TrainNo 和 PassengerID 分别关联车次与乘客形成典型的星型结构。-- 车次基本信息表 CREATE TABLE TrainInfo ( TrainNo NVARCHAR(20) PRIMARY KEY, -- 车次号如 G102 TrainType NVARCHAR(20) NOT NULL, -- 车型高铁/动车/普快 MaxSpeed INT, -- 运行速度 DepartureStation NVARCHAR(50) NOT NULL, ArrivalStation NVARCHAR(50) NOT NULL, SeatCount INT DEFAULT 100 -- 座位总数 ); -- 时刻表 CREATE TABLE Schedule ( ScheduleID INT IDENTITY(1,1) PRIMARY KEY, TrainNo NVARCHAR(20) FOREIGN KEY REFERENCES TrainInfo(TrainNo), StopStation NVARCHAR(50) NOT NULL, -- 途经站 ArriveTime DATETIME NOT NULL, DepartTime DATETIME NOT NULL ); -- 乘客表 CREATE TABLE Passenger ( PassengerID INT IDENTITY PRIMARY KEY, Name NVARCHAR(30) NOT NULL, IDCard NVARCHAR(18) UNIQUE NOT NULL, Phone NVARCHAR(20) ); -- 订单表 CREATE TABLE OrderInfo ( OrderID INT IDENTITY PRIMARY KEY, TrainNo NVARCHAR(20) FOREIGN KEY REFERENCES TrainInfo(TrainNo), PassengerID INT FOREIGN KEY REFERENCES Passenger(PassengerID), SeatType NVARCHAR(10), -- 一等座/二等座/硬座 TicketCount INT DEFAULT 1, TotalPrice DECIMAL(10,2) );这段建表脚本基本覆盖了摘要里提到的功能模块。几个选型理由值得说清楚第一所有文本字段一律用 NVARCHAR 而不是 VARCHAR因为 SQL Server 里 VARCHAR 对中文的排序和比较依赖数据库默认排序规则NVARCHAR 是 Unicode 编码存中文不乱码、查询也稳定。第二TrainNo 直接做主键而不是用自增 ID因为车次号本身具备业务唯一性查询按车次号走主键索引比额外加自增列更直观。第三订单价格用 DECIMAL(10,2) 而不是 FLOAT避免浮点精度误差导致金额对不上。如果导入后发现表结构和预期功能对不上优先检查脚本末尾有没有 ALTER TABLE 语句——有些源码把外键约束单独放在脚本后面执行为了让建表结构看起来更清晰。教务系统里常见的是先删表再建表的 DROP IF EXISTS 逻辑如果脚本里没带重复执行时会报“对象已存在”的错误。2.3 为什么是 SqlDataAdapter DataSet而不是 SqlCommand 直读访问数据库的方式上这个项目采用 ADO.NET 的经典组合SqlConnection 建连接SqlDataAdapter 执行查询并把结果填进 DataSet最后 DataGridView 的 DataSource 直接指向 DataSet 里那张表。这是一条在 WinForms 场景下最稳的路子也是很多 C# 课程设计源码的标准写法。// 典型查询流程FrmTheSearch 的加载事件 string connStr ConfigurationManager.ConnectionStrings[TrainSystem].ConnectionString; string sql SELECT TrainNo, TrainType, DepartureStation, ArrivalStation FROM TrainInfo; using (SqlConnection conn new SqlConnection(connStr)) { SqlDataAdapter adapter new SqlDataAdapter(sql, conn); DataSet ds new DataSet(); adapter.Fill(ds, TrainSchedule); dgvResult.DataSource ds.Tables[TrainSchedule]; }代码逻辑分四步先从配置文件读连接字符串再写查询 SQL用适配器把数据库结果填充到内存里的 DataSet最后把 DataSet 中的表交给 DataGridView。SqlDataAdapter 内部会隐式完成 Connection 打开、执行、关闭的整个生命周期不需要显式调用 conn.Open()。DataGridView 只认实现了 IListSource 接口的数据源DataTable 恰好满足绑定后控件自动按查询结果生成列。这种写法的好处是代码量少、出错点集中在 SQL 字符串本身非常适合课程设计和毕业设计这种需要快速交付的场景。代价是 DataSet 把数据全部缓存进内存数据量超过几万行时界面会明显变卡生产系统一般换成分页查询或 SqlDataReader 流式读取。对这个火车信息管理系统来说单表数据量不会太大这个选型在可读性和性能之间是合理的。如果你想改成 SqlCommand SqlDataReader 的方式逻辑上也行但要手动管理连接开关和逐行读取代码会膨胀不少。3. 把源码跑起来SQL 脚本导入、连接字符串改造与编译环境3.1 SQL 脚本导入用 SSMS 或 sqlcmd 把数据库建出来拿到压缩包后最要紧的一件事不是急着打开 .sln 点 F5而是先把数据库建起来。没有库程序一启动就会在连接字符串那一步抛异常。项目里附带的是 .sql 脚本常见导入方式有两种。方式一用 SSMS 图形化操作打开 SQL Server Management Studio连接到本地实例新建查询把整个 .sql 文件内容粘贴进去点执行。如果脚本里已经包含 CREATE DATABASE 语句那么不需要手动建库。方式二用命令行工具 sqlcmd适合已经装了 SQL Server 但没装 SSMS 的情况sqlcmd -S localhost -U sa -P 你的密码 -d master -i C:\train_system\database\train.sql参数含义-S 指定服务器实例localhost 指本机默认实例-U 和 -P 是登录名和密码-d master 表示在 master 库上下文中执行脚本脚本内部的 USE TrainDB 会把后续语句切换到目标库-i 后面跟脚本文件的完整路径。执行完后用下面这条命令验证表是否建好sqlcmd -S localhost -U sa -P 你的密码 -d TrainDB -Q SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES能看到 TrainInfo、Schedule、Passenger、OrderInfo 这些表说明脚本执行成功。常见问题有两个一是脚本里带了 GO 批处理分隔符复制到 SSMS 时被截断二是脚本头部没有 CREATE DATABASE而执行时没指定 -d TrainDB导致表建到了 master 库下面。血的教训是先确认表落在哪个库里再去改连接字符串的 Initial Catalog顺序反了会排查半天。3.2 app.config 的 connectionStrings只需要改四个参数整个部署过程最核心的配置节点就是 app.config。打开项目里的 App.config 或 app.config会看到类似下面的结构?xml version1.0 encodingutf-8 ? configuration connectionStrings add nameTrainSystem connectionStringData Source.;Initial CatalogTrainDB;User IDsa;Password123456; providerNameSystem.Data.SqlClient / /connectionStrings startup supportedRuntime versionv4.0 sku.NETFramework,Versionv4.7.2 / /startup /configuration需要改动的只有 connectionString 里的四个键值它们的含义和注意事项如下表参数含义常见取值踩坑点Data SourceSQL Server 实例名. 或 localhost 或 电脑名\实例名命名实例漏写反斜杠写成本机名加实例名之间少了分隔符Initial Catalog数据库名TrainDB必须和 3.1 步实际建出的库名完全一致大小写不敏感但字母要一致User IDSQL 登录账号sa 或自定义账号不能用 Windows 账号除非连接串换成 Integrated SecurityTruePassword登录密码你自己设置的值特殊字符如分号、引号需要转义整串可用两种引号包裹新手特别容易踩一个坑改的是项目里的 app.config但程序运行时真正读的是 bin\Debug\项目名.exe.config。如果只改源文件没重新生成按 F5 跑的时候还是旧配置。解决办法很简单改完配置后在 VS 里重新生成解决方案或者直接到 bin\Debug 目录下用记事本改 exe.config效果一样。我自己的习惯是两步都做源码里改一份保持版本一致发布目录里核对一份保证当前运行使用的是新值。提示连接字符串里的 name 属性不要乱改。项目代码里用 ConfigurationManager.ConnectionStrings[TrainSystem] 来读取如果你把 name 改成 TrainSystem1程序会返回 null然后报“未将对象引用设置到对象的实例”。3.3 编译环境VS 版本、目标框架和那些 .cache 文件的处理打开 .sln 文件时如果弹出“需要重定目标”的提示说明本机 VS 的默认目标框架比项目原本的高跟着向导点确定就好。这类基于 .NET Framework 的 WinForms 项目最常见的组合是 .NET Framework 4.5 到 4.7.2VS 2019、VS 2022 都支持。只有一种情况会编译失败项目用了高版本的 C# 语法而本机编译器不支持但这个火车信息管理系统用的都是基础语法不会碰到。压缩包里的 ResolveAssemblyReference.cache、DesignTimeResolveAssemblyReferencesInput.cache、GenerateResource.Cache 这些文件本质是 VS 编译时自动生成的缓存删掉不影响项目加载反而能减少一些莫名其妙的引用冲突。如果打开项目后控件不见了、窗体设计器报错先把这些 .cache 文件和整个 bin、obj 目录删掉再重新打开解决方案让 VS 从头解析一遍八成能自愈。还有一个不起眼但容易卡壳的点如果代码里用了 ConfigurationManager项目必须引用 System.Configuration 程序集。编译时如果报“当前上下文中不存在名称 ConfigurationManager”右键项目 - 添加引用 - 程序集 - System.Configuration。引用缺失和命名空间漏写是两个不同问题遇到 CS0246 错误看 using 是否齐全遇到 CS1061 或 CS0103 错误则先检查引用再检查变量作用域。这种细节在交作业前务必跑一遍完整编译别只跑到一半就停了。4. 核心功能落地登录权限分流、车次查询与 DataGridView 绑定4.1 登录窗口的角色分流管理员写入、普通用户查询火车信息管理系统一般把用户分成两类管理员和普通乘客。管理员能进 FrmTheUser 做增删改普通用户只能进 FrmTheSearch 查车次。这个分流在登录按钮的点击事件里完成核心逻辑是查用户表拿角色字段再按角色决定打开哪个窗体。// 登录事件核心逻辑 private void btnLogin_Click(object sender, EventArgs e) { string connStr ConfigurationManager.ConnectionStrings[TrainSystem].ConnectionString; string sql SELECT Role FROM Users WHERE UserName userName AND Password password; using (SqlConnection conn new SqlConnection(connStr)) { SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.AddWithValue(userName, txtUserName.Text.Trim()); cmd.Parameters.AddWithValue(password, txtPassword.Text); conn.Open(); object role cmd.ExecuteScalar(); conn.Close(); if (role null) { MessageBox.Show(用户名或密码错误); return; } if (role.ToString() Admin) { FrmTheUser adminForm new FrmTheUser(); adminForm.Show(); this.Hide(); } else { FrmTheSearch searchForm new FrmTheSearch(); searchForm.Show(); this.Hide(); } } }逻辑说明用参数化 SqlCommand 执行查询ExecuteScalar 返回第一行第一列的值也就是角色字段。查不到记录说明用户名或密码不匹配返回 null。注意两个细节——用户名做了 Trim 去空格避免手滑输入带空格的账号参数化写法 userName、password 是为了防 SQL 注入。如果这里改成字符串拼接输入一个引号就能打破 SQL 语义这是课程设计里最容易被扣分的点。常见做法是把密码的存储再加密一层比如 SHA256 哈希后入库登录时把输入转换后再比对。项目如果没做这一步属于安全设计上的简化但不影响功能演示。你要是想升级在 SQL 脚本里给 Password 字段手动 UPDATE 成哈希值即可代码端对应改一行。4.2 车次查询组合筛选条件与 LIKE 模糊匹配查询界面是这个系统使用频率最高的功能。按车次、出发站、到达站三个条件任意组合核心是动态拼接 WHERE 子句。图省事的写法是把三个条件都用 LIKE 加 % 通配符这样用户输入完整车次还是几个关键字都能命中。private void btnSearch_Click(object sender, EventArgs e) { StringBuilder sql new StringBuilder(); sql.Append(SELECT TrainNo, TrainType, DepartureStation, ArrivalStation FROM TrainInfo WHERE 11 ); if (!string.IsNullOrWhiteSpace(txtTrainNo.Text)) { sql.Append(AND TrainNo LIKE trainNo ); } if (!string.IsNullOrWhiteSpace(txtDeparture.Text)) { sql.Append(AND DepartureStation LIKE departure ); } if (!string.IsNullOrWhiteSpace(txtArrival.Text)) { sql.Append(AND ArrivalStation LIKE arrival ); } using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql.ToString(), conn)) { if (!string.IsNullOrWhiteSpace(txtTrainNo.Text)) cmd.Parameters.AddWithValue(trainNo, % txtTrainNo.Text.Trim() %); if (!string.IsNullOrWhiteSpace(txtDeparture.Text)) cmd.Parameters.AddWithValue(departure, % txtDeparture.Text.Trim() %); if (!string.IsNullOrWhiteSpace(txtArrival.Text)) cmd.Parameters.AddWithValue(arrival, % txtArrival.Text.Trim() %); SqlDataAdapter adapter new SqlDataAdapter(cmd); DataTable dt new DataTable(); adapter.Fill(dt); dgvResult.DataSource dt; } }参数说明WHERE 11 是恒真条件作用是让后续 AND 可以直接拼接不需要判断是不是第一次拼接。三个文本框单独判断非空只把有值的条件拼进去。LIKE 配合 % 通配符实现模糊匹配输入“北京”会命中“北京南”“北京西”。有一层细节值得注意参数值里的 % 是手动加在字符串两端的而不是写在 SQL 模板里写 LIKE % trainNo %。两种写法效果一样但手动拼参数值能让 SQL Server 更好地复用执行计划避免带通配符的参数被单独编译一份专用计划。如果要改成精确匹配把 LIKE 换成 并且去掉参数值两端的 % 即可。模糊查询和精确查询在数据量大时性能差异明显LIKE %xxx% 无法利用索引全表扫描是必然的但对课程设计的数据量来说完全够用。4.3 DataGridView 绑定AutoGenerateColumns 与列映射数据查出来后怎么显示决定用户对系统专业度的第一印象。直接把 DataTable 丢给 DataGridView列会按查询结果自动生成表头是英文列名显示效果很生硬。更规范的做法是关闭自动生成列预先定义中文字段头再用 DataPropertyName 做映射。// 在窗体加载时初始化列 private void InitGridColumns() { dgvResult.AutoGenerateColumns false; DataGridViewTextBoxColumn colTrainNo new DataGridViewTextBoxColumn(); colTrainNo.HeaderText 车次号; colTrainNo.DataPropertyName TrainNo; colTrainNo.Width 100; DataGridViewTextBoxColumn colType new DataGridViewTextBoxColumn(); colType.HeaderText 车型; colType.DataPropertyName TrainType; colType.Width 80; DataGridViewTextBoxColumn colDeparture new DataGridViewTextBoxColumn(); colDeparture.HeaderText 出发站; colDeparture.DataPropertyName DepartureStation; colDeparture.Width 120; dgvResult.Columns.AddRange(colTrainNo, colType, colDeparture); }代码逻辑AutoGenerateColumns 设为 false 后DataGridView 不再根据数据源自动建列完全按手动添加的列显示。每个列的 DataPropertyName 告诉控件绑定数据源里的哪个字段HeaderText 是显示给用户看的表头文字。这样做的好处是即使 SQL 里写了复杂的列别名甚至多表 JOIN 后出现同名字段界面上的列顺序和宽度都能完全受控。如果给一个不存在的字段名赋值控件不会报错但那一列会全部显示为空这是新手排查“为什么这列是空的”时最容易忽略的点。另一个实用习惯查询结果绑定后经常需要在底部状态栏显示“共查到 N 条记录”。N 可以直接用 dgvResult.Rows.Count 获取但注意 Rows.Count 只在数据源绑定后有效绑定前是 0。如果这段代码放在 Fill 之前执行永远显示 0别问我是怎么知道的。把统计逻辑放在 DataSource 赋值之后或者直接遍历 DataTable 的 Rows.Count都能避开这个陷阱。5. 避坑指南连接失败、登录被绕过、中文乱码的排查记录这一章把复现这类 C# SQL Server 系统时高频踩坑的细节整理成“现象-原因-解决”三段式每一条都能直接对照。5.1 现象SqlConnection.Open() 报“用户 sa 登录失败”这是拿到源码后第一个拦路虎。程序一启动就弹异常错误信息显示 login failed for user sa。原因集中在三类第一SQL Server 安装时只启用了 Windows 身份验证模式SQL 账号登录通道被关闭第二sa 账户本身被禁用第三密码强度不满足 SQL Server 策略安装时被强制改成了你记不住的值。解决步骤用 Windows 身份验证打开 SSMS右键服务器 - 属性 - 安全性把服务器身份验证改成“SQL Server 和 Windows 身份验证模式”确定后重启 SQL Server 服务。然后展开“安全性 - 登录名”双击 sa重置密码勾选“启用”。改完回到程序再运行一次。这条翻车率最高因为很多人只改了连接字符串里的密码忘了启用 SQL Server 的混合验证模式。5.2 现象改了 app.config 里的密码运行还是报旧密码错误这个坑出在配置文件的生效机制上。WinForms 项目里 app.config 是编译期源文件程序运行时读的是输出目录下的“项目名.exe.config”。如果只改了工程里的 app.config 没有重新生成bin\Debug 下的配置文件不会同步。更隐蔽的情况是 VS 增量编译的时间戳判断异常导致 exe.config 没有被覆盖。解决改完配置后手动按 CtrlShiftB 重新生成整个解决方案然后打开 bin\Debug 目录确认 exe.config 里的 connectionString 确实更新了。项目如果已经发布到其他机器直接改发布目录下的 exe.config 最省事改完重启程序生效。从那以后我配置完环境的第一件事就是打开 bin 目录核对配置文件省得被旧配置摆一道。5.3 现象程序里插入中文正常但重启后数据显示乱码这类问题多半是表结构设计时用了 VARCHAR 而非 NVARCHAR。当数据库默认排序规则是 Latin1_General 或其他非中文排序规则时VARCHAR 字段存中文会发生编码转换异常查询出来变成一串问号或不可读字符。SQL Server 的 VARCHAR 只支持单字节字符集中文必须用 NVARCHAR 的 Unicode 编码。解决把相关字段改成 NVARCHAR表已建好就执行 ALTER TABLEALTER TABLE TrainInfo ALTER COLUMN DepartureStation NVARCHAR(50);改完后再插入数据程序端和数据库端字符集就对齐了。连接字符串里不需要额外加 Character Set 参数SQL Server 场景下那套 MySQL 的字符集玩法不适用。新增数据时确保 C# 端用 AddWithValue 传字符串而不是字节数组大部分乱码问题到这一步就消失了。5.4 现象按车次查询返回空但 SSMS 里 SELECT 能看到数据最典型的原因是数据里有前导或尾随空格。比如脚本导入时给 DepartureStation 加了默认空格值用户输入“北京”而库里存的是“ 北京”LIKE %北京% 查不到。另一种常见原因是连错了库——你本地查的是 TrainDBapp.config 里 Data Source 指向了远程服务器两边数据不同步。解决先用 SELECT 确认数据源查一下字段长度SELECT TrainNo, DepartureStation, LEN(DepartureStation) FROM TrainInfo;LEN 结果和肉眼看到的字符数对比就能判断有没有空格。有的话执行 UPDATE TrainInfo SET DepartureStation LTRIM(RTRIM(DepartureStation))。确认无空格后再回程序查一次还不行就把查询 SQL 原样复制到 SSMS 里跑一遍对比结果。这个习惯能排除掉一半的玄学问题——不是程序玄学是环境数据不对。5.5 现象VS 打开项目提示“未能加载项目”或设计器控件丢失这种问题跟代码无关跟 VS 的项目缓存和引用路径有关。压缩包解压后直接双击 .slnVS 可能因为锁文件残留导致解析失败。另一个高频场景是代码里引用了某个 DLL但本机没有对应文件设计器直接报错。解决删除 bin、obj 目录删除 ResolveAssemblyReference.cache 等缓存文件重新打开 .sln。如果项目有 packages.config检查 NuGet 包是否还原。没有的话用记事本打开 .csproj检查 HintPath 指向的 DLL 是否存在。这套组合拳能解决八成 WinForms 项目的加载问题剩下两成是控件版本高于本机环境需要升级 VS 或降低目标框架。6. 进阶验证给系统加一张统计报表再导出 Excel系统跑通之后可以试着验证自己是否真正理解整套代码方式是在不动原有窗体结构的前提下加一个小功能按线路统计售票量。这个功能能串联起前面所有知识点——连接字符串、SqlCommand、参数化、DataGridView 绑定。// 新增统计查询按钮事件 string sql SELECT t.DepartureStation, t.ArrivalStation, SUM(o.TicketCount) AS TotalTickets FROM OrderInfo o INNER JOIN TrainInfo t ON o.TrainNo t.TrainNo WHERE o.OrderDate startDate AND o.OrderDate endDate GROUP BY t.DepartureStation, t.ArrivalStation ORDER BY TotalTickets DESC; SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.AddWithValue(startDate, dtpStart.Value.Date); cmd.Parameters.AddWithValue(endDate, dtpEnd.Value.Date);逻辑说明OrderDate 与日期选择控件组合筛选Group By 按线路聚合Sum 汇总售票量。做完查询顺手导出 Excel不用 COM 组件为了兼容没装 Office 的机器用生成 CSV 文件的方式StringBuilder sb new StringBuilder(); foreach (DataGridViewColumn col in dgvResult.Columns) { sb.Append(col.HeaderText).Append(,); } sb.AppendLine(); foreach (DataGridViewRow row in dgvResult.Rows) { foreach (DataGridViewCell cell in row.Cells) { sb.Append(cell.Value?.ToString()).Append(,); } sb.AppendLine(); } File.WriteAllText(export.csv, sb.ToString(), new UTF8Encoding(true));CSV 导出的逻辑很简单先拼表头再逐行列拼单元格逗号分隔最后用 UTF-8 编码写入。注意 Excel 打开 UTF-8 无 BOM 的 CSV 会乱码new UTF8Encoding(true) 里的参数 true 表示生成 BOM 头这是最容易忽略的细节。这个验证过程的价值在于它逼着你把“查询-绑定-导出”这条完整链路在系统外重建了一遍任何对 ADO.NET 理解的漏洞都会在编译和运行时暴露出来。做完这一步这份源码就不再是别人的项目而是一个你随时能改造成其他业务场景的 WinForms 模板。从那以后我每次拿到这类源码包都强制走一遍“先导库、再看连接字符串、然后编译最后加一个自己设计的小功能”的顺序做完一整套才敢说吃透了。希望帮到你。本文还有配套的精品资源点击获取