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

Resilient Execution Strategies: Retryable Failures, Idempotency, and Transaction Delegates

Place user transactions inside provider execution-strategy delegates, distinguish transient replay from business conflicts, and design for ambiguous commit outcomes.

Advanced145–190 minutesexecution strategy + idempotency 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 execution strategy is EF/provider logic that can replay database operations after failures classified as transient. It is not a generic catch/retry loop, and it is not optimistic-concurrency resolution. When the application starts its own transaction, replay must encompass the entire transaction so the unit can be reconstructed safely.

01

Explain provider execution strategies and transient-failure classification without hard-coding universal retry counts.

02

Inspect the strategy supplied by the current provider and keep SQLite’s non-cloud baseline honest.

03

Recognize the InvalidOperationException caused by combining a retrying strategy with an unmanaged user transaction.

04

Execute the entire user transaction inside CreateExecutionStrategy().ExecuteAsync.

05

Reason about unknown commit outcomes and idempotency/client-generated identity.

06

Use verification or durable transaction/outbox IDs when replay safety requires proof of prior success.

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. Provider retries classify infrastructure failures, not business conflicts

Failure Execution strategy candidate? Why
SQL/Azure SQL transient connection error Often, using SQL Server provider strategy Provider knows error numbers and retry semantics.
Network interruption Possibly Depends on provider classification and commit ambiguity.
SQLite BUSY/LOCKED Do not assume EF SQLite has the same retry strategy as SQL Server SQLite locking/timeout policy is provider/engine-specific.
DbUpdateConcurrencyException No generic transient replay Stored business version changed; Chapter 13 resolution policy applies.
Unique/check constraint violation Usually no Same invalid input will normally fail again.

Never copy EnableRetryOnFailure to providers that do not expose the same extension or error taxonomy. The SQL Server provider includes a retrying execution strategy; the mandatory SQLite lab focuses on the API contract and local transaction safety without pretending it is Azure SQL.

2. Inspect the strategy you actually configured

csharp · provider evidence
var strategy = db.Database.CreateExecutionStrategy();Console.WriteLine(strategy.GetType().FullName);Console.WriteLine($"RetriesOnFailure: {strategy.RetriesOnFailure}");

Record this output in the lab. If the SQLite strategy is non-retrying, that is useful evidence—not a missing feature to paper over. A production SQL Server configuration can opt into connection resiliency:

csharp · optional SQL Server configuration
options.UseSqlServer(    connectionString,    sql => sql.EnableRetryOnFailure());

Do not freeze universal retry counts or delays. Provider defaults and workload tolerance are part of production configuration, monitoring, and incident design.

3. Deliberately wrong under a retrying strategy: start a transaction outside replay

csharp · unsafe replay boundary
await using var tx = await db.Database.BeginTransactionAsync(ct);// Under a retrying strategy, EF cannot replay this user transaction automatically.await db.SaveChangesAsync(ct);await tx.CommitAsync(ct);

With a retrying execution strategy such as SQL Server’s, EF documents an InvalidOperationException explaining that user-initiated transactions are not supported in that form. A command cannot be retried independently if correctness requires replaying earlier commands in the same transaction.

4. Repair: the transaction is inside the strategy delegate

csharp · retriable transaction unit
var strategy = db.Database.CreateExecutionStrategy();await strategy.ExecuteAsync(async () =>{    await using var attempt = await factory.CreateDbContextAsync(ct);    await using var tx = await attempt.Database.BeginTransactionAsync(ct);    var order = await attempt.WorkOrders.SingleAsync(x => x.Id == id, ct);    order.ReviseSummary("retry-safe transaction attempt");    order.AdvanceRevision();    await attempt.SaveChangesAsync(ct);    attempt.Set<OutboxMessage>().Add(new OutboxMessage    {        Id = messageId,                  // stable client-generated identity        OccurredUtc = occurredUtc,        Type = "WorkOrderChanged",        PayloadJson = payload    });    await attempt.SaveChangesAsync(ct);    await tx.CommitAsync(ct);});

The delegate describes the unit that can be replayed. A fresh context per attempt prevents a failed connection/transaction from contaminating a later attempt. Stable inputs such as messageId are created outside the delegate so each replay represents the same logical operation.

5. Commit can fail ambiguously

If the connection drops while commit is occurring, the client may not know whether the server committed. Replaying as though rollback occurred can duplicate work if the first commit actually succeeded. EF’s connection-resiliency guidance explicitly calls this the idempotency/commit-unknown problem.

Technique What it buys Boundary
Client-generated Guid key A replay collides with the same identity rather than silently inserting another auto-key row Requires domain/schema to accept stable client identity.
Rebuild application state Discard failed context and re-read database May require user/operator confirmation when outcome is unknown.
ExecuteInTransaction + verifySucceeded Checks whether the logical operation is already present after commit ambiguity Verification itself must tolerate the same infrastructure faults.
Transaction/outbox operation ID Durable idempotency evidence stored with the same local commit Needs schema, uniqueness, cleanup/retention, and operational ownership.

6. Verification pattern with AcceptAllChanges deferred

csharp · documented commit-verification shape
var strategy = db.Database.CreateExecutionStrategy();await strategy.ExecuteInTransactionAsync(    db,    operation: (context, token) =>        context.SaveChangesAsync(acceptAllChangesOnSuccess: false, token),    verifySucceeded: (context, token) =>        context.Set<OutboxMessage>()            .AsNoTracking()            .AnyAsync(x => x.Id == messageId, token));db.ChangeTracker.AcceptAllChanges();

acceptAllChangesOnSuccess: false keeps the tracked state replayable until the strategy has determined success. This is advanced infrastructure code: test it with deliberate failure injection and keep the verification key unique and meaningful.

7. The strategy may buffer result sets

EF’s connection-resiliency documentation notes that enabling retries causes EF to internally buffer result sets so a query can be replayed. That can materially increase memory for large streaming queries. Retry configuration therefore affects query memory behavior as well as writes—another reason not to enable a setting by cargo cult.

8. Hands-on lab

  1. Print the mandatory SQLite execution-strategy type and RetriesOnFailure.
  2. Execute a simple strategy delegate and verify normal behavior even if the provider is non-retrying.
  3. Write a unit/integration seam that injects a fake/test execution strategy or fault wrapper to prove the transaction delegate can run more than once without producing a second logical outbox ID.
  4. Keep messageId outside the replay delegate and assert uniqueness/idempotency.
  5. Implement the documented ExecuteInTransactionAsync verification shape on the disposable outbox table.
  6. For optional SQL Server, enable retries and reproduce the user-transaction InvalidOperationException; then move the transaction into the delegate.
  7. Record conflict exceptions separately and prove your retry classifier does not treat DbUpdateConcurrencyException as transient infrastructure.
  8. Document memory implications before enabling retries around very large result streams.

Check your understanding

  1. Why must a user transaction be inside a retrying execution-strategy delegate?
  2. Is DbUpdateConcurrencyException normally a transient provider failure?
  3. Why create a stable message/operation ID outside the replay delegate?
  4. What is an ambiguous commit?
  5. Why delay AcceptAllChanges in a verification pattern?
  6. Can retrying execution strategies affect query memory?
Review the answers

Because a transient failure may require replaying the entire transaction unit, not one command in isolation.

No. It represents changed business/database state and needs a conflict policy.

Every retry should represent the same logical operation and be detectable/deduplicable.

A failure occurs while committing and the client cannot know whether the server committed or rolled back.

It keeps tracked state available for replay until transaction success has been verified.

Yes. EF documents internal result buffering when retries are enabled.

9. Production judgment and bridge

Retry only failures the provider classifies as transient, replay the correct unit, and make ambiguous commits observable/idempotent. Chapter 14’s next lesson keeps one local transaction but spreads work across multiple EF contexts and raw ADO.NET, showing that transaction coordination and change-tracker coordination are separate problems.

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