目录

C# 中的事务处理 (Transactions)

事务(Transaction)是数据库操作中的一个核心概念,它确保一组操作要么全部成功,要么全部失败。在 C# 开发中,我们主要通过 ADO.NET、`System.Transactions` 命名空间以及 ORM 框架(如 Entity Framework)来处理事务。

1. 事务的基本概念 (ACID)

事务必须具备 ACID 四大特性:

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());
            }
        }
    }
}

关键点:

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. 最佳实践

  1. 保持事务简短:事务持有数据库锁,运行时间越长,阻塞其他用户的可能性越大。
  2. 避免在事务中进行用户交互:不要在事务中间等待用户输入。
  3. 优先使用 TransactionScope:代码更简洁,且易于配置。
  4. 注意异步流转:在 `async` 方法中使用 `TransactionScope` 务必开启 `AsyncFlowOption`。
  5. 处理死锁:在高并发系统中,即使代码正确也可能发生死锁,应设计重试机制。