为何EF Core 6中跨双DbContext的事务未升级为分布式事务且无报错?
我有如下匿名后的控制器代码。由于EF Core 6不支持分布式事务,且在同一个TransactionScope中使用了两个不同的DbConnection和DbContext,我预期会抛出异常,且事务应尝试升级为分布式事务。我已调试确认两个连接的进程ID不同,因此与连接池无关。为何这段代码能正常运行?
环境信息
- .NET Core 6
- Npgsql.EntityFrameworkCore.PostgreSQL 版本 6.0.7
- Microsoft.EntityFrameworkCore 版本 6.0.8
控制器代码
using System; using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Npgsql; namespace Controllers.Test; [Route("test")] public sealed class TestController : ControllerBase { private readonly IConfiguration _configuration; private readonly IHostEnvironment _host; public TestController( IConfiguration configuration, IHostEnvironment host) { _configuration = configuration; _host = host; } [HttpGet] public void Get() { using var transactionScope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromMinutes(30) }, TransactionScopeAsyncFlowOption.Enabled); var fooConnectionString = _configuration.GetConnectionString("foo")!; var barConnectionString = _configuration.GetConnectionString("bar")!; using var fooConnection = new NpgsqlConnection(fooConnectionString); using var barConnection = new NpgsqlConnection(barConnectionString); fooConnection.Open(); barConnection.Open(); using var fooDbContext = new FooDbContext(new DbContextOptionsBuilder<FooDbContext>() .UseNpgsql(fooConnection, o => o.UseNodaTime()) .Options); using var barDbContext = new BarDbContext(new DbContextOptionsBuilder<BarDbContext>() .UseNpgsql(barConnection, o => o.UseNodaTime()) .Options); var foo = fooDbContext.Foos.OrderBy(x => x.Id).First(); foo.SequenceNumber++; fooDbContext.SaveChanges(); var bar = barDbContext.Bars.OrderBy(x => x.Id).First(); bar.SequenceNumber++; barDbContext.SaveChanges(); transactionScope.Complete(); } }
解答
这段代码能正常运行的核心原因是两个DbContext的操作并未真正加入同一个TransactionScope事务,而是各自使用独立的本地事务,具体细节如下:
TransactionScope与第一个连接的本地事务绑定
当你在TransactionScope中打开第一个PostgreSQL连接时,Npgsql会将该连接的本地事务关联到TransactionScope。此时TransactionScope本质是本地事务,而非分布式事务。第二个连接未加入TransactionScope事务
EF Core 6本身不支持通过TransactionScope自动升级为分布式事务,而PostgreSQL在.NET Core环境下默认也不依赖MSDTC(分布式事务协调器)。当你打开第二个连接时,EF Core并不会强制抛出异常,而是允许该连接创建自己的独立本地事务——也就是说第二个DbContext的操作完全不受TransactionScope的管控。事务原子性并未实现
你调用的两次SaveChanges()各自在独立的本地事务中执行,transactionScope.Complete()并不会保证两个操作的原子性。例如,如果第一个SaveChanges()成功后,第二个操作抛出异常,第一个操作的修改不会被回滚,这和你预期的分布式事务行为完全不符。为何未抛出异常?
EF Core 6对TransactionScope的支持有限,尤其是针对不支持分布式事务的数据库提供程序。它不会主动检测并抛出"跨连接事务"的异常,而是静默允许每个连接使用自己的本地事务,这就导致代码能正常运行,但并未实现你想要的事务原子性。
你可以通过以下方式验证:在第二个SaveChanges()之前手动抛出异常,观察第一个SaveChanges()的修改是否被保留——结果会是第一个修改依然存在,证明两个操作不在同一个事务中。
内容的提问来源于stack exchange,提问作者Chris Yungmann

