Skip to main content
Version: 2.4.1

Entity Framework Core

Overview​

The EF Core provider wires EFCoreRepository<TEntity> into the repository abstraction layer. It implements IReadOnlyRepository<TEntity>, IWriteOnlyRepository<TEntity>, ILinqRepository<TEntity>, and IGraphRepository<TEntity>, giving you full LINQ query support, eager loading, automatic soft-delete handling, and opt-in change-tracking control.

The provider resolves a RCommonDbContext-derived DbContext from the IDataStoreFactory at runtime, which makes it possible to register multiple named DbContext instances and route repositories to the right one.

Installation​

NuGet Package
dotnet add package RCommon.EfCore

DbContext setup​

Your DbContext must derive from RCommonDbContext rather than DbContext directly. RCommonDbContext implements IDataStore, which is the mechanism the IDataStoreFactory uses to resolve the correct context at runtime.

using Microsoft.EntityFrameworkCore;
using RCommon.Persistence.EFCore;

public class LeaveManagementDbContext : RCommonDbContext
{
public LeaveManagementDbContext(DbContextOptions<LeaveManagementDbContext> options)
: base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(LeaveManagementDbContext).Assembly);
}

public DbSet<LeaveRequest> LeaveRequests { get; set; }
public DbSet<LeaveType> LeaveTypes { get; set; }
public DbSet<LeaveAllocation> LeaveAllocations { get; set; }
}

If you need audit stamping or other cross-cutting concerns, introduce an intermediate abstract class that still inherits from RCommonDbContext:

public abstract class AuditableDbContext : RCommonDbContext
{
private readonly ICurrentUser _currentUser;
private readonly ISystemTime _systemTime;

public AuditableDbContext(DbContextOptions options,
ICurrentUser currentUser, ISystemTime systemTime)
: base(options)
{
_currentUser = currentUser;
_systemTime = systemTime;
}

public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default)
{
foreach (var entry in ChangeTracker.Entries<BaseDomainEntity>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified))
{
string userId = _currentUser?.Id?.ToString() ?? "System";
entry.Entity.DateLastModified = _systemTime.Now;
entry.Entity.LastModifiedBy = userId;

if (entry.State == EntityState.Added)
{
entry.Entity.DateCreated = _systemTime.Now;
entry.Entity.CreatedBy = userId;
}
}

return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}

Configuration​

Register the EF Core persistence provider in your application startup:

builder.Services.AddRCommon()
.WithPersistence<EFCorePerisistenceBuilder>(ef =>
{
ef.AddDbContext<LeaveManagementDbContext>(
"LeaveManagement",
options => options.UseSqlServer(
builder.Configuration.GetConnectionString("LeaveManagement")));

ef.SetDefaultDataStore(ds =>
ds.DefaultDataStoreName = "LeaveManagement");
});

Multiple DbContexts can be registered by calling AddDbContext multiple times with different names:

ef.AddDbContext<InventoryDbContext>("Inventory",
options => options.UseNpgsql(inventoryConnectionString));

ef.AddDbContext<OrderingDbContext>("Ordering",
options => options.UseSqlServer(orderingConnectionString));

ef.SetDefaultDataStore(ds => ds.DefaultDataStoreName = "Ordering");

Usage​

Injecting and targeting a data store​

Inject IGraphRepository<TEntity> (or any narrower interface) and set DataStoreName to match the name given to AddDbContext:

public class CreateLeaveTypeCommandHandler
{
private readonly IGraphRepository<LeaveType> _leaveTypeRepository;

public CreateLeaveTypeCommandHandler(IGraphRepository<LeaveType> leaveTypeRepository)
{
_leaveTypeRepository = leaveTypeRepository;
_leaveTypeRepository.DataStoreName = "LeaveManagement";
}

public async Task HandleAsync(CreateLeaveTypeCommand request, CancellationToken cancellationToken)
{
var leaveType = new LeaveType { Name = request.Name, DefaultDays = request.DefaultDays };
await _leaveTypeRepository.AddAsync(leaveType, cancellationToken);
}
}

CRUD operations​

// Create
await _repo.AddAsync(entity, cancellationToken);
await _repo.AddRangeAsync(entities, cancellationToken);

// Read
LeaveType? lt = await _repo.FindAsync(id, cancellationToken);
var all = await _repo.FindAsync(lt => lt.DefaultDays > 0, cancellationToken);
var single = await _repo.FindSingleOrDefaultAsync(lt => lt.Name == "Annual", cancellationToken);
bool any = await _repo.AnyAsync(lt => lt.DefaultDays > 20, cancellationToken);
long count = await _repo.GetCountAsync(lt => lt.DefaultDays > 0, cancellationToken);

// Update
await _repo.UpdateAsync(entity, cancellationToken);

// Delete (auto-detects ISoftDelete)
await _repo.DeleteAsync(entity, cancellationToken);

// Bulk delete
int affected = await _repo.DeleteManyAsync(lt => lt.DefaultDays == 0, cancellationToken);

LINQ queries​

Because IGraphRepository<TEntity> extends IQueryable<TEntity> you can use it directly in LINQ expressions:

IQueryable<LeaveType> query = _repo.FindQuery(lt => lt.DefaultDays > 5);

Eager loading​

var requests = await _leaveRequestRepo
.Include(r => r.LeaveType)
.FindAsync(r => r.EmployeeId == employeeId, cancellationToken);

Chain additional navigation properties with ThenInclude:

var allocations = await _allocationRepo
.Include(a => a.LeaveType)
.FindAsync(a => a.EmployeeId == userId, cancellationToken);

Disabling change tracking​

Set Tracking = false for read-only queries that do not need to participate in the change tracker:

_repo.Tracking = false;
var list = await _repo.FindAsync(lt => lt.DefaultDays > 0, cancellationToken);

Paged queries​

IPaginatedList<LeaveType> page = await _repo.FindAsync(
expression: lt => lt.DefaultDays > 0,
orderByExpression: lt => lt.Name,
orderByAscending: true,
pageNumber: 1,
pageSize: 10,
token: cancellationToken);

Using specifications​

var spec = new AllocationExistsSpec(userId, leaveTypeId, year);
long count = await _allocationRepo.GetCountAsync(spec, cancellationToken);

API Summary​

TypePurpose
IEFCorePersistenceBuilderFluent startup builder with AddDbContext<TDbContext> and SetDefaultDataStore
EFCorePerisistenceBuilderConcrete implementation that registers EF Core repositories in the DI container
RCommonDbContextAbstract base class for all EF Core contexts; implements IDataStore
EFCoreRepository<TEntity>Concrete repository; implements IGraphRepository<TEntity> and lower interfaces
IGraphRepository<TEntity>Full CRUD + LINQ + paging + eager loading + Tracking property
RCommonRCommon