EF Core 中 BeginTransaction 前必须确保连接已打开,否则抛出 InvalidOperationException;推荐显式调用 OpenConnectionAsync(),跨 DbContext 事务需共享 DbConnection,避免使用 TransactionScope。
EF Core 中用 BeginTransaction 开事务前必须确认连接已打开
ef core 的 database.begintransaction() 不会自动打开底层数据库连接;如果当前 dbcontext 连接处于关闭状态(比如刚 new 出来还没执行过查询),调用它会直接抛出 invalidoperationexception: the connection is not open。
实操建议:
- 先触发一次轻量级操作让连接就绪,例如
context.Database.CanConnect()或context.Set<t>().Any()</t> - 更稳妥的做法是显式打开连接:
await context.Database.OpenConnectionAsync(),再调用BeginTransaction - 注意:如果用了连接池(默认开启),
OpenConnectionAsync实际开销极小,不是真正“建连”
嵌套 SaveChanges 时事务不会自动传播
在同一个 DbContext 实例中多次调用 SaveChanges,哪怕都在一个 Transaction 内,EF Core 也不会帮你“续上”事务上下文——它只保证单次提交原子性。但如果你手动控制事务,所有 SaveChanges 都会复用当前连接和事务,这点没问题。
容易踩的坑:
- 误以为多个
DbContext实例共享事务:不共享。每个实例有独立连接,必须显式传入DbTransaction - 跨 DbContext 事务需共用
DbConnection:先从一个上下文拿到connection,再用它初始化另一个上下文(构造时传DbContextOptionsBuilder.UseSqlServer(connection)) -
SaveChanges失败后,事务仍处于活动状态,必须显式Rollback或Dispose(using 块可自动回滚)
TransactionScope 在 EF Core 6+ 默认不支持分布式事务
很多人习惯用 TransactionScope 包裹多个 SaveChanges,但在 .NET 6+、EF Core 6+、SQL Server 上,默认启用的是轻量级事务(lightweight transaction),一旦涉及多个连接(比如两个不同 DbContext),就会静默升级为分布式事务,触发 System.Transactions.TransactionManagerException。
为什么这样做:
- 分布式事务依赖 MSDTC 服务,在容器、云环境常被禁用或不可用
- EF Core 官方明确不推荐
TransactionScope用于新项目 - 性能差、调试难、超时策略不透明
替代方案:老老实实用 BeginTransaction + 共享连接,或改用领域事件 + 补偿事务处理跨上下文一致性。
异步方法必须配对使用 BeginTransactionAsync 和 CommitAsync
混用同步与异步事务 API 是常见崩溃点。比如用 BeginTransaction()(同步)开头,却调用 CommitAsync()(异步),会导致 InvalidOperationException: This operation cannot be performed on a closed transaction —— 因为同步版事务在 SaveChanges 后可能已被释放。
关键原则:
- 全链路异步:从
BeginTransactionAsync→SaveChangesAsync→CommitAsync/RollbackAsync - 别在 using 块里混合 async/await 和同步 commit;
using var tx = await context.Database.BeginTransactionAsync()是安全的 - 注意:.NET 8 中
Transaction类型本身不实现IAsyncDisposable,所以await using不适用,仍用using+await CommitAsync











