青少年编程与数学 02-020 C#程序设计基础 13课题、数据访问
- 一、使用数据库
- 1. 使用ADO.NET连接数据库
- 连接SQL Server示例
- 连接其他数据库
- 2. 使用Entity Framework (EF Core)
- 安装EF Core
- 示例代码
- 3. 数据绑定到WinForms控件
- DataGridView绑定
- 简单控件绑定
- 4. 使用本地数据库(SQLite)
- 安装SQLite
- 示例代码
- 5. 最佳实践
- 6. 配置文件存储连接字符串
- 二、使用SQL Server数据库
- 1. 优势分析
- 2. 劣势与挑战
- 3. 适用场景评价
- 4. 技术实现建议
- 5. 替代方案对比
- 6. 结论
- 三、.NET Framework ADO.NET 详解
- (一)ADO.NET 核心组件
- 1. 两大核心组件集
- 2. 核心对象模型
- (二)详细使用指南
- 1. 连接数据库 (SqlConnection)
- 2. 执行命令 (SqlCommand)
- 基本CRUD操作
- 参数化查询的重要性
- 3. 数据读取器 (SqlDataReader)
- 4. 数据适配器与数据集 (SqlDataAdapter + DataSet)
- (三)高级主题
- 1. 事务处理
- 2. 存储过程调用
- 3. 批量操作
- 4. 异步操作
- (四)性能优化技巧
- (五)安全实践
- (六)ADO.NET vs Entity Framework
- (七)实际应用示例 - WinForms数据绑定
- 四、.NET 8 ADO.NET 详解
- (一)现代 ADO.NET 架构
- 1. 组件模型升级
- 2. 核心命名空间
- (二)连接管理与配置
- 1. 现代连接字符串配置
- 2. 高级连接选项
- (三)命令执行增强
- 1. 参数化查询现代化
- 2. 批量操作优化
- (四)数据读取高级技术
- 1. 现代DataReader模式
- 2. 结果集流式处理
- (五)事务与隔离级别
- 1. 现代事务管理
- 2. 分布式事务支持
- (六)安全增强特性
- 1. Always Encrypted 集成
- 2. Azure AD 集成
- (七)性能优化技巧
- 1. 连接池优化配置
- 2. 命令准备与重用
- (八)诊断与监控
- 1. 活动追踪配置
- 2. 获取性能统计
- (九)与 Entity Framework Core 集成
- 1. 混合使用原始SQL
- 2. 批量操作集成
- (十)迁移指南
- 从传统 ADO.NET 迁移步骤:
- 小结
- 五、综合示例
- 1. 项目设置
- 1.1 创建项目
- 2. 完整代码实现
- 2.1 数据库帮助类 (`Data/DatabaseHelper.cs`)
- 2.2 主窗体 (`MainForm.cs`)
- 2.3 产品详情窗体 (`ProductDetailForm.cs`)
- 2.4 分类管理窗体 (`CategoryForm.cs`)
- 2.5 订单处理窗体 (`OrderForm.cs`)
- 3. 主要特点
- 4. 与传统ADO.NET的区别
- 总结
摘要:本文深入探讨了在C# WinForms应用程序中使用ADO.NET和Entity Framework Core进行数据库操作的方法。详细介绍了ADO.NET的核心组件、使用方法、性能优化和安全实践,以及Entity Framework Core的安装和使用。文章还提供了数据绑定到WinForms控件的示例,并展示了.NET 8中ADO.NET的现代化改进和性能优化技巧。通过一个完整的WinForms应用程序示例,展示了如何使用ADO.NET进行数据库操作,为开发者提供了实用的技术参考。
关键词:C# WinForms、ADO.NET、Entity Framework Core、数据库操作、数据绑定、安全实践、.NET 8
AI助手:DeepSeek、Kimi
一、使用数据库
在C# WinForms应用程序中使用数据库是常见的需求,下面我将介绍几种主要的方法:
1. 使用ADO.NET连接数据库
ADO.NET是.NET框架中访问数据库的核心技术。
连接SQL Server示例
using System.Data.SqlClient;
// 连接字符串
string connectionString = "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;";
// 执行查询
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// 执行SQL命令
string sql = "SELECT * FROM Customers";
using (SqlCommand command = new SqlCommand(sql, connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader["CustomerName"].ToString());
}
}
}
}
连接其他数据库
对于MySQL、Oracle等数据库,需要使用相应的提供程序(如MySql.Data.MySqlClient)。
2. 使用Entity Framework (EF Core)
EF Core是一个流行的ORM框架,可以简化数据库操作。
安装EF Core
通过NuGet包管理器安装:
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer (或其他数据库提供程序)
示例代码
// 定义模型
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// 创建DbContext
public class AppDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("your_connection_string");
}
}
// 使用示例
using (var context = new AppDbContext())
{
// 查询
var customers = context.Customers.Where(c => c.Name.Contains("John")).ToList();
// 添加
var newCustomer = new Customer { Name = "Alice", Email = "alice@example.com" };
context.Customers.Add(newCustomer);
context.SaveChanges();
// 更新
var customer = context.Customers.First();
customer.Email = "newemail@example.com";
context.SaveChanges();
}
3. 数据绑定到WinForms控件
DataGridView绑定
// 使用ADO.NET DataTable
DataTable dataTable = new DataTable();
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Customers", connection);
adapter.Fill(dataTable);
}
dataGridView1.DataSource = dataTable;
// 使用EF Core
dataGridView1.DataSource = dbContext.Customers.ToList();
简单控件绑定
// 绑定到TextBox
textBoxName.DataBindings.Add("Text", dataTable, "CustomerName");
4. 使用本地数据库(SQLite)
对于小型应用程序,SQLite是一个不错的选择。
安装SQLite
通过NuGet安装:
- Microsoft.Data.Sqlite
- Microsoft.EntityFrameworkCore.Sqlite
示例代码
public class AppDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=mydatabase.db");
}
}
5. 最佳实践
- 连接管理:始终使用
using
语句确保连接和命令对象被正确释放 - 异常处理:捕获和处理数据库异常(SqlException等)
- 参数化查询:防止SQL注入
string sql = "SELECT * FROM Customers WHERE Id = @Id"; command.Parameters.AddWithValue("@Id", customerId);
- 异步操作:对于长时间运行的数据库操作,使用异步方法
var customers = await dbContext.Customers.ToListAsync();
- 配置管理:将连接字符串存储在app.config或appsettings.json中
6. 配置文件存储连接字符串
在app.config中:
<configuration>
<connectionStrings>
<add name="MyDb" connectionString="Server=.;Database=MyDb;Integrated Security=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
在代码中读取:
string connectionString = ConfigurationManager.ConnectionStrings["MyDb"].ConnectionString;
以上是在C# WinForms应用程序中使用数据库的主要方法。根据项目规模和需求,可以选择ADO.NET直接操作或使用ORM框架如EF Core来简化开发。
二、使用SQL Server数据库
在C# WinForms应用程序中使用SQL Server数据库是一种常见且强大的组合,以下是全面评价:
1. 优势分析
高性能与稳定性
- SQL Server是成熟的商业数据库,特别适合处理大量数据和复杂查询
- 对于企业级WinForms应用,能够提供可靠的性能表现
- 支持存储过程、触发器等高级功能,可将业务逻辑部分放在数据库层
与.NET生态完美集成
- ADO.NET原生支持SQL Server,提供最优化的访问方式
- Entity Framework对SQL Server的支持最全面,功能实现最完整
- Visual Studio提供SQL Server的直接工具支持(Server Explorer等)
开发效率高
- 丰富的可视化工具(SQL Server Management Studio)
- LINQ to SQL和Entity Framework可显著减少数据访问层代码量
- 数据集设计器(DataSet Designer)可快速创建类型化数据集
安全特性完善
- 集成Windows身份验证(Integrated Security)
- 细粒度的权限控制
- 数据加密功能完善
企业级功能支持
- 事务处理能力强
- 支持分布式查询
- 完善的备份恢复机制
2. 劣势与挑战
部署复杂度
- 需要安装SQL Server(Express版可减轻此问题)
- 相比SQLite等文件数据库,部署更复杂
- 需要管理数据库服务器
成本考量
- 标准版和企业版成本较高(但Express版免费)
- 需要专门的数据库服务器硬件
不适合小型应用
- 对于单机小型应用可能"杀鸡用牛刀"
- 简单的数据存储需求使用SQLite或Access可能更合适
版本兼容性
- 不同SQL Server版本间有时存在兼容性问题
- 连接字符串配置可能需要调整
3. 适用场景评价
✅ 推荐使用场景
- 企业级内部管理系统(ERP、CRM等)
- 多用户并发访问的系统
- 需要处理复杂业务逻辑和大量数据的应用
- 已有SQL Server基础设施的环境
❌ 不推荐场景
- 单机小型工具类应用
- 需要简单部署的共享软件
- 移动端或需要离线使用的应用
4. 技术实现建议
架构选择
- 对于简单应用:ADO.NET + DataSet
- 对于中型应用:Entity Framework Core
- 对于复杂企业应用:Repository模式 + Dapper
连接管理最佳实践
// 使用配置文件的连接字符串
string connStr = ConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;
// 使用using确保资源释放
using (SqlConnection conn = new SqlConnection(connStr))
{
await conn.OpenAsync();
// 数据库操作
}
性能优化建议
- 使用异步方法(async/await)避免UI冻结
- 合理使用连接池
- 对常用查询添加索引
- 考虑使用存储过程处理复杂逻辑
安全建议
- 永远使用参数化查询防止SQL注入
- 不要硬编码连接字符串
- 实施最小权限原则
5. 替代方案对比
特性 | SQL Server | SQLite | MySQL | PostgreSQL |
---|---|---|---|---|
部署难度 | 中 | 易 | 中 | 中 |
性能 | 高 | 中 | 高 | 高 |
适合规模 | 中大型 | 小型 | 中大 | 中大 |
.NET支持 | 最佳 | 好 | 好 | 好 |
成本 | 中高(Express免费) | 免费 | 免费 | 免费 |
6. 结论
C# WinForms + SQL Server组合在适合的场景下是非常强大的选择,特别是对于企业级Windows桌面应用开发。它提供了性能、可靠性和开发效率的良好平衡,但开发者需要考虑部署复杂度和成本因素。对于不适用SQL Server的场景,可以考虑SQLite(单机简单应用)或其他替代方案。
三、.NET Framework ADO.NET 详解
ADO.NET 是 .NET Framework 中用于数据访问的核心组件,为 C# WinForms 应用程序提供了一套强大而灵活的数据库操作方式。下面我将从基础到高级全面解析 ADO.NET。
(一)ADO.NET 核心组件
1. 两大核心组件集
-
.NET Framework 数据提供程序 (专用于特定数据库类型)
- SQL Server 提供程序:
System.Data.SqlClient
- OLE DB 提供程序:
System.Data.OleDb
- ODBC 提供程序:
System.Data.Odbc
- Oracle 提供程序:
System.Data.OracleClient
(已过时,推荐使用 Oracle 官方提供程序)
- SQL Server 提供程序:
-
DataSet (独立于数据库的内存数据容器)
2. 核心对象模型
连接对象 (Connection) → 命令对象 (Command)
↓
数据读取器 (DataReader) ← 执行查询
↓
数据适配器 (DataAdapter) → DataSet/DataTable
(二)详细使用指南
1. 连接数据库 (SqlConnection)
// 基本连接字符串格式
string connStr = "Server=服务器名;Database=数据库名;User Id=用户名;Password=密码;";
// 使用Windows身份验证
string connStr = "Server=服务器名;Database=数据库名;Integrated Security=True;";
// 创建连接对象
using (SqlConnection connection = new SqlConnection(connStr))
{
try
{
connection.Open();
// 执行数据库操作...
}
catch (SqlException ex)
{
MessageBox.Show($"数据库错误: {ex.Message}");
}
}
最佳实践:
- 始终使用
using
语句确保连接关闭 - 将连接字符串存储在配置文件中
- 启用连接池(默认开启)
2. 执行命令 (SqlCommand)
基本CRUD操作
// 插入数据
string insertSql = "INSERT INTO Customers (Name, Email) VALUES (@Name, @Email)";
using (SqlCommand cmd = new SqlCommand(insertSql, connection))
{
cmd.Parameters.AddWithValue("@Name", "张三");
cmd.Parameters.AddWithValue("@Email", "zhangsan@example.com");
int rowsAffected = cmd.ExecuteNonQuery();
}
// 更新数据
string updateSql = "UPDATE Customers SET Email = @Email WHERE Id = @Id";
using (SqlCommand cmd = new SqlCommand(updateSql, connection))
{
cmd.Parameters.AddWithValue("@Email", "new@example.com");
cmd.Parameters.AddWithValue("@Id", 1);
cmd.ExecuteNonQuery();
}
// 删除数据
string deleteSql = "DELETE FROM Customers WHERE Id = @Id";
using (SqlCommand cmd = new SqlCommand(deleteSql, connection))
{
cmd.Parameters.AddWithValue("@Id", 1);
cmd.ExecuteNonQuery();
}
参数化查询的重要性
- 防止SQL注入攻击
- 提高查询性能(执行计划可重用)
- 自动处理数据类型转换
3. 数据读取器 (SqlDataReader)
string sql = "SELECT Id, Name, Email FROM Customers";
using (SqlCommand cmd = new SqlCommand(sql, connection))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int id = reader.GetInt32(0); // 按索引获取
string name = reader["Name"].ToString(); // 按列名获取
string email = reader.GetString(2); // 使用类型特定方法
// 处理数据...
}
}
}
特点:
- 只进、只读的快速数据访问
- 非常节省内存(不缓存整个结果集)
- 需要保持连接打开状态
4. 数据适配器与数据集 (SqlDataAdapter + DataSet)
// 创建适配器和数据集
SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Customers", connection);
DataSet ds = new DataSet();
// 填充数据
adapter.Fill(ds, "Customers"); // "Customers"是DataTable名称
// 访问数据
foreach (DataRow row in ds.Tables["Customers"].Rows)
{
Console.WriteLine($"{row["Id"]}: {row["Name"]}");
}
// 更新数据
DataTable dt = ds.Tables["Customers"];
dt.Rows[0]["Name"] = "修改后的名字";
// 配置命令生成器自动生成更新命令
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
adapter.Update(dt);
DataSet 特点:
- 断开式数据访问模型
- 内存中的数据缓存
- 可以包含多个表和数据关系
- 支持数据绑定
(三)高级主题
1. 事务处理
using (SqlTransaction transaction = connection.BeginTransaction())
{
try
{
using (SqlCommand cmd = connection.CreateCommand())
{
cmd.Transaction = transaction;
cmd.CommandText = "INSERT INTO Orders...";
cmd.ExecuteNonQuery();
cmd.CommandText = "UPDATE Inventory...";
cmd.ExecuteNonQuery();
transaction.Commit(); // 提交事务
}
}
catch
{
transaction.Rollback(); // 回滚事务
throw;
}
}
2. 存储过程调用
using (SqlCommand cmd = new SqlCommand("sp_GetCustomerOrders", connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@CustomerId", 123);
using (SqlDataReader reader = cmd.ExecuteReader())
{
// 处理结果...
}
}
3. 批量操作
// 使用SqlBulkCopy高效导入数据
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName = "TargetTable";
bulkCopy.WriteToServer(dataTable); // 可以从DataTable、IDataReader等导入
}
4. 异步操作
// 异步方法
public async Task<List<Customer>> GetCustomersAsync()
{
List<Customer> customers = new List<Customer>();
using (SqlConnection conn = new SqlConnection(connStr))
{
await conn.OpenAsync();
using (SqlCommand cmd = new SqlCommand("SELECT * FROM Customers", conn))
{
using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
customers.Add(new Customer
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Email = reader.GetString(2)
});
}
}
}
}
return customers;
}
(四)性能优化技巧
-
连接池优化
- 默认已启用,不要手动开关池
- 保持连接字符串一致(区分大小写)
-
命令对象重用
- 对于重复执行的命令,重用SqlCommand对象
- 使用
Prepare()
方法预编译命令
-
适当选择数据访问方式
- 大量只读数据 → DataReader
- 离线编辑数据 → DataAdapter + DataSet
- 单值查询 → ExecuteScalar
-
批处理操作
- 合并多个操作为一个SQL语句
- 使用表值参数(TVP)传递多行数据
(五)安全实践
-
防范SQL注入
- 永远使用参数化查询
- 不要拼接SQL字符串
// 错误做法(危险!) string sql = "SELECT * FROM Users WHERE Name = '" + userName + "'"; // 正确做法 string sql = "SELECT * FROM Users WHERE Name = @UserName"; cmd.Parameters.AddWithValue("@UserName", userName);
-
连接字符串安全
- 不要硬编码在代码中
- 使用配置文件和加密技术
-
最小权限原则
- 应用程序使用最小必要权限的数据库账户
(六)ADO.NET vs Entity Framework
特性 | ADO.NET | Entity Framework |
---|---|---|
抽象级别 | 低级别,接近SQL | 高级别,面向对象 |
开发速度 | 较慢,需要更多代码 | 更快,自动生成很多代码 |
性能 | 更高效,更可控 | 良好,但可能有额外开销 |
灵活性 | 完全控制SQL | 有限制,但可以混合使用原始SQL |
学习曲线 | 需要了解SQL | 需要理解ORM概念 |
适用场景 | 高性能需求,复杂查询 | 快速开发,简单到中等复杂度应用 |
(七)实际应用示例 - WinForms数据绑定
// 绑定DataGridView
public void LoadCustomersToGrid()
{
string sql = "SELECT * FROM Customers";
SqlDataAdapter adapter = new SqlDataAdapter(sql, connectionString);
DataTable dt = new DataTable();
adapter.Fill(dt);
dataGridView1.DataSource = dt;
// 设置列标题
dataGridView1.Columns["Id"].HeaderText = "编号";
dataGridView1.Columns["Name"].HeaderText = "姓名";
dataGridView1.Columns["Email"].HeaderText = "电子邮箱";
}
// 主从表绑定
public void BindMasterDetail()
{
DataSet ds = new DataSet();
// 主表
SqlDataAdapter masterAdapter = new SqlDataAdapter("SELECT * FROM Customers", connectionString);
masterAdapter.Fill(ds, "Customers");
// 从表
SqlDataAdapter detailAdapter = new SqlDataAdapter("SELECT * FROM Orders", connectionString);
detailAdapter.Fill(ds, "Orders");
// 建立关系
DataRelation relation = new DataRelation("CustOrders",
ds.Tables["Customers"].Columns["Id"],
ds.Tables["Orders"].Columns["CustomerId"]);
ds.Relations.Add(relation);
// 绑定
dataGridView1.DataSource = ds.Tables["Customers"];
dataGridView2.DataSource = ds.Tables["Customers"];
dataGridView2.DataMember = "CustOrders"; // 显示关联的订单
}
ADO.NET 为 WinForms 数据库开发提供了坚实的基础,理解其核心概念和最佳实践将帮助您构建高效、安全的数据库应用程序。对于新项目,可以考虑结合使用 ADO.NET 和更高级的 ORM 工具以获得更好的开发体验。
四、.NET 8 ADO.NET 详解
ADO.NET 在 .NET 8 中经过现代化改造,保留了核心数据访问能力的同时,引入了诸多改进和新特性。以下是对 .NET 8 中 ADO.NET 的全面详解:
(一)现代 ADO.NET 架构
1. 组件模型升级
- 模块化设计:通过 NuGet 分发 (
Microsoft.Data.SqlClient
) - 分层架构:
2. 核心命名空间
Microsoft.Data.SqlClient
(替代旧的System.Data.SqlClient
)System.Data.Common
(通用接口)Microsoft.Data.SqlClient.AlwaysEncrypted
(专门加密支持)
(二)连接管理与配置
1. 现代连接字符串配置
// 推荐的安全连接字符串
var connectionString = new SqlConnectionStringBuilder
{
DataSource = "your-server.database.windows.net",
InitialCatalog = "your-database",
UserID = "your-username",
Password = "your-password",
Encrypt = SqlConnectionEncryptOption.Mandatory, // 强制加密
TrustServerCertificate = false, // 生产环境应为false
ConnectTimeout = 15,
Pooling = true, // 启用连接池
MaxPoolSize = 100 // 连接池大小
}.ConnectionString;
2. 高级连接选项
// 使用连接属性配置
var connection = new SqlConnection(connectionString)
{
AccessToken = azureAdToken, // Azure AD集成
RetryLogicProvider = SqlConfigurableRetryFactory.CreateFixedRetryProvider(
new SqlRetryLogicOption
{
NumberOfTries = 3,
DeltaTime = TimeSpan.FromSeconds(1)
})
};
(三)命令执行增强
1. 参数化查询现代化
// 现代参数化查询(防注入)
var command = new SqlCommand(
"SELECT * FROM Products WHERE CategoryId = @Category AND Price > @MinPrice",
connection);
// 强类型参数添加(推荐方式)
command.Parameters.Add(new SqlParameter
{
ParameterName = "@Category",
SqlDbType = SqlDbType.Int,
Value = categoryId
});
// 简化方式(自动推断类型)
command.Parameters.AddWithValue("@MinPrice", minimumPrice);
2. 批量操作优化
// 高性能批量插入(使用SqlBulkCopy)
using var bulkCopy = new SqlBulkCopy(connection)
{
DestinationTableName = "Products",
BatchSize = 1000,
BulkCopyTimeout = 30
};
// 从DataReader流式传输
await bulkCopy.WriteToServerAsync(dataReader);
// 从DataTable批量写入
await bulkCopy.WriteToServerAsync(dataTable);
(四)数据读取高级技术
1. 现代DataReader模式
await using var reader = await command.ExecuteReaderAsync();
// 获取列元数据(避免魔法字符串)
var idOrdinal = reader.GetOrdinal("ProductId");
var nameOrdinal = reader.GetOrdinal("ProductName");
while (await reader.ReadAsync())
{
var product = new Product
{
// 按序号访问(性能最优)
Id = reader.GetInt32(idOrdinal),
// 处理可能为NULL的值
Name = reader.IsDBNull(nameOrdinal) ? null : reader.GetString(nameOrdinal),
// 新型GetFieldValue方法
Price = await reader.GetFieldValueAsync<decimal>(2)
};
}
2. 结果集流式处理
// 返回IAsyncEnumerable实现流式处理
public async IAsyncEnumerable<Product> StreamLargeProducts()
{
await using var reader = await command.ExecuteReaderAsync(
CommandBehavior.SequentialAccess); // 大对象优化
while (await reader.ReadAsync())
{
yield return new Product
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
// 分块读取大文本/二进制数据
Description = await reader.GetTextReader(2).ReadToEndAsync()
};
}
}
(五)事务与隔离级别
1. 现代事务管理
// 使用TransactionScope(推荐)
await using var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.Snapshot,
Timeout = TransactionManager.DefaultTimeout
},
TransactionScopeAsyncFlowOption.Enabled);
try
{
// 多个操作...
await connection1.ExecuteNonQueryAsync(cmd1);
await connection2.ExecuteNonQueryAsync(cmd2);
scope.Complete(); // 提交事务
}
catch
{
// 自动回滚
}
2. 分布式事务支持
// 启用MSDTC或使用新式分布式事务
var options = new SqlConnectionOptions
{
EnlistDistributedTransaction = true
};
await using var conn = new SqlConnection(connectionString, options);
(六)安全增强特性
1. Always Encrypted 集成
// 配置加密列处理
var connectionString = "Server=...; Column Encryption Setting=enabled;";
var command = new SqlCommand("SELECT * FROM Customers WHERE SSN = @SSN", connection);
// 自动加密/解密
command.Parameters.AddWithValue("@SSN", "123-45-6789");
// 查询结果自动解密
await using var reader = await command.ExecuteReaderAsync();
2. Azure AD 集成
// 使用Azure AD令牌认证
var connection = new SqlConnection(connectionString)
{
AccessToken = await GetAzureAdTokenAsync()
};
// 或者使用连接字符串配置
var connStr = "Server=...; Authentication=Active Directory Default;";
(七)性能优化技巧
1. 连接池优化配置
// 在应用程序启动时配置全局连接池
SqlConnection.Configure(
new SqlConnectionConfiguration
{
PoolSettings = new SqlConnectionPoolSettings
{
MaxPoolSize = 200,
MinPoolSize = 10,
LoadBalanceTimeout = 30
}
});
2. 命令准备与重用
// 准备常用命令(查询计划缓存)
var cmd = new SqlCommand("SELECT * FROM Products WHERE Id = @Id", connection);
cmd.Parameters.Add("@Id", SqlDbType.Int);
await cmd.PrepareAsync(); // 显式准备
// 重用命令对象
for (int i = 0; i < ids.Length; i++)
{
cmd.Parameters["@Id"].Value = ids[i];
await using var reader = await cmd.ExecuteReaderAsync();
// ...
}
(八)诊断与监控
1. 活动追踪配置
// 启用诊断监听
using var listener = new SqlClientListener(
new SqlClientListenerOptions
{
CommandTimeout = true,
ConnectionOpen = true,
Statistics = true
});
listener.EventWritten += (sender, e) =>
{
Console.WriteLine($"{e.EventId}: {e.Message}");
};
2. 获取性能统计
connection.StatisticsEnabled = true;
await connection.OpenAsync();
// 执行操作...
var stats = connection.RetrieveStatistics();
Console.WriteLine($"Bytes received: {stats["BytesReceived"]}");
(九)与 Entity Framework Core 集成
1. 混合使用原始SQL
var products = await context.Products
.FromSqlRaw("SELECT * FROM Products WITH (NOLOCK) WHERE Price > {0}", minPrice)
.ToListAsync();
// 使用SqlParameter防止注入
var param = new SqlParameter("@category", categoryId);
var products = await context.Products
.FromSqlRaw("EXEC GetProductsByCategory @category", param)
.AsNoTracking()
.ToListAsync();
2. 批量操作集成
// 使用EF Core + ADO.NET批量插入
var bulkConfig = new BulkConfig
{
BatchSize = 4000,
SqlBulkCopyOptions = SqlBulkCopyOptions.TableLock
};
await context.BulkInsertAsync(products, bulkConfig);
(十)迁移指南
从传统 ADO.NET 迁移步骤:
-
更新NuGet包:
dotnet add package Microsoft.Data.SqlClient
-
替换命名空间:
// 替换 using System.Data.SqlClient; // 为 using Microsoft.Data.SqlClient;
-
异步化改造:
- 将同步方法替换为异步版本
- 在调用链中添加
await
-
安全配置更新:
- 添加
Encrypt
和TrustServerCertificate
选项 - 考虑启用 Always Encrypted
- 添加
-
性能优化调整:
- 配置连接池
- 实现重试逻辑
- 考虑使用批处理操作
小结
.NET 8 中的 ADO.NET 提供了:
- 现代化的异步编程模型
- 增强的安全特性(如 Always Encrypted)
- 深度云集成(Azure AD、托管身份)
- 显著性能优化
- 更好的诊断和监控能力
- 与 EF Core 的无缝集成
虽然基础编程模型保持熟悉,但开发者应充分利用新特性和最佳实践,以构建高性能、安全的现代数据访问层。
五、综合示例
下面是一个完整的.NET 8 WinForms应用程序示例,直接使用ADO.NET DataSet进行数据库操作。
1. 项目设置
1.1 创建项目
dotnet new winforms -n ProductManagementApp
cd ProductManagementApp
dotnet add package Microsoft.Data.SqlClient
2. 完整代码实现
2.1 数据库帮助类 (Data/DatabaseHelper.cs
)
using Microsoft.Data.SqlClient;
using System.Data;
namespace ProductManagementApp.Data
{
public class DatabaseHelper
{
private readonly string _connectionString;
public DatabaseHelper(string connectionString)
{
_connectionString = connectionString;
}
public async Task<SqlConnection> CreateConnectionAsync()
{
var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
return connection;
}
public async Task<DataSet> GetProductsDataSetAsync()
{
var ds = new DataSet();
await using var connection = await CreateConnectionAsync();
// 获取产品数据
var productsAdapter = new SqlDataAdapter(
"SELECT p.*, c.Name as CategoryName FROM Products p LEFT JOIN Categories c ON p.CategoryId = c.CategoryId",
connection);
productsAdapter.Fill(ds, "Products");
// 获取分类数据
var categoriesAdapter = new SqlDataAdapter(
"SELECT * FROM Categories",
connection);
categoriesAdapter.Fill(ds, "Categories");
// 添加关系
ds.Relations.Add("ProductCategory",
ds.Tables["Categories"].Columns["CategoryId"],
ds.Tables["Products"].Columns["CategoryId"]);
return ds;
}
public async Task<int> AddProductAsync(DataRow productRow)
{
await using var connection = await CreateConnectionAsync();
var adapter = new SqlDataAdapter("SELECT * FROM Products WHERE 1=0", connection);
var builder = new SqlCommandBuilder(adapter);
var dt = new DataTable();
adapter.Fill(dt);
var newRow = dt.NewRow();
newRow.ItemArray = productRow.ItemArray;
dt.Rows.Add(newRow);
return await adapter.UpdateAsync(dt);
}
public async Task<int> UpdateProductAsync(DataRow productRow)
{
await using var connection = await CreateConnectionAsync();
var adapter = new SqlDataAdapter("SELECT * FROM Products WHERE ProductId = @ProductId", connection);
adapter.SelectCommand.Parameters.AddWithValue("@ProductId", productRow["ProductId"]);
var builder = new SqlCommandBuilder(adapter);
var dt = new DataTable();
adapter.Fill(dt);
if (dt.Rows.Count == 1)
{
dt.Rows[0].ItemArray = productRow.ItemArray;
return await adapter.UpdateAsync(dt);
}
return 0;
}
public async Task<int> DeleteProductAsync(int productId)
{
await using var connection = await CreateConnectionAsync();
var adapter = new SqlDataAdapter("SELECT * FROM Products WHERE ProductId = @ProductId", connection);
adapter.SelectCommand.Parameters.AddWithValue("@ProductId", productId);
var builder = new SqlCommandBuilder(adapter);
var dt = new DataTable();
adapter.Fill(dt);
if (dt.Rows.Count == 1)
{
dt.Rows[0].Delete();
return await adapter.UpdateAsync(dt);
}
return 0;
}
public async Task<int> AddCategoryAsync(string name, string description)
{
await using var connection = await CreateConnectionAsync();
var adapter = new SqlDataAdapter("SELECT * FROM Categories WHERE 1=0", connection);
var builder = new SqlCommandBuilder(adapter);
var dt = new DataTable();
adapter.Fill(dt);
var newRow = dt.NewRow();
newRow["Name"] = name;
newRow["Description"] = description;
dt.Rows.Add(newRow);
return await adapter.UpdateAsync(dt);
}
public async Task<bool> ProcessOrderAsync(int productId, int quantity)
{
await using var connection = await CreateConnectionAsync();
await using var transaction = await connection.BeginTransactionAsync();
try
{
// 1. 检查库存
var checkCmd = new SqlCommand(
"SELECT StockQuantity FROM Products WHERE ProductId = @ProductId",
connection, transaction);
checkCmd.Parameters.AddWithValue("@ProductId", productId);
var currentStock = (int)await checkCmd.ExecuteScalarAsync();
if (currentStock < quantity)
{
throw new Exception("库存不足");
}
// 2. 更新库存
var updateCmd = new SqlCommand(
"UPDATE Products SET StockQuantity = StockQuantity - @Quantity WHERE ProductId = @ProductId",
connection, transaction);
updateCmd.Parameters.AddWithValue("@ProductId", productId);
updateCmd.Parameters.AddWithValue("@Quantity", quantity);
await updateCmd.ExecuteNonQueryAsync();
// 3. 记录订单 (示例)
// var orderCmd = new SqlCommand("INSERT INTO Orders...", connection, transaction);
// await orderCmd.ExecuteNonQueryAsync();
await transaction.CommitAsync();
return true;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
}
2.2 主窗体 (MainForm.cs
)
using ProductManagementApp.Data;
using System.Data;
namespace ProductManagementApp
{
public partial class MainForm : Form
{
private readonly DatabaseHelper _dbHelper;
private DataSet _dataSet;
private DataView _productsView;
public MainForm()
{
InitializeComponent();
// 配置连接字符串 (实际项目中应该放在配置文件中)
var connectionString = "Server=(local);Database=ProductDB;Integrated Security=true;Encrypt=False;";
_dbHelper = new DatabaseHelper(connectionString);
}
private async void MainForm_Load(object sender, EventArgs e)
{
await LoadDataAsync();
ConfigureDataGridView();
ConfigureCategoryComboBox();
}
private async Task LoadDataAsync()
{
try
{
_dataSet = await _dbHelper.GetProductsDataSetAsync();
_productsView = new DataView(_dataSet.Tables["Products"]);
dataGridView1.DataSource = _productsView;
}
catch (SqlException ex)
{
MessageBox.Show($"数据库错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (Exception ex)
{
MessageBox.Show($"错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void ConfigureDataGridView()
{
dataGridView1.AutoGenerateColumns = false;
dataGridView1.Columns.Clear();
// 添加列
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "ProductId",
HeaderText = "ID",
Name = "colProductId",
ReadOnly = true
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "Name",
HeaderText = "产品名称",
Name = "colName"
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "CategoryName",
HeaderText = "分类",
Name = "colCategory",
ReadOnly = true
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "Price",
HeaderText = "价格",
Name = "colPrice",
DefaultCellStyle = new DataGridViewCellStyle { Format = "C2" }
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "StockQuantity",
HeaderText = "库存",
Name = "colStock"
});
// 双击事件
dataGridView1.CellDoubleClick += DataGridView1_CellDoubleClick;
}
private void ConfigureCategoryComboBox()
{
if (_dataSet?.Tables["Categories"] != null)
{
cmbCategories.DataSource = _dataSet.Tables["Categories"];
cmbCategories.DisplayMember = "Name";
cmbCategories.ValueMember = "CategoryId";
}
}
private void DataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0)
{
var row = ((DataRowView)_productsView[e.RowIndex]).Row;
ShowProductDetail(row);
}
}
private void ShowProductDetail(DataRow productRow)
{
var detailForm = new ProductDetailForm(_dbHelper, productRow);
if (detailForm.ShowDialog() == DialogResult.OK)
{
_productsView.Table.AcceptChanges();
dataGridView1.Refresh();
}
}
private async void btnAdd_Click(object sender, EventArgs e)
{
if (_dataSet == null) return;
var newRow = _dataSet.Tables["Products"].NewRow();
newRow["Name"] = txtName.Text;
newRow["Description"] = txtDescription.Text;
newRow["Price"] = decimal.Parse(txtPrice.Text);
newRow["StockQuantity"] = (int)nudStock.Value;
newRow["CategoryId"] = cmbCategories.SelectedValue;
newRow["CreatedDate"] = DateTime.Now;
try
{
int result = await _dbHelper.AddProductAsync(newRow);
if (result > 0)
{
MessageBox.Show("产品添加成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
await LoadDataAsync();
ClearInputs();
}
}
catch (Exception ex)
{
MessageBox.Show($"添加产品失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void ClearInputs()
{
txtName.Clear();
txtDescription.Clear();
txtPrice.Clear();
nudStock.Value = 0;
if (cmbCategories.Items.Count > 0)
cmbCategories.SelectedIndex = 0;
}
private async void btnRefresh_Click(object sender, EventArgs e)
{
await LoadDataAsync();
}
private async void btnAddCategory_Click(object sender, EventArgs e)
{
var categoryForm = new CategoryForm(_dbHelper);
if (categoryForm.ShowDialog() == DialogResult.OK)
{
await LoadDataAsync();
ConfigureCategoryComboBox();
}
}
private async void btnProcessOrder_Click(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count > 0)
{
var row = ((DataRowView)dataGridView1.SelectedRows[0].DataBoundItem).Row;
int productId = (int)row["ProductId"];
int currentStock = (int)row["StockQuantity"];
using var orderForm = new OrderForm(productId, currentStock);
if (orderForm.ShowDialog() == DialogResult.OK)
{
try
{
bool success = await _dbHelper.ProcessOrderAsync(
productId, orderForm.Quantity);
if (success)
{
MessageBox.Show("订单处理成功!", "成功",
MessageBoxButtons.OK, MessageBoxIcon.Information);
await LoadDataAsync();
}
}
catch (Exception ex)
{
MessageBox.Show($"订单处理失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
else
{
MessageBox.Show("请先选择一个产品", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
2.3 产品详情窗体 (ProductDetailForm.cs
)
using ProductManagementApp.Data;
using System.Data;
namespace ProductManagementApp
{
public partial class ProductDetailForm : Form
{
private readonly DatabaseHelper _dbHelper;
private readonly DataRow _productRow;
public ProductDetailForm(DatabaseHelper dbHelper, DataRow productRow)
{
InitializeComponent();
_dbHelper = dbHelper;
_productRow = productRow;
InitializeData();
}
private void InitializeData()
{
txtProductId.Text = _productRow["ProductId"].ToString();
txtName.Text = _productRow["Name"].ToString();
txtDescription.Text = _productRow["Description"].ToString();
txtPrice.Text = Convert.ToDecimal(_productRow["Price"]).ToString("N2");
nudStock.Value = Convert.ToInt32(_productRow["StockQuantity"]);
dtpCreatedDate.Value = Convert.ToDateTime(_productRow["CreatedDate"]);
}
private async void btnSave_Click(object sender, EventArgs e)
{
_productRow["Name"] = txtName.Text;
_productRow["Description"] = txtDescription.Text;
_productRow["Price"] = decimal.Parse(txtPrice.Text);
_productRow["StockQuantity"] = (int)nudStock.Value;
try
{
int result = await _dbHelper.UpdateProductAsync(_productRow);
if (result > 0)
{
MessageBox.Show("产品更新成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
DialogResult = DialogResult.OK;
Close();
}
else
{
MessageBox.Show("更新产品失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"更新产品失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async void btnDelete_Click(object sender, EventArgs e)
{
if (MessageBox.Show("确定要删除这个产品吗?", "确认",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
try
{
int productId = (int)_productRow["ProductId"];
int result = await _dbHelper.DeleteProductAsync(productId);
if (result > 0)
{
MessageBox.Show("产品删除成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
DialogResult = DialogResult.OK;
Close();
}
else
{
MessageBox.Show("删除产品失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"删除产品失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
}
2.4 分类管理窗体 (CategoryForm.cs
)
using ProductManagementApp.Data;
namespace ProductManagementApp
{
public partial class CategoryForm : Form
{
private readonly DatabaseHelper _dbHelper;
public CategoryForm(DatabaseHelper dbHelper)
{
InitializeComponent();
_dbHelper = dbHelper;
}
private async void btnSave_Click(object sender, EventArgs e)
{
try
{
int result = await _dbHelper.AddCategoryAsync(txtName.Text, txtDescription.Text);
if (result > 0)
{
MessageBox.Show("分类添加成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
DialogResult = DialogResult.OK;
Close();
}
}
catch (Exception ex)
{
MessageBox.Show($"添加分类失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
2.5 订单处理窗体 (OrderForm.cs
)
namespace ProductManagementApp
{
public partial class OrderForm : Form
{
public int Quantity => (int)nudQuantity.Value;
public OrderForm(int productId, int currentStock)
{
InitializeComponent();
lblProductId.Text = productId.ToString();
lblStock.Text = currentStock.ToString();
nudQuantity.Maximum = currentStock;
}
private void btnOK_Click(object sender, EventArgs e)
{
if (nudQuantity.Value > 0)
{
DialogResult = DialogResult.OK;
Close();
}
else
{
MessageBox.Show("请输入有效的数量", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
}
3. 主要特点
-
纯ADO.NET实现:
- 完全使用DataSet/DataTable进行数据操作
- 使用SqlDataAdapter进行数据填充和更新
- 利用SqlCommandBuilder自动生成CRUD命令
-
断开式数据访问:
- 数据加载到内存后断开连接
- 修改后批量提交更改
-
数据绑定:
- DataGridView直接绑定到DataView
- ComboBox绑定到DataTable
-
事务处理:
- 订单处理演示完整的事务操作
- 使用SqlTransaction确保数据一致性
-
异步编程:
- 所有数据库操作都使用异步方法
- 避免UI线程阻塞
4. 与传统ADO.NET的区别
-
现代异步API:
- 使用
ExecuteNonQueryAsync
等异步方法 SqlDataAdapter
新增异步方法
- 使用
-
连接管理:
- 使用
await using
确保资源释放 - 自动连接池管理
- 使用
-
安全增强:
- 默认推荐加密连接
- 参数化查询防止SQL注入
这个示例展示了如何在.NET 8 WinForms应用程序中完全不依赖Dapper等ORM,直接使用ADO.NET DataSet进行数据库操作,同时保持了现代编程实践如异步编程和依赖注入。
总结
本文全面介绍了在C# WinForms应用程序中使用数据库的技术和方法,重点聚焦于ADO.NET和Entity Framework Core两大主流技术栈。首先,文章详细阐述了ADO.NET的核心组件和使用方法,包括连接数据库、执行命令、数据读取、数据适配器与数据集的使用等,并提供了性能优化和安全实践的建议。接着,文章深入探讨了Entity Framework Core的安装、使用以及与ADO.NET的对比,展示了ORM框架在简化开发中的优势。此外,文章还介绍了如何将数据绑定到WinForms控件,如DataGridView,并提供了使用本地数据库SQLite的示例。在.NET 8环境下,文章详细解析了ADO.NET的现代化改进,包括新的连接管理、命令执行增强、数据读取技术、事务处理、安全增强特性以及性能优化技巧,并通过一个完整的WinForms应用程序示例,展示了如何使用ADO.NET进行数据库操作。最后,文章对比了ADO.NET和Entity Framework Core的适用场景,为开发者提供了技术选型的参考。