Skip to content

Entity Framework

Arnel Robles edited this page Sep 29, 2026 · 1 revision

Entity Framework Core (Carom.EntityFramework)

using Carom;
using Carom.EntityFramework;

var written = await db.SaveChangesWithRetryAsync(retries: 3, cancellationToken);

var total = await db.ExecuteWithRetryAsync(() => db.Orders.SumAsync(o => o.Total), retries: 3, cancellationToken);

What counts as transient

The int overloads use a built-in check. A DbUpdateException is judged by its inner exception; with no inner exception it is not retried. An exception is retried when it is a TimeoutRejectedException, or when its message contains, ignoring case, one of: timeout, deadlock, connection, network, transport.

Carom has no provider dependency, so it does not read SqlException error numbers. The message check is a heuristic. If you need exact rules, use the Bounce overload with your own predicate.

DbUpdateConcurrencyException is not retried: a blind retry sends the same stale values and hits the same conflict.

The Bounce overload

var bounce = Bounce.Times(3)
    .WithTimeout(TimeSpan.FromSeconds(5))
    .When(ex => ex is DbUpdateException { InnerException: TimeoutException });

await db.SaveChangesWithRetryAsync(bounce, cancellationToken);

This overload uses your Bounce as given. Without a When predicate it retries every exception except cancellation, the core default, not the transient check above. A Bounce timeout cancels the save itself (fixed in 2.0.1).

Retrying SaveChanges re-sends whatever the change tracker holds. That is safe for a transient connection failure before the commit, and not something Carom can check for you.

Clone this wiki locally