====== 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`。 - **处理死锁**:在高并发系统中,即使代码正确也可能发生死锁,应设计重试机制。