ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WPF连接SQL Server增删改查完整工程解析:StudentM逐层拆解与避坑指南

WPF连接SQL Server增删改查完整工程解析:StudentM逐层拆解与避坑指南 简介面向C# WPF初学者的SQL数据库CRUD完整示例工程以学生信息管理为实际场景围绕SqlConnection连接、SqlCommand执行、参数化查询与DataGrid数据绑定演示在WPF桌面应用中实现增删改查的完整流程并以可直接运行的示例降低新手入门门槛。资源共56个文件主要包含C#逻辑代码.cs、XAML界面设计、可编译生成的exe文件、sln解决方案入口以及cache、baml等编译中间文件和Properties资源文件整体压缩包仅95KB属于轻量但结构清晰的教学Demo。目前已有2212人学习下载适合正在学习桌面应用与数据库交互的初级开发者对照练习。项目内置MainWindow、INSERT、DELETE、UPDATE、Query等多个页面分别对应学生记录的录入、删除、修改与查询并示范了DataTable填充、DataContext设置和ItemsSource绑定方法同时展示了连接字符串配置、SqlCommand生命周期及DataReader读取记录参数化查询与异常处理思路也贯穿其中可帮助理解数据库操作的安全写法。在此基础上读者还可进一步结合MVVM模式、事务处理与ORM工具如Entity Framework扩展更工程化的应用。1. WPF 连 SQL 做增删改查这份 StudentM 工程解决什么问题上周帮人看一个学生信息管理的小需求界面指定 WPF数据存 SQL Server核心功能就是增删改查。看着简单但真上手你会发现连接字符串写错一个斜杠整个窗口就废了DataGrid 绑定了却不显示UPDATE 写完发现全表被改了。这份 StudentM 工程的价值就是把这些坑提前替你踩了一遍——一个主窗口加四个功能窗口分别对应查询、新增、修改、删除链路非常完整。它适合两类人一类是刚开始做 C# 和数据交互的初级开发者另一类是长期用 WinForms 写工具、想切到 WPF 界面看看新写法的熟手。对熟手来说重点看 WPF 里 DataGrid 绑定和窗口导航的写法对新手来说从 SqlConnection 到 DataTable 的整个流程就是一条现成的学习路径。下面从文件结构到五个窗口的实现逻辑再到最容易翻车的几个点逐层拆开说清楚。2. StudentM 工程拆解从 .sln 到五个窗口的分工与选型理由2.1 解压后的文件清单哪些是源码哪些可以直接忽略压缩包里是一个完整的 Visual Studio 解决方案名字叫 StudentM。解压之后先别急着双击 .sln花两分钟把文件分类能省掉后面大量的困惑。我按「要不要改」把它们分成三类。文件 / 目录作用要不要改StudentM.sln解决方案入口双击后用 Visual Studio 打开不需要StudentM.suo / StudentM.v11.suo用户选项文件记录窗口布局、断点等个人信息可以直接删StudentM.csproj项目文件包含程序集名称、目标框架、编译条目基本不用App.xaml / App.xaml.cs应用入口StartupUri 指向 MainWindow不用改MainWindow.xaml / MainWindow.xaml.cs主窗口放导航按钮负责打开四个功能窗口按需改导航逻辑INSERT.xaml / INSERT.xaml.cs新增学生窗口需要理解DELETE.xaml / DELETE.xaml.cs删除学生窗口需要理解UPDATE.xaml / UPDATE.xaml.cs修改学生窗口需要理解Query.xaml / Query.xaml.cs查询学生窗口需要理解Properties包含 AssemblyInfo.cs 等程序集元数据不用改obj / bin编译中间产物和最终输出不用管这里有一个值得说的细节StudentM.v11.suo里的 v11 是 Visual Studio 2012 的内部版本号说明这个项目最早是在那个时代创建的。你用 VS2015 之后的版本打开会提示做一次解决方案升级直接确认继续就行这种老项目升级到新 IDE 基本不会碰代码层面的冲突最多是目标框架版本需要顺手拉高一下。真正要重点关注的是四个功能窗口。每个窗口的名字就是它的职责INSERT、DELETE、UPDATE、Query英文命名一眼就能看出对应哪条 SQL 语句这种命名习惯在团队协作里非常有价值——一个月后你回头看自己的代码不需要打开文件就知道它是干什么的。2.2 五窗口分工MainWindow 只做导航整个工程的结构非常像传统 WinForms 的「主窗体 子窗体」模式MainWindow 负责放按钮每个按钮对应打开一个功能窗口。这种设计对于教学项目来说是合理的因为它把「界面导航」和「数据库操作」彻底分开了你在 Query 窗口里排查查询问题的时候完全不担心会影响到 INSERT 窗口的代码。MainWindow.xaml.cs 里的导航代码常见做法是这样private void BtnQuery_Click(object sender, RoutedEventArgs e) { QueryWindow queryWindow new QueryWindow(); queryWindow.ShowDialog(); } private void BtnInsert_Click(object sender, RoutedEventArgs e) { InsertWindow insertWindow new InsertWindow(); insertWindow.ShowDialog(); }逻辑说明用ShowDialog()而不是Show()是因为功能窗口是模态窗口。模态窗口打开后用户必须关闭它才能回到主窗口这符合「录入一条学生信息再继续下一条」的操作习惯。Show()是非模态的用户可能同时开着五个窗口数据刷新时机就变得很难控制。参数上如果要让主窗口知道子窗口操作完成了可以读ShowDialog()的返回值这个细节在第 5 章里会具体说。五个窗口这样一拆每条 SQL 语句都能在一个独立文件里被完整阅读对新手来说心智负担小很多。缺点是当功能变多时比如要加「按班级筛选」「导出 Excel」一个窗口一个按钮堆在 MainWindow 里会显得臃肿届时就该考虑用 TabControl 或者引入 MVVM 框架来重构了。2.3 为什么选 SqlConnection DataTable 而不是 EF Core这个工程用的是最原始的 ADO.NET 写法SqlConnection管连接SqlCommand管命令SqlDataAdapter把查询结果填进DataTable再把DataTable.DefaultView丢给 DataGrid。整套链路没有任何 ORM 参与。很多新手会问现在不是都推荐 Entity Framework Core 吗这里要分清场景。EF Core 的优点是让你用 C# 对象直接操作数据免写 SQL但代价是你必须理解 DbContext、实体类映射、迁移Migration这一整套概念。对于一个单表 CRUD 的小工具引入 EF Core 属于明显的过度设计。对比维度SqlConnection DataTableEntity Framework Core上手门槛低核心就三个类高要理解 DbContext 和映射SQL 可见性完全可见你写什么数据库就执行什么被 LINQ 表达式屏蔽适合规模单表、几条语句的小工具几十张表以上的业务系统调试方式拿 SQL 去 SSMS 里直接跑要看生成的 SQL 日志学习价值帮你理解数据库交互本质帮你理解 ORM 设计思想我的建议是如果你在做 C# 上位机配套的小工具、工厂数据录入界面这类项目直接用 ADO.NET 这套写法数据访问逻辑完全透明出了问题拿 SQL 语句去 SSMS 里一跑就知道是谁的锅。如果项目表结构复杂、关联关系多再去学 EF Core 不迟——而且先理解了 SqlConnection 的运作原理再回头用 EF Core你会更清楚它替你省掉了哪些底层操作。3. 把查询窗口跑通连接字符串、SELECT 与 DataGrid 绑定链路3.1 先准备数据库StudentManagement 库与 Students 表按照摘要描述工程假设你已经有一个名为StudentManagement的数据库里面有一张Students表。如果是从零开始建库建表脚本可以直接在 SSMS 里执行CREATE DATABASE StudentManagement; CREATE TABLE Students ( ID INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) NOT NULL, Age INT ); INSERT INTO Students (Name, Age) VALUES (N张三, 20), (N李四, 21), (N王五, 22);逻辑说明ID用IDENTITY(1,1)做自增主键这样 INSERT 时不需要手动指定 ID数据库自动分配避免并发下主键冲突。这里我特别用了NVARCHAR而不是VARCHAR这是处理中文数据的关键——NVARCHAR按 Unicode 存储C# 的 string 也是 Unicode两边不用做代码页转换。如果你用VARCHAR在简体中文系统下偶尔没事换一台英文系统服务器就会出现乱码这个坑在第 5 章会详细讲。建完表先插三条测试数据这样查询窗口一打开就能立刻验证链路通不通不用先走一遍新增流程。我一般会把测试数据和正式数据分开避免后面调试 UPDATE 时误伤初始记录。3.2 连接字符串三个参数逐个解释连接字符串是整个工程里「黑匣子」属性最强的一段配置。写错一个符号程序报的错根本不在你写的代码上而在数据源解析上。核心连接字符串长这样string connectionString Data Source.\SQLEXPRESS;Initial CatalogStudentManagement;Integrated SecurityTrue;参数说明Data Source指定数据库服务器位置.\SQLEXPRESS表示本机的 SQL Server Express 实例那个反斜杠是实例名分隔符Initial Catalog指定要连接的数据库名对应我们的StudentManagementIntegrated SecurityTrue意思是使用 Windows 当前登录身份验证不把用户名密码写死在代码里。字符串前面的是逐字字符串标识符防止\被转义成控制字符。不同 SqlConnection 写法对应的场景差异很大我用一张表总结Data Source 值适用场景注意点.\SQLEXPRESS本机装了 SQL Server Express最常见的开发环境(localdb)\MSSQLLocalDB本机装了 LocalDB适合临时调试数据文件在用户目录.本机默认实例 SQL Server完整版 SQL Server 默认安装192.168.1.10,1433远程服务器要配置防火墙和 SQL 远程访问如果是跟着这份资源复现我建议先确认自己装的是哪一类。最简单的方法打开 SSMS看「服务器名称」下拉框里显示什么然后把那个值原样抄到Data Source位置。很多新手在这里照抄网上的连接字符串结果本机根本没有那个实例折腾半小时才发现问题出在环境不匹配这种「玄学报错」最常见。3.3 Query.xaml.cs 完整查询流程三行核心六个细节查询窗口是整个工程最值得先读的文件因为 SELECT 是其他三个操作的基础。Query.xaml.cs 里查询按钮的典型实现private void BtnQuery_Click(object sender, RoutedEventArgs e) { string connectionString Data Source.\SQLEXPRESS;Initial CatalogStudentManagement;Integrated SecurityTrue; string sql SELECT ID, Name, Age FROM Students ORDER BY ID; DataTable dt new DataTable(); using (SqlConnection conn new SqlConnection(connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { try { conn.Open(); SqlDataAdapter adapter new SqlDataAdapter(cmd); adapter.Fill(dt); dataGrid.ItemsSource dt.DefaultView; } catch (Exception ex) { MessageBox.Show($查询失败{ex.Message}); } } }逻辑说明SqlConnection和SqlCommand都放在using块里这是一个必须养成的习惯——SqlConnection底层是数据库连接池如果不用using确保 Dispose连接池会被占满程序跑几十次后就开始报连接超时这是慢 SQL 之外的另一个常见性能杀手。SqlDataAdapter.Fill(dt)这一步会自动处理连接的打开和关闭但我还是习惯显式调用conn.Open()原因是想让意图更清晰这里明确了我打算发起一次数据库访问。参数说明SQL 里加了ORDER BY ID保证多次查询的显示顺序一致否则 SQL Server 返回行的顺序没有承诺相同数据可能每次排列都不同用户体验会很怪。dataGrid.ItemsSource dt.DefaultView是把DataTable的视图直接交给 DataGrid。为什么不直接赋dt因为 DataGrid 的自动列生成机制在绑定 DataView 时更稳定而且DataView天然支持排序和筛选为后续加搜索功能留了后路。3.4 DataGrid 绑定与表头显示AutoGenerateColumns 的两个选择查询窗口的 XAML 里DataGrid 的写法通常有两种。一种是全部自动生成列DataGrid x:NamedataGrid AutoGenerateColumnsTrue IsReadOnlyTrue Grid.Row1 Margin10 /这种写法的优点是零配置Fill返回的 DataTable 里有什么列界面上就显示什么列列的顺序和 SQL 里 SELECT 的顺序一致。缺点很明显列头显示的是英文字段名 ID、Name、Age不是「编号」「姓名」「年龄」。如果你想要中文表头优先改 SQL 而不是关掉自动生成列。最常见做法是用别名SELECT ID AS 编号, Name AS 姓名, Age AS 年龄 FROM Students ORDER BY ID这样 DataGrid 的列头自然就是中文。另一种做法是把AutoGenerateColumns设为False手动定义列DataGrid x:NamedataGrid AutoGenerateColumnsFalse IsReadOnlyTrue DataGrid.Columns DataGridTextColumn Header编号 Binding{Binding ID} Width80 / DataGridTextColumn Header姓名 Binding{Binding Name} Width120 / DataGridTextColumn Header年龄 Binding{Binding Age} Width80 / /DataGrid.Columns /DataGrid这里有个非常容易翻车的点手动列里Binding{Binding ID}必须和 DataTable 的列名完全一致包括大小写。SQL Server 默认不区分大小写但 WPF 的绑定路径区分大小写——你写{Binding id}而列名是ID运行时不报错但单元格永远空白。这种问题查到最后往往是玄学因为代码逻辑完全正确唯独绑定路径差了一个字母。4. 写入与更新INSERT / UPDATE / DELETE 的 SQL 执行细节4.1 INSERT参数化插入与自增 ID 的取舍INSERT 窗口的核心逻辑是把用户在文本框里输入的名字和年龄写入数据库。这里最关键的技术决策是绝对不能用字符串拼接 SQL必须用参数化写法。private void BtnSave_Click(object sender, RoutedEventArgs e) { string connectionString Data Source.\SQLEXPRESS;Initial CatalogStudentManagement;Integrated SecurityTrue; string sql INSERT INTO Students (Name, Age) VALUES (Name, Age); using (SqlConnection conn new SqlConnection(connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(Name, txtName.Text.Trim()); cmd.Parameters.AddWithValue(Age, int.Parse(txtAge.Text.Trim())); try { conn.Open(); int rows cmd.ExecuteNonQuery(); MessageBox.Show(rows 0 ? 添加成功 : 添加失败); } catch (Exception ex) { MessageBox.Show(写入失败 ex.Message); } } }逻辑说明ExecuteNonQuery专门用来执行 INSERT、UPDATE、DELETE 这类不返回结果集的 SQL它的返回值是受影响的行数。Name和Age是 SQL 参数占位符通过AddWithValue把 C# 变量的值安全地传进去。这样做有两个好处一是防止 SQL 注入——如果有人把窗口当输入框在姓名栏里输入; DROP TABLE Students;--用字符串拼接会把这条语句直接拼接成破坏性 SQL二是免去手动处理字符串转义和数据类型转换的麻烦。参数说明int.Parse(txtAge.Text.Trim())这里有个隐患——如果用户没填年龄或者填了非数字字符程序会在还没连数据库时就抛出 FormatException。更稳妥的做法是用int.TryParseif (int.TryParse(txtAge.Text.Trim(), out int age)) { cmd.Parameters.AddWithValue(Age, age); } else { MessageBox.Show(年龄必须是数字); return; }我一般会加上这一步校验因为用户输入不可控是常态等异常冒到catch (Exception ex)时用户只会看到一个看不懂的报错框。另一个值得注意的点是建表时ID INT IDENTITY(1,1)自增所以 INSERT 里完全不用管 ID 字段数据库会自己分配。如果你以后遇到不是自增主键的表才需要把 ID 也加进 INSERT 列表。4.2 UPDATE先查再改WHERE 条件永远是第一优先级UPDATE 窗口的代码结构和 INSERT 非常像最大的区别是它多了一个 WHERE 条件而且这个条件决定了你会改掉一条数据还是整个表。先看基本代码string sql UPDATE Students SET Name Name, Age Age WHERE ID ID; using (SqlConnection conn new SqlConnection(connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(ID, int.Parse(txtId.Text.Trim())); cmd.Parameters.AddWithValue(Name, txtName.Text.Trim()); cmd.Parameters.AddWithValue(Age, int.Parse(txtAge.Text.Trim())); conn.Open(); int rows cmd.ExecuteNonQuery(); MessageBox.Show(rows 0 ? 修改成功 : 未找到该记录); }逻辑说明WHERE ID ID通过主键精确定位要更新的行。如果没有 WHERE或者 WHERE 条件写错UPDATE Students SET NameName, AgeAge会把整张表所有人的名字和年龄都改了——这是数据库新手最容易犯、后果也最严重的错误之一。我收到过不止一次「为什么我改一个人全班都变了」的问题十有八九就是漏了 WHERE。参数说明int.Parse(txtId.Text.Trim())在这里需要格外小心因为 ID 是自增主键用户正常操作时不应该手动输入。常见做法是先打开查询窗口查出所有学生在 DataGrid 里双击某一行把这个学生的 ID、Name、Age 自动带进 UPDATE 窗口的输入框用户改完再提交。这样不仅省去手输 ID 的麻烦也天然保证了 WHERE 条件一定存在。另外UPDATE 窗口的一个 UI 细节打开窗口时把原有数据回填到文本框里让用户看到「当前值」再改。这个动作对应的代码是txtId.Text row[ID].ToString(); txtName.Text row[Name].ToString(); txtAge.Text row[Age].ToString();row是 DataGrid 选中行对应的DataRowView。如果回填完发现文本框是空的原因大概率是上一章说的手动列绑定路径有问题排错方向要往 XAML 的Binding上找而不是数据库。4.3 DELETE确认弹窗与影响行数双保险缺一不可DELETE 窗口是所有操作里最需要「后悔药」的所以我强烈建议代码里必须加确认弹窗。不只用户需要二次确认开发者也一样——DELETE 语句一旦执行没有事务包裹就找不回来了。if (MessageBox.Show($确认删除学生 {txtName.Text.Trim()} 吗, 删除确认, MessageBoxButton.YesNo, MessageBoxImage.Question) MessageBoxResult.Yes) { string sql DELETE FROM Students WHERE ID ID; using (SqlConnection conn new SqlConnection(connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(ID, int.Parse(txtId.Text.Trim())); conn.Open(); int rows cmd.ExecuteNonQuery(); if (rows 0) { MessageBox.Show(删除成功); } else { MessageBox.Show(未找到该记录可能已被删除); } } }逻辑说明确认弹窗里我用的是txtName.Text.Trim()而不是txtId这是为了让用户看到即将删除的是「谁」而不是一串抽象的编号。人对自己名字的敏感度远高于一个数字这个细节能在真实场景里阻止很多误操作。rows 0判断有两层含义如果没有匹配到 ID返回 0提示「未找到」如果返回 1说明删除成功。参数说明这里DELETE FROM Students WHERE ID ID和 UPDATE 一样必须带 WHERE。ExecuteNonQuery返回值就是受影响行数用它的值给用户反馈比固定弹一个「删除成功」更诚实——万一记录已经被别人删了你能明确告诉用户「这条数据不存在」而不是给出错误的好消息。DELETE 窗口还有一个加分项如果 ID 是自增主键删除一条记录后新插入的数据不会复用这个 ID。所以「删了张三再新增时发现 ID 从 4 开始」是正常现象不是 bug。遇到用户问为什么编号不连续解释清楚 IDENTITY 机制就好。5. WPF 连数据库避坑记录五个常见问题从现象到解决5.1 连接字符串报错找不到服务器或数据库现象程序一启动点查询按钮就弹错内容类似「System.Data.SqlClient.SqlException: 在建立与服务器的连接时出错」或者「Cannot open database StudentManagement requested by the login」。原因绝大部分情况是Data Source写错了。比如本机只装了 LocalDB但连接字符串写的是.\SQLEXPRESS或者数据库名拼写不一致代码里写StudentManagement实际建的库叫StudentM。连接字符串本身没有语法错误所以编译不报错运行时才暴露。解决把连接字符串里的Data Source和Initial Catalog分别验证一遍。打开 SSMS看左上角服务器名称下拉框选一个你能真正连上的把那个值复制进代码。数据库名打开 SSMS 左侧对象资源管理器看实际名字复制粘贴进代码别手打字——手打字容易把Management少打一个n这种错误肉眼很难发现。我一般会建一个DbConfig.cs把连接字符串单独放一个常量四个窗口共用避免每处 Copy 一份导致改一处漏一处。5.2 DataGrid 行数正确但单元格全部空白现象查询执行成功DataGrid 的行数和数据库记录数完全一致但每行每个单元格都是空白没有任何文字。原因最常见的是手动列绑定路径和 DataTable 列名对不上。比如 SQL 写的是SELECT ID, Name, Age FROM Students列名是Name但 XAML 里写的是Binding{Binding 姓名}而你是把AutoGenerateColumns关了以后自己加的列——这种情况下绑定目标不存在WPF 不会报错只会静默地不显示内容。另一种情况是SELECT Name AS 姓名和Binding{Binding 姓名}看似对上了但别名中间有空格写成了姓 名绑定路径也要跟着带空格。解决优先用AutoGenerateColumnsTrue先确认数据本身有没有正确进来。行数对、内容空90% 是绑定问题行数都不对那就是 SQL 或 Fill 的问题。确认绑定问题时可以在临时中断点里直接看dt.Columns的列名集合把实际列名复制到 XAML 的 Binding 里。这个排查思路能把「玄学」变成「可验证的科学」。5.3 中文乱码写入后查询显示问号现象通过 INSERT 窗口写入「张三」查询出来是「???」。在 SSMS 里直接看表的记录也是乱码。原因表结构里用的是VARCHAR而且数据库默认排序规则和 C# 字符串的 UTF-16 编码不对齐。VARCHAR在 SQL Server 里按代码页存储简体中文环境通常走 GBK一旦连接字符串里没有显式指定Character Set从 C# 传进去的 UTF-16 中文就会被转成问号。这个坑在「数据库是本机创建的」时往往不出现一旦数据库文件是从别处复制来的或服务器是英文区域设置问题立刻暴露。解决建表时用NVARCHAR代替VARCHAR这是根治方案。NVARCHAR内部按 Unicode 存储C# 的string传给 SQL 参数时不需要代码页转换。如果你遇到的是已经建好的表可以使用ALTER TABLE Students ALTER COLUMN Name NVARCHAR(50) NOT NULL修改列类型但要注意如果乱码数据已经写入ALTER 之后那些问号字符已经变成数据本身了需要先清掉重插。防止乱码的最好时机是建表那一刻后面都是修数据的活。5.4 ExecuteNonQuery 返回 -1明明成功了却提示失败现象INSERT 语句执行成功数据已经写进表里但ExecuteNonQuery返回的值不是 1而是 -1程序弹了「添加失败」。原因这是最骗人的一个返回值。ExecuteNonQuery正常情况下返回受影响的行数但如果连接字符串或数据库会话里启用了SET NOCOUNT ONSQL Server 不再返回受影响行数信息方法就会返回 -1。很多教程里的连接字符串模板并没有 NOCOUNT但如果你复用的是某个工程里的连接或数据库里挂了返回结果集的触发器就可能踩上。只要代码里用rows 0判断成功失败-1 就会被误判成失败。解决不要拿rows 0当唯一的成功标准。在 INSERT 之后可以追加SELECT ROWCOUNT来获得真实影响行数或者用SCOPE_IDENTITY()拿到刚插入的自增 ID——能拿到 ID 说明插入必然成功了。如果你只是需要一个「是否成功」的判断改成if (rows 0)也能绕过去但治标不治本因为 -1 可能掩盖真实异常。我个人的习惯是在 INSERT 的 SQL 尾部加上SELECT SCOPE_IDENTITY() AS NewID;然后用ExecuteScalar()拿返回值这样既能确认成功又拿到了新 ID还给后续「插入后立即跳转到详情」留了余地。5.5 操作成功后 DataGrid 不刷新新增完回来看不到新数据现象在 Query 窗口查出了数据列表然后打开 INSERT 窗口添加一条记录关闭 INSERT 后回到 Query 窗口DataGrid 还是旧数据新记录看不到。原因DataGrid 里显示的是内存中那份DataTable的视图数据库里的数据变了内存里的DataTable不会自动重新查询。你没有刷新它当然保持原样。这个不算 bug但要明白WPF 的ItemsSource绑定的是数据源不是「自动从数据库拉取」的活数据源。只有 DataGrid 和数据库之间建立实时通知机制比如 SignalR 或轮询才会自动刷新普通桌面程序不会这样做。解决把查询逻辑从按钮点击事件里抽成一个独立方法比如LoadData()然后在 Query 窗口每次被激活时重新调用。配合ShowDialog的返回值可以做得很优雅——INSERT 窗口正常关闭时返回true主窗口拿到这个值就刷新InsertWindow insertWindow new InsertWindow(); bool? result insertWindow.ShowDialog(); if (result true) { queryWindow.LoadData(); // 重新执行查询并刷新 DataGrid }注意LoadData()里要做一次adapter.Fill(dt)然后dataGrid.ItemsSource dt.DefaultView。我看到很多人把DataTable定义成窗口的成员字段复用时直接dt.Rows.Clear()再重新 Fill这也可以但要注意DataTable的列结构不会因为第二次查询返回不同列而自动调整。把LoadData()做成每次新建局部DataTable并重新赋给ItemsSource是最不容易出残留数据问题的方式。6. 封装 DbHelper 与迈向 MVVM让 CRUD 代码不再重复粘贴四个窗口写完后你会发现一个尴尬的事实每个窗口里的连接、打开、命令、关闭代码几乎一模一样只是 SQL 语句和参数不同。这时候就该抽一个公共类了。我一般会建一个静态DbHelper把查询和执行的通用逻辑收拢起来public static class DbHelper { private static readonly string ConnStr Data Source.\SQLEXPRESS;Initial CatalogStudentManagement;Integrated SecurityTrue; public static DataTable Query(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(ConnStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); SqlDataAdapter adapter new SqlDataAdapter(cmd); DataTable dt new DataTable(); adapter.Fill(dt); return dt; } } public static int Execute(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(ConnStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteNonQuery(); } } }参数说明params SqlParameter[]让调用方可以直接写DbHelper.Query(sql, new SqlParameter(Name, 张三))或者传多个参数非常灵活。ConnStr放成私有只读字段四个窗口都不再各自维护连接字符串以后换服务器只需改这一个地方。事务场景要单独注意——Execute方法内部是自己开连接所以跨多条语句的事务没法直接用这个封装。我的处理原则是单表 CRUD 用DbHelper足够如果哪天需要「先删主表再删子表」这种原子操作就把连接对象和事务对象在业务层手动创建不强行套封装。做完这一步再回头看这个 StudentM 工程它其实已经把 WPF 数据库交互的核心链路完整展示出来了连接、命令、适配器、数据表、绑定、参数化、异常处理。如果再往前走一步就是 MVVM 了——把DataTable换成ObservableCollectionStudentModel把按钮事件换成ICommand把DbHelper.Query的结果在 ViewModel 里转换成实体集。像 HandyControl 这类控件库也能直接换皮肤不影响底层数据访问代码。但对数据库交互的理解永远从这段最朴素的SqlConnection开始最扎实。我自己早期用 WPF 写工具时也习惯把代码全堆在Button_Click里直到一次给同事交接代码对方看到 Query 窗口里功能和事件揉在一起的几百行皱了下眉头。从那以后我每次新建窗口都强制先抽一个LoadData()再写按钮事件数据操作一律走DbHelper——这个习惯让我后来改需求时少踩了太多坑。希望这份拆解也能帮到你。本文还有配套的精品资源点击获取
返回列表