Chapter 14 · Transactions, Savepoints, Execution Strategies, and Cross-Context Coordination

Ambient / System.Transactions Boundaries, Distributed Transactions, and Safer Architectural Alternatives

Bound TransactionScope and distributed transactions by provider/platform capability, then choose local transactions plus outbox/saga patterns for cross-service workflows.

Advanced145–185 minutesambient/distributed boundary + outbox/saga labEF Core 10.0.11 · .NET 10.0.11SQLite provider 10.0.11 mandatory baselineSDK checkpoint: 10.0.400Last reviewed: August 2026

Learning outcomes

An ambient transaction is a transaction implicitly available to code through System.Transactions.Transaction.Current, usually created by TransactionScope. It can simplify in-process coordination when providers support it, but it can also hide enlistment, escalate to a distributed transaction, and fail completely on unsupported providers/platforms. EF Core does not make distributed transactions portable.

01

Explain TransactionScope, ambient enlistment, Required/RequiresNew/Suppress, and async-flow requirements.

02

Record the mandatory SQLite result: Microsoft.Data.Sqlite does not support System.Transactions.

03

Bound SQL Server/SqlClient ambient/distributed support by provider and platform rather than EF abstraction.

04

Explain distributed-transaction escalation and .NET’s Windows-only System.Transactions distributed support.

05

Reject TransactionScope as a way to make HTTP/message-broker calls atomic with a database.

06

Design ServiceHub cross-service consistency with local transaction + outbox + idempotent saga/process coordination.

Reproducible baseline

Mandatory labs use .NET SDK 10.0.400, .NET runtime 10.0.11, Microsoft.EntityFrameworkCore/SQLite 10.0.11, dotnet-ef 10.0.11, the disposable servicehub-lab.db, deterministic ServiceHub seed data, the existing Guid Revision concurrency token, and the Chapter 12 outbox teaching model when referenced. SQL Server retry/MARS/System.Transactions examples are optional provider-specific paths. EF Core 11 previews are excluded.

1. TransactionScope changes how code finds a transaction

csharp · optional provider-supported ambient transaction
using var scope = new TransactionScope(    TransactionScopeOption.Required,    TransactionScopeAsyncFlowOption.Enabled);await db.SaveChangesAsync(ct);await otherSupportedResource.DoDatabaseWorkAsync(ct);scope.Complete();

Required joins an existing ambient transaction or creates one; RequiresNew creates a new ambient transaction; Suppress runs without the ambient transaction. For async code, TransactionScopeAsyncFlowOption.Enabled is required so the ambient transaction flows across continuations.

2. Mandatory SQLite capability check: do not rely on TransactionScope

SQLite boundary

Microsoft.Data.Sqlite documentation explicitly says System.Transactions is not supported and recommends ADO.NET transactions instead. Therefore TransactionScope is not a valid mandatory ServiceHub SQLite lab. The correct local mechanism is BeginTransaction/DbTransaction as taught in Lessons 1–4.

csharp · free local alternative
await using var tx = await db.Database.BeginTransactionAsync(ct);try{    // ServiceHub aggregate + outbox database work only    await db.SaveChangesAsync(ct);    await tx.CommitAsync(ct);}catch{    await tx.RollbackAsync(ct);    throw;}

This negative capability test is part of provider-aware engineering: a common .NET API existing in the framework does not mean the active ADO.NET provider implements it.

3. Provider support and distributed escalation are separate questions

Question Example answer Why it matters
Does provider enlist in System.Transactions? SqlClient supports it; Microsoft.Data.Sqlite does not Unsupported providers may ignore/fail ambient semantics.
Is it still one local resource? One compatible database connection/resource manager may remain local Local ambient transaction can be cheaper/simpler than distributed coordination.
Did multiple durable resource managers enlist? May escalate to distributed transaction Requires DTC-capable infrastructure and operations.
Does the platform support distributed System.Transactions? .NET distributed transaction support is Windows-only Non-Windows deployments cannot assume it works.
Does TransactionScope dispose asynchronously? No async commit/rollback on scope disposal Synchronous disposal can block the executing thread.

4. Deliberately wrong: put database + HTTP + broker inside TransactionScope

csharp · not cross-service atomicity
using var scope = new TransactionScope(    TransactionScopeAsyncFlowOption.Enabled);await db.SaveChangesAsync(ct);await httpClient.PostAsJsonAsync("https://inventory/...", request, ct);await messageBus.PublishAsync(message, ct);scope.Complete();

An ordinary HTTP server or message broker does not become a System.Transactions participant because the call happened lexically inside the scope. A database rollback cannot un-send a completed HTTP request. Conversely, a broker publish can fail after the database committed. This is the same dual-write problem Chapter 12’s outbox addressed.

5. Safer ServiceHub boundary: local transaction + outbox

csharp · atomic local state and durable intent
var order = await db.WorkOrders.SingleAsync(x => x.Id == id, ct);order.ReviseSummary("dispatch approved");order.AdvanceRevision();db.Set<OutboxMessage>().Add(new OutboxMessage{    Id = commandId, // stable idempotency identity    OccurredUtc = clock.GetUtcNow().UtcDateTime,    Type = "WorkOrderDispatchApproved",    PayloadJson = BuildMinimalOutboxPayload(order)});await db.SaveChangesAsync(ct); // one local database transaction// Separate dispatcher publishes only after the row is durable.

The local database stays strongly atomic. Cross-service progress becomes a durable workflow rather than an impossible global lock over independently deployed systems.

6. Saga/process manager: consistency over time, not one giant transaction

A saga coordinates a multi-step business workflow where each participant commits a local transaction and later steps may require compensating actions. A process manager records workflow state and decides which command/event comes next. Neither provides isolation equivalent to a single database transaction; they provide explicit recovery and progress semantics across boundaries.

Local database transaction Distributed transaction Outbox + saga/process manager
Atomic within one database/resource Atomic across supported enlisted resources Each service commits locally; workflow converges through durable messages
Short, efficient when scoped well Operationally heavy; provider/platform constrained Designed for independently deployed services and partial failure
Rollback restores local DB state Coordinator can commit/abort participants Compensation/idempotency handles already-committed steps
Use for ServiceHub aggregate + outbox Use only when requirements/infrastructure truly justify it Preferred general cross-service learning/production pattern

7. Idempotency and compensation are first-class

If the outbox dispatcher republishes after a crash, downstream handlers should recognize the stable message/command ID. If step 3 of a saga fails after steps 1 and 2 committed, the business must define whether to retry, compensate, pause for operator review, or mark the workflow failed. “Rollback everything” may no longer be physically possible.

Do not call compensation rollback

A compensation is a new business action that attempts to counter an earlier committed action. It can itself fail and may not perfectly restore the original world—for example, an email cannot be unsent and an external reservation may incur fees.

8. Hands-on boundary lab

  1. Record the Microsoft.Data.Sqlite documentation result that System.Transactions is unsupported; do not create a misleading TransactionScope success test on SQLite.
  2. Run the free local alternative: one ServiceHub update + outbox insert, inject a failure, and prove one local transaction rolls back both.
  3. Run the outbox dispatcher twice with the same stable ID and verify the fake downstream consumer deduplicates/idempotently handles the replay.
  4. Model a three-step saga table/state machine on paper or in a small test: reserve part → schedule technician → notify customer; define failure/compensation for each step.
  5. For optional Windows + SQL Server/SqlClient, test a local TransactionScope with TransactionScopeAsyncFlowOption.Enabled and record Transaction.Current plus provider behavior.
  6. Do not require MSDTC/distributed transactions for the mandatory lab. If experimenting, document Windows-only support, DTC configuration, firewall/security, provider enlistment, and operational rollback/recovery.
  7. Capture workflow IDs, local transaction outcome, outbox delivery attempts, and compensation status in structured logs.

Check your understanding

  1. Does EF Core make System.Transactions work on every provider?
  2. What must TransactionScope use in async code?
  3. Are distributed System.Transactions portable across Linux and Windows?
  4. Will TransactionScope make an HTTP request roll back?
  5. What problem does an outbox solve?
  6. What is compensation?
Review the answers

No. Providers must implement support; Microsoft.Data.Sqlite explicitly does not.

TransactionScopeAsyncFlowOption.Enabled so the ambient transaction flows across async continuations.

No. EF docs state distributed transaction support is Windows-only on modern .NET.

No. The remote service is not automatically an enlisted transactional resource.

It atomically records local state and durable intent to publish, avoiding the database/message dual-write gap.

A new business action used to counter an already-committed saga step; it is not a physical rollback and can fail.

9. Production judgment and bridge to Chapter 15

Prefer the narrowest coordination mechanism that matches the topology: one SaveChanges transaction, a short explicit DbTransaction, savepoints, a provider execution-strategy delegate, or shared local DbTransaction when legacy components genuinely need it. Treat ambient/distributed transactions as provider/platform infrastructure choices, never universal EF features. Across service boundaries, make local commits durable with an outbox and coordinate progress explicitly. Chapter 15 now shifts from runtime transaction boundaries to schema-change transactions: migration snapshots, generated operations, scripts, bundles, seeding, deployment ownership, and rollback strategy.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.

\n