1. 环境准备与基础概念
在开始动手之前,我们需要确保开发环境已经准备就绪。首先需要安装Visual Studio(建议2019或2022版本),这是开发WPF应用的标准IDE。同时确保本地或远程已经部署了SQL Server数据库服务,我推荐使用SQL Server 2019 Express版,它对个人开发者完全免费。
很多新手容易忽略一个关键点:数据库连接本质上是通过网络协议进行的通信。就像我们用微信发送消息需要对方的微信号一样,连接数据库也需要几个基本信息:服务器地址(可以是IP或主机名)、数据库名称、登录账号和密码。这里有个小技巧,如果连接本地数据库,服务器地址可以简写为"(local)"或者一个点".",这比写"localhost"更简洁。
注意:在实际项目中,千万不要像示例代码那样把连接字符串明文写在代码里!我们稍后会讲解如何安全地存储数据库凭据。
2. 建立数据库连接
2.1 连接字符串详解
连接字符串是通往数据库的"钥匙",它的基本格式是这样的:
string connStr = "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;";每个参数都有讲究:
- Server:不仅支持IP地址,也可以填主机名
- Database:要连接的具体数据库名称
- User Id/Password:建议使用专门的数据库账号,而非sa超级管理员
- 还可以添加Connect Timeout=30来控制连接超时时间
我遇到过不少连接失败的情况,90%的问题都出在连接字符串上。建议先用SQL Server Management Studio测试连接,确保参数正确后再写到代码中。
2.2 使用SqlConnection对象
创建连接对象的正确姿势应该是这样:
using (SqlConnection conn = new SqlConnection(connStr)) { try { conn.Open(); // 这里执行数据库操作 } catch (SqlException ex) { MessageBox.Show($"连接失败:{ex.Message}"); } }这里有几个关键点:
- 一定要用using语句包裹,确保连接会被自动关闭
- Open()方法会实际建立连接,可能抛出异常
- 连接是稀缺资源,用完要及时释放
我曾经在一个项目中忘记关闭连接,结果导致数据库连接池耗尽,整个系统卡死。这个教训让我养成了总是使用using块的好习惯。
3. 实现CRUD操作
3.1 增删改操作(ExecuteNonQuery)
对于插入、更新和删除操作,我们使用ExecuteNonQuery方法。这里以插入数据为例:
string sql = "INSERT INTO Users (Name, Age) VALUES (@name, @age)"; using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@name", "张三"); cmd.Parameters.AddWithValue("@age", 25); int rowsAffected = cmd.ExecuteNonQuery(); Console.WriteLine($"影响了{rowsAffected}行数据"); }特别注意:
- 一定要使用参数化查询,避免SQL注入攻击
- @name这种参数名可以自定义,但前后要一致
- ExecuteNonQuery返回受影响的行数,可以用来验证操作是否成功
3.2 查询操作(ExecuteReader)
查询数据稍微复杂些,需要用到SqlDataReader:
string sql = "SELECT Id, Name, Age FROM Users WHERE Age > @minAge"; using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@minAge", 18); using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { int id = reader.GetInt32(0); string name = reader.GetString(1); int age = reader.GetInt32(2); Console.WriteLine($"{id}: {name}, {age}岁"); } } }这里有几个实用技巧:
- 按列索引读取比按列名读取效率更高
- 记得检查reader.HasRows判断是否有数据
- 可以用reader.GetOrdinal("列名")先获取列索引
4. WPF界面与数据绑定
4.1 设计基础界面
在MainWindow.xaml中,我们可以设计一个简单的数据管理界面:
<Grid> <DataGrid x:Name="usersGrid" AutoGenerateColumns="False" Margin="10"> <DataGrid.Columns> <DataGridTextColumn Header="ID" Binding="{Binding Id}"/> <DataGridTextColumn Header="姓名" Binding="{Binding Name}"/> <DataGridTextColumn Header="年龄" Binding="{Binding Age}"/> </DataGrid.Columns> </DataGrid> <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Margin="10"> <Button Content="刷新" Click="RefreshData_Click" Width="80" Margin="5"/> <Button Content="新增" Click="AddUser_Click" Width="80" Margin="5"/> <Button Content="删除" Click="DeleteUser_Click" Width="80" Margin="5"/> </StackPanel> </Grid>4.2 实现数据绑定
我们需要创建一个User模型类:
public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }然后在代码中实现数据加载:
private void LoadUsers() { List<User> users = new List<User>(); string sql = "SELECT Id, Name, Age FROM Users"; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand(sql, conn)) { using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { users.Add(new User { Id = reader.GetInt32(0), Name = reader.GetString(1), Age = reader.GetInt32(2) }); } } } } usersGrid.ItemsSource = users; }5. 项目优化与安全实践
5.1 连接字符串加密
把连接字符串写在代码中是非常危险的做法。正确的方式是使用配置文件:
- 在app.config中添加:
<connectionStrings> <add name="MyDB" connectionString="Server=.;Database=TestDB;User ID=appuser;Password=123456;" providerName="System.Data.SqlClient"/> </connectionStrings>- 在代码中读取:
string connStr = ConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;5.2 使用异步方法
长时间运行的数据库操作会阻塞UI线程,应该使用异步版本的方法:
private async Task LoadUsersAsync() { // ... 省略其他代码 using (SqlConnection conn = new SqlConnection(connStr)) { await conn.OpenAsync(); using (SqlCommand cmd = new SqlCommand(sql, conn)) { using (SqlDataReader reader = await cmd.ExecuteReaderAsync()) { // 处理数据 } } } }5.3 异常处理最佳实践
完善的异常处理应该区分不同类型的错误:
try { // 数据库操作 } catch (SqlException ex) when (ex.Number == 18456) { // 登录失败 } catch (SqlException ex) when (ex.Number == 547) { // 外键约束冲突 } catch (SqlException ex) { // 其他SQL错误 } catch (Exception ex) { // 非数据库错误 }6. 完整项目源码解析
现在让我们看一个完整的用户管理系统的实现。这个项目包含以下功能:
- 用户列表展示
- 新增用户
- 编辑用户信息
- 删除用户
- 按条件筛选用户
核心代码结构:
/Models User.cs - 用户模型 /ViewModels UserViewModel.cs - 业务逻辑 /Views MainWindow.xaml - 主界面 App.config - 配置文件在UserViewModel中,我们实现了所有数据库操作逻辑,并通过属性与界面绑定。例如新增用户的方法:
public async Task AddUserAsync(User user) { string sql = @"INSERT INTO Users (Name, Age) VALUES (@Name, @Age); SELECT SCOPE_IDENTITY();"; using (var conn = new SqlConnection(connStr)) { await conn.OpenAsync(); using (var cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@Name", user.Name); cmd.Parameters.AddWithValue("@Age", user.Age); int newId = Convert.ToInt32(await cmd.ExecuteScalarAsync()); user.Id = newId; } } Users.Add(user); // 更新界面列表 }这个实现有几个亮点:
- 使用异步方法避免UI卡顿
- 执行插入后立即获取自增ID
- 自动更新绑定列表
7. 常见问题排查
在实际开发中,你可能会遇到这些问题:
连接超时
- 检查服务器地址是否正确
- 检查网络是否通畅
- 适当增加Connect Timeout的值
登录失败
- 确认用户名密码正确
- 检查SQL Server是否允许混合验证模式
- 确认sa账号是否启用
查询结果为空
- 检查查询条件是否正确
- 先用Management Studio验证数据是否存在
- 检查表名和列名是否拼写正确
性能问题
- 确保频繁使用的查询有适当的索引
- 考虑使用存储过程代替复杂查询
- 批量操作时使用事务
我在项目中曾经遇到一个棘手的问题:在虚拟机上运行的SQL Server经常断开连接。后来发现是虚拟机电源设置导致的网卡休眠,修改电源选项后问题解决。这提醒我们,数据库问题有时可能和环境和配置有关。