Table of Contents

DbContext extensions

The MADE.Data.EFCore.Extensions.DbContextExtensions class provides helper methods for common EF Core operations: updating and saving in one call, removing entities by predicate, automatic date management, and safe save with concurrency handling.

UpdateAsync

Updates an entity and saves changes in a single call, reducing the boilerplate of marking an entity as modified and then calling SaveChangesAsync separately:

await dbContext.UpdateAsync(user);

RemoveWhere

Removes all entities from a DbSet that match a predicate:

await dbContext.RemoveWhere<Session>(s => s.ExpiresAt < DateTime.UtcNow);

Automatic date management with SetEntityDates

SetEntityDates scans the change tracker for entities implementing IDatedEntity and automatically sets their CreatedDate (on insert) and UpdatedDate (on insert and update). Call it from your SaveChangesAsync override:

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    this.SetEntityDates();
    return await base.SaveChangesAsync(cancellationToken);
}

This ensures timestamps are set consistently regardless of which code path triggers the save. You never need to manually set UpdatedDate at your call sites.

TrySaveChangesAsync

A wrapper around SaveChangesAsync that catches DbUpdateConcurrencyException and returns a boolean indicating success:

bool saved = await dbContext.TrySaveChangesAsync();
if (!saved)
{
    // Handle concurrency conflict
}

Combining extensions in SaveChangesAsync

A typical SaveChangesAsync override uses multiple extensions together:

public class AppDbContext : DbContext
{
    private readonly IAuthenticatedUserAccessor userAccessor;

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        this.InterceptSoftDeletions();       // Convert hard deletes to soft deletes
        this.SetEntityDates();               // Set CreatedDate and UpdatedDate
        this.SetEntityAuditInfo(             // Set CreatedBy and UpdatedBy
            userAccessor.AuthenticatedUser.Subject);
        return await base.SaveChangesAsync(cancellationToken);
    }
}

See Soft delete and Audit tracking for details on InterceptSoftDeletions and SetEntityAuditInfo.