9.1 ADO.NET基础
1. ADO.NET概述
ADO.NET是.NET Framework中用于数据访问的核心组件,提供了一组类库用于连接数据库、执行命令和检索结果。它是现代C#应用程序与数据库交互的标准方式。
关键特性:
- 断开式数据访问模型
- 与XML紧密集成
- 高性能数据访问
- 支持多种数据源
2. ADO.NET核心组件
2.1 数据提供程序(Data Providers)
| 提供程序名称 | 对应数据库 | 核心类前缀 |
|---|---|---|
| SqlClient | SQL Server | Sql |
| OleDb | OLE DB兼容 | OleDb |
| Odbc | ODBC兼容 | Odbc |
| OracleClient | Oracle | Oracle |
2.2 核心类
- Connection:建立与数据源的连接
- Command:执行SQL命令或存储过程
- DataReader:提供只进、只读的数据流
- DataAdapter:在数据源和DataSet之间架桥
- DataSet:内存中的数据库表示
- DataTable:内存中的表表示
3. 基本数据访问流程
3.1 连接数据库
using System.Data.SqlClient;
string connectionString = "Server=myServer;Database=myDB;User Id=myUser;Password=myPass;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// 执行数据库操作
}
3.2 执行查询
string sql = "SELECT * FROM Customers WHERE Country = @Country";
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@Country", "Germany");
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"ID: {reader["CustomerID"]}, Name: {reader["CompanyName"]}");
}
}
}
3.3 执行非查询操作
string insertSql = "INSERT INTO Products (ProductName, UnitPrice) VALUES (@Name, @Price)";
using (SqlCommand command = new SqlCommand(insertSql, connection))
{
command.Parameters.AddWithValue("@Name", "New Product");
command.Parameters.AddWithValue("@Price", 19.99);
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine($"{rowsAffected} row(s) inserted.");
}
4. 参数化查询
为什么使用参数化查询?
- 防止SQL注入攻击
- 提高性能(查询计划重用)
- 处理特殊字符更安全
参数化查询示例
string sql = "UPDATE Employees SET Salary = @Salary WHERE EmployeeID = @ID";
using (SqlCommand cmd = new SqlCommand(sql, connection))
{
cmd.Parameters.Add("@Salary", SqlDbType.Decimal).Value = 55000.00m;
cmd.Parameters.Add("@ID", SqlDbType.Int).Value = 5;
cmd.ExecuteNonQuery();
}
5. 事务处理
基本事务示例
using (SqlTransaction transaction = connection.BeginTransaction())
{
try
{
// 执行多个命令
command1.Transaction = transaction;
command1.ExecuteNonQuery();
command2.Transaction = transaction;
command2.ExecuteNonQuery();
// 提交事务
transaction.Commit();
}
catch
{
// 回滚事务
transaction.Rollback();
throw;
}
}
6. 断开式数据访问(DataSet/DataTable)
使用DataAdapter填充DataSet
string sql = "SELECT * FROM Products; SELECT * FROM Categories";
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
{
DataSet ds = new DataSet();
adapter.Fill(ds);
DataTable products = ds.Tables[0];
DataTable categories = ds.Tables[1];
// 处理数据...
}
更新数据源
// 修改DataTable中的数据后...
using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Products", connection))
{
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
adapter.Update(ds.Tables[0]); // 将更改同步回数据库
}
7. 最佳实践
连接管理:
- 使用using语句确保连接及时关闭
- 避免频繁打开/关闭连接,考虑连接池
错误处理:
- 捕获特定异常(SqlException)
- 实现重试逻辑
性能优化:
- 使用存储过程代替动态SQL
- 批量操作代替单行操作
- 合理使用连接池
安全考虑:
- 永远不要拼接SQL字符串
- 最小权限原则配置数据库账户
- 加密敏感连接字符串信息
8. 常见问题与解决方案
问题1:连接字符串泄露敏感信息
- 解决方案:使用配置管理器或Azure Key Vault存储连接字符串
问题2:连接泄漏
- 解决方案:确保所有连接对象都在using块中创建
问题3:长时间运行的查询
- 解决方案:设置CommandTimeout属性
问题4:并发冲突
- 解决方案:实现乐观并发控制
9. 现代替代方案
虽然ADO.NET仍然是.NET数据访问的基础,但现代开发中更多使用:
- Entity Framework Core(ORM框架)
- Dapper(轻量级ORM)
- 各种NoSQL客户端库
然而,理解ADO.NET基础对于处理复杂场景和性能关键型应用仍然至关重要。
