====== C# 中的事务处理 (Transactions) ======
事务(Transaction)是数据库操作中的一个核心概念,它确保一组操作要么全部成功,要么全部失败。在 C# 开发中,我们主要通过 ADO.NET、`System.Transactions` 命名空间以及 ORM 框架(如 Entity Framework)来处理事务。
===== 1. 事务的基本概念 (ACID) =====
事务必须具备 ACID 四大特性:
* **原子性 (Atomicity)**: 事务中的所有操作作为一个整体,要么全部完成,要么全部不完成。
* **一致性 (Consistency)**: 事务开始前和结束后,数据库的完整性约束没有被破坏。
* **隔离性 (Isolation)**: 多个事务并发执行时,一个事务的执行不应影响其他事务。
* **持久性 (Durability)**: 事务一旦提交,对数据的修改就是永久的。
===== 2. 使用 ADO.NET 本地事务 (SqlTransaction) =====
这是最基础的事务处理方式,依赖于具体的数据库连接(如 `SqlConnection`)。适用于简单的、基于单一数据库连接的操作。
using System;
using System.Data.SqlClient;
public void ExecuteSqlTransaction(string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// 1. 开启事务
SqlTransaction transaction = connection.BeginTransaction();
// 将命令与事务关联
SqlCommand command = connection.CreateCommand();
command.Transaction = transaction;
try
{
command.CommandText = "INSERT INTO Users (Name) VALUES ('Alice')";
command.ExecuteNonQuery();
command.CommandText = "INSERT INTO Logs (Message) VALUES ('User Created')";
command.ExecuteNonQuery();
// 2. 提交事务
transaction.Commit();
Console.WriteLine("Both records are written to database.");
}
catch (Exception ex)
{
// 3. 发生异常,回滚事务
Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
Console.WriteLine(" Message: {0}", ex.Message);
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
}
}
}
}
**关键点:**
* 必须显式调用 `BeginTransaction()`。
* `SqlCommand` 对象必须设置 `Transaction` 属性,否则会报错。
* 必须在 `try-catch` 块中处理 `Commit()` 和 `Rollback()`。
===== 3. 使用 TransactionScope (隐式事务/分布式事务) =====
`System.Transactions.TransactionScope` 是 .NET 2.0 引入的更高级、更易用的事务模型。它可以自动管理事务的提升(从轻量级事务提升为分布式事务 MSDTC),并且代码更加整洁。
**注意**:在 .NET Core / .NET 5+ 中使用分布式事务需要注意平台兼容性。
using System;
using System.Transactions;
using System.Data.SqlClient;
public void ExecuteTransactionScope(string connStr)
{
// 1. 创建 TransactionScope
// 使用 using 语句块,确保事务在退出时自动处理(未提交则回滚)
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection conn1 = new SqlConnection(connStr))
{
conn1.Open();
SqlCommand cmd1 = new SqlCommand("INSERT INTO Users (Name) VALUES ('Bob')", conn1);
cmd1.ExecuteNonQuery();
}
// 可以在同一个 Scope 中打开第二个连接(甚至连接不同的数据库)
// 如果连接不同数据库,事务会自动升级为分布式事务
using (SqlConnection conn2 = new SqlConnection(connStr))
{
conn2.Open();
SqlCommand cmd2 = new SqlCommand("INSERT INTO Logs (Message) VALUES ('Bob Created')", conn2);
cmd2.ExecuteNonQuery();
}
// 2. 提交事务
// 调用 Complete() 表示事务成功。
// 如果代码在执行到这里之前抛出异常,Complete() 不会被调用,
// using 块结束时会自动回滚。
scope.Complete();
}
}
**TransactionScope 的配置选项:**
你可以通过 `TransactionOptions` 来配置隔离级别和超时时间。
var options = new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted, // 设置隔离级别
Timeout = TimeSpan.FromMinutes(1) // 设置超时时间
};
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options))
{
// ... 业务代码
scope.Complete();
}
===== 4. Entity Framework Core 中的事务 =====
EF Core 默认情况下,每次调用 `SaveChanges()` 时,都会自动在一个事务中运行。如果所有更改都成功,事务提交;否则回滚。但在某些复杂场景下,我们需要手动控制。
==== 4.1 使用 DbContext.Database.BeginTransaction() ====
类似于 ADO.NET 的方式,但在 EF 上下文中操作。
using (var context = new MyDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
context.Users.Add(new User { Name = "Charlie" });
context.SaveChanges();
context.Logs.Add(new Log { Message = "Charlie Added" });
context.SaveChanges();
// 提交事务
transaction.Commit();
}
catch (Exception)
{
// 回滚事务(可选,因为 Dispose 会自动处理未提交的事务,但显式调用更清晰)
transaction.Rollback();
}
}
}
==== 4.2 结合 TransactionScope 使用 EF Core ====
EF Core 也完全支持 `TransactionScope`。
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
using (var context = new MyDbContext())
{
context.Users.Add(new User { Name = "Dave" });
context.SaveChanges();
}
// 可以在这里做其他非数据库的操作,或者操作另一个 DbContext
scope.Complete();
}
**注意**:在异步编程(async/await)中使用 `TransactionScope` 时,必须指定 `TransactionScopeAsyncFlowOption.Enabled`,否则事务上下文可能无法在线程间正确流转。
===== 5. 事务隔离级别 (Isolation Levels) =====
隔离级别决定了一个事务在多大程度上与其他事务隔离。C# 中通过 `IsolationLevel` 枚举定义:
^ 隔离级别 ^ 说明 ^ 脏读 ^ 不可重复读 ^ 幻读 ^
| **ReadUncommitted** | 允许读取未提交的数据。性能最高,但最不安全。 | 是 | 是 | 是 |
| **ReadCommitted** | (SQL Server 默认) 只能读取已提交的数据。 | 否 | 是 | 是 |
| **RepeatableRead** | 锁定查询中使用的所有数据,直到事务结束。 | 否 | 否 | 是 |
| **Serializable** | 最高级别。锁定整个范围,就像事务是串行执行一样。 | 否 | 否 | 否 |
| **Snapshot** | 使用行版本控制来读取数据的一致性快照(减少锁竞争)。 | 否 | 否 | 否 |
===== 6. 最佳实践 =====
- **保持事务简短**:事务持有数据库锁,运行时间越长,阻塞其他用户的可能性越大。
- **避免在事务中进行用户交互**:不要在事务中间等待用户输入。
- **优先使用 TransactionScope**:代码更简洁,且易于配置。
- **注意异步流转**:在 `async` 方法中使用 `TransactionScope` 务必开启 `AsyncFlowOption`。
- **处理死锁**:在高并发系统中,即使代码正确也可能发生死锁,应设计重试机制。