导读:本期聚焦于小伙伴创作的《C# EF Core如何使用数据库事务处理多个表的写入操作?》,敬请观看详情。在订单与库存分表存储的场景中,若写入订单成功但扣减库存失败,就会产生脏数据。EF Core默认把单次SaveChanges包成事务,可一旦跨多次调用就失效。借助DbContext.Database.BeginTransaction,能把多条插入、更新指令压进同一事务,要么全成功要么全回滚。本文说明如何手动开启事务、混合执行原生SQL与跟踪实体,以及用依赖注入复用上下文时的注意点,帮你稳妥处理多表一致性。

在使用 C# 进行业务开发时,经常会遇到需要同时修改多张数据库表的情况,比如新增订单的同时扣减商品库存。EF Core 作为流行的对象关系映射框架,提供了多种控制数据库事务的手段,理解这些手段可以避免数据不一致的问题。

C# EF Core如何使用数据库事务处理多个表的写入操作?

EF Core 默认的事务行为

EF Core 的 DbContext 在调用 SaveChangesSaveChangesAsync 时,如果当前没有由用户开启的事务,框架会自动创建一个数据库事务,将本次上下文中所有被跟踪实体的变更在一个事务里提交。这意味着单次 SaveChanges 内部的多次 INSERT、UPDATE、DELETE 是具有原子性的。

但很多开发者忽略的是,这种自动事务仅覆盖单次 SaveChanges 调用。如果你为了业务逻辑清晰,分两次调用 SaveChanges,或者在两次操作之间执行了原生 SQL,那么默认机制就无法保证它们之间的一致性。一旦第一次成功、第二次失败,系统就会出现部分写入的脏数据。

使用 BeginTransaction 手动控制事务

当我们需要在同一个事务中执行多个 SaveChanges,或者混合使用 EF 跟踪实体与原生 SQL 时,应当使用 Database.BeginTransaction 方法。该方法返回一个 IDbContextTransaction 对象,我们可以显式调用 Commit 或 Rollback。

下面的示例展示了在单个 DbContext 中,同时写入订单表和扣减库存表,并保证两者要么都成功要么都失败:

using (var transaction = context.Database.BeginTransaction())
{
    try
    {
        // 新增订单
        var order = new Order
        {
            OrderNo = "20240101001",
            ProductId = 1,
            Quantity = 2,
            CreateTime = DateTime.Now
        };
        context.Orders.Add(order);
        context.SaveChanges();

        // 扣减库存,使用原生 SQL
        context.Database.ExecuteSqlInterpolated($"UPDATE Products SET Stock = Stock - {order.Quantity} WHERE Id = {order.ProductId}");

        // 提交事务
        transaction.Commit();
    }
    catch (Exception ex)
    {
        // 发生异常回滚所有操作
        transaction.Rollback();
        throw;
    }
}

上述代码中,即使库存更新语句因为并发冲突或字段约束报错,之前写入的订单记录也会随 Rollback 一起撤销。注意 BeginTransaction 默认使用数据库连接的隔离级别,一般无需额外配置。

如果你希望使用异步方式,可以改用 BeginTransactionAsync 并在内部使用 SaveChangesAsyncExecuteSqlInterpolatedAsync,代码结构保持一致,只是在调用链上增加 await 关键字。

跨 DbContext 的分布式事务

在分层架构中,有时订单上下文和库存上下文是分开的 DbContext 类。如果它们指向同一个数据库,可以使用 Database.BeginTransaction 配合 TransactionScope 来涵盖多个上下文。但如果数据库不同,就需要引入分布式事务管理器,这在 EF Core 中支持有限且依赖操作系统配置。

以下代码演示同一数据库下两个上下文共享事务的方式:

var orderContext = new OrderContext();
var stockContext = new StockContext();

using (var transaction = orderContext.Database.BeginTransaction())
{
    // 让库存上下文加入同一个事务
    stockContext.Database.UseTransaction(transaction.GetDbTransaction());

    orderContext.Orders.Add(new Order { OrderNo = "T002", ProductId = 3, Quantity = 1 });
    orderContext.SaveChanges();

    stockContext.Database.ExecuteSqlInterpolated($"UPDATE Stock SET Count = Count - 1 WHERE ProductId = 3");
    stockContext.SaveChanges();

    transaction.Commit();
}

这里的关键是 UseTransaction 方法,它接收一个底层的 DbTransaction 实例,使得第二个上下文复用第一个上下文开启的事务。这样做可以避免自己写复杂的协调逻辑。

需要提醒的是,如果两个上下文使用了不同的连接字符串,即便域名相同,数据库不同,上述代码也会抛出异常,因为底层 SQL 事务无法跨库提升为分布式事务而不借助 MSDTC 等服务。

依赖注入场景下的使用注意

在 ASP.NET Core 中,DbContext 通常注册为 Scoped 服务,由容器在每个请求中提供同一个实例。此时在 Controller 或 Service 里注入 DbContext,然后开启事务是安全的,因为整个请求共用一个上下文,事务生命周期不会错乱。

但如果你的服务中注入了多个不同种类的 DbContext,或者使用了 DbContextFactory 每次创建新实例,就必须确认这些实例是否共享数据库连接。若未共享,事务代码将不会如预期工作。推荐的做法是尽量在一个请求内只通过一个上下文操作同一数据库,或显式传递事务对象。

常见误区与最佳实践

一个典型误区是认为只要把多个 SaveChanges 写在一个方法里,EF Core 就会自动包成事务。事实上只有单次 SaveChanges 内部才是自动事务。另一个误区是在 using 块外调用了 Commit,却忘了在 catch 中 Rollback,这可能导致连接被占用或锁表。

最佳实践包括:尽量缩短事务持续时间,不要在事务中调用外部 HTTP 接口;对只涉及单表的操作不必手动开事务;使用拦截器或单元测试验证回滚逻辑是否生效。这样可以在保证数据一致的同时,维持系统的吞吐量与稳定性。

小结对比

下表列出了不同事务处理方式的特点:

方式适用场景原子性范围
默认自动事务单次 SaveChanges 多实体变更仅本次 SaveChanges
BeginTransaction多次 SaveChanges 或混合 SQL显式提交前所有操作
TransactionScope跨上下文同库或分布式代码块内全部资源

通过合理选择上述方式,C# 开发者可以让 EF Core 下的数据写入既灵活又可靠。

EF_Core数据库事务SaveChanges修改时间:2026-08-04 09:15:33

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。