aspnetboilerplate/aspnetboilerplate · error · AbpDbConcurrencyException

ex.Message (wraps DbUpdateConcurrencyException)

Error message

ex.Message (wraps DbUpdateConcurrencyException)

What it means

When base.SaveChanges() raises DbUpdateConcurrencyException (an optimistic-concurrency conflict detected by EF Core), AbpDbContext catches it and rethrows as ABP's AbpDbConcurrencyException, carrying the original message and inner exception. This lets applications handle concurrency conflicts with a single ABP exception type.

Solutions

  1. Catch AbpDbConcurrencyException at the application/service layer and inform the user to reload and re-apply changes
  2. Reload the entity (repository refresh / re-query) and retry the change after a conflict
  3. Add a [Timestamp] RowVersion column to enforce optimistic concurrency consistently
  4. Use ABP's built-in concurrency handling in application services (AbpServiceBase handles it for AJAX responses)

Example fix

// before
_repository.Update(order); // throws AbpDbConcurrencyException to caller unhandled
// after
try
{
    _repository.Update(order);
    await CurrentUnitOfWork.SaveChangesAsync();
}
catch (AbpDbConcurrencyException)
{
    // reload entity, notify user, re-apply changes
    await _repository.ReloadAsync(order);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// detect likely conflict before save (optional re-check)
var entry = dbContext.Entry(entity);
var dbRowVersion = await dbContext.Set<TRowVersionHolder>()
    .Where(x => x.Id == entity.Id)
    .Select(x => (long?)x.RowVersion)
    .FirstOrDefaultAsync();
if (dbRowVersion.HasValue && dbRowVersion != entry.Property("RowVersion").CurrentValue)
{
    // conflict already exists; reload entity instead of saving
}

Type guard

bool isConcurrencyConflict(Exception ex)
    => ex is AbpDbConcurrencyException || ex.InnerException is DbUpdateConcurrencyException;

Try / catch

try
{
    await CurrentUnitOfWork.SaveChangesAsync();
}
catch (AbpDbConcurrencyException ex)
{
    Logger.Warn($"Concurrency conflict on entity: {ex.Message}");
    await CurrentUnitOfWork.RollbackAsync();
    throw new UserFriendlyException(L("ConcurrencyConflictMessage"));
}

Prevention

When it happens

Trigger: Calling SaveChanges() (or the surrounding repository/UoW save) on an AbpDbContext while another transaction modified the same row, so the UPDATE affects 0 rows and EF raises DbUpdateConcurrencyException.

Common situations: Two users editing the same entity concurrently; [Timestamp]/RowVersion concurrency tokens; stale entity loaded earlier and saved after a competing update; retry loops causing double writes.

Related errors


AI-assisted analysis of aspnetboilerplate/aspnetboilerplate@2323c13a15 (2026-09-08). Data as JSON: /api/errors/3a0da6b960c66d7d. Report an issue: GitHub.

Appendix: source

Thrown at src/Abp.EntityFrameworkCore/EntityFrameworkCore/AbpDbContext.cs:276

                    .Entity<TEntity>()
                    .Property(property.Name)
                    .HasConversion(dateTimeValueConverter);
            });
        }
    }

    public override int SaveChanges()
    {
        try
        {
            var changeReport = ApplyAbpConcepts();
            var result = base.SaveChanges();
            EntityChangeEventHelper.TriggerEvents(changeReport);
            return result;
        }
        catch (DbUpdateConcurrencyException ex)
        {
            throw new AbpDbConcurrencyException(ex.Message, ex);
        }
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        try
        {
            var changeReport = ApplyAbpConcepts();
            var result = await base.SaveChangesAsync(cancellationToken);
            await EntityChangeEventHelper.TriggerEventsAsync(changeReport);
            return result;
        }
        catch (DbUpdateConcurrencyException ex)
        {
            throw new AbpDbConcurrencyException(ex.Message, ex);
        }
    }

View on GitHub (pinned to 2323c13a15)