transactionscope在跨服务场景必然失效,因其仅支持本地资源管理器(如sql server),无法协调http、kafka等外部服务;现代微服务必须采用saga、本地消息表或cap等应用层最终一致性方案。

TransactionScope 在 C# 中无法真正实现跨服务分布式事务,它只是 Windows MSDTC 的包装器,仅适用于同域、同网络、全 Windows 环境下的 SQL Server + NetTcp 场景;现代微服务中必须用 Saga、本地消息表或 CAP 这类应用层方案。
TransactionScope 为什么在跨服务时大概率失效
很多人以为 TransactionScope 包住 HTTP 调用或多个 DbContext 就能“自动分布式”,实际完全不是:
-
TransactionScope只能协调实现了IEnlistmentNotification的资源(如 SQL Server 的SqlConnection、MSMQ),对HttpClient、RestClient、Kafka 生产者等完全无感知——调用失败后事务不会回滚远端服务 - 一旦涉及跨机器,
TransactionManagerCommunicationException是最常见错误,根源是 MSDTC 未启用、防火墙拦截 RPC 动态端口、或未取消“要求验证”选项 - Entity Framework Core 中若提前打开
DbConnection(比如手动调OpenAsync()),enlist=true失效,事务范围静默降级为本地事务,日志里都看不到提示 - Docker 容器、Linux 主机、Azure Functions、AWS Lambda 等环境根本没 MSDTC,
TransactionScope直接抛PlatformNotSupportedException
CAP 不是“开箱即用”的分布式事务,而是三阶段协同机制
CAP 常被误认为是“.NET 版 Seata”,但它不提供两阶段提交语义,本质是“本地事务 + 消息表 + 后台调度”的组合:
-
ICapPublisher.PublishAsync()不发消息到 RabbitMQ/Kafka,只写入本地数据库的Cap.Published表,状态为Processing - 后台
Dispatcher轮询该表,成功投递后才更新状态为Succeeded;若数据库连接串配错或事务未提交,这条记录直接消失,消息永不发出 - 消费者方法必须标注
[CapSubscribe("xxx.order.created")],且方法签名只能是async Task+ 单个 DTO 参数(不能多加ILogger或CancellationToken) - 幂等必须自己做:推荐在消费逻辑开头查
Cap.Received表,用MessageId做唯一约束去重,别依赖业务字段(如订单号)
手写轻量 Saga 的关键控制点
比起引入 CAP 或 MassTransit,简单业务可直接用一张 SagaInstance 表 + 定时任务实现可控的最终一致性:
- 表结构至少含:
Id(业务唯一键)、CurrentStep(当前执行到哪步)、Status(Running/Compensated/Failed)、LastUpdated、RetryCount - 每步执行前先
SELECT ... FOR UPDATE查该Id记录,确认Status == Running && CurrentStep == N才继续,避免并发重复执行 - 执行失败时,不是直接抛异常,而是更新
Status = Failed并写入错误详情;补偿逻辑由独立定时任务扫描LastUpdated 的失败记录触发 - 不要在同一个 HTTP 请求里串行调用多个服务再统一提交——Saga 必须每步落库后立即提交本地事务,把“下一步是否执行”的决策权交给状态机
真正难的不是选哪个框架,而是明确每一步操作的持久化边界和失败回退路径。MSDTC 已死,CAP 易丢消息,手写 Saga 容易漏状态检查——所有方案都要求你把“事务”从黑盒变成白盒,一行代码一个 commit,一次失败一次补偿。











