Class RepositoryBase<TAggregate, TId>
- Namespace
- Trellis.EntityFrameworkCore
- Assembly
- Trellis.EntityFrameworkCore.dll
Base class for EF Core repositories that persist Aggregate<TId> instances. Provides standard read and staging methods. Repositories stage changes to the DbContext change tracker; the IUnitOfWork (typically driven by a pipeline behavior) is responsible for committing staged changes.
public abstract class RepositoryBase<TAggregate, TId> where TAggregate : Aggregate<TId> where TId : notnull
Type Parameters
TAggregateThe aggregate root type.
TIdThe type of the aggregate's unique identifier.
- Inheritance
-
RepositoryBase<TAggregate, TId>
- Inherited Members
- Extension Methods
Examples
public class OrderRepository : RepositoryBase<Order, OrderId>
{
public OrderRepository(AppDbContext context) : base(context) { }
protected override IQueryable<Order> BuildFindByIdQuery()
=> base.BuildFindByIdQuery().Include(o => o.LineItems);
}
Remarks
This base class eliminates the repetitive find/query/add/remove boilerplate that appears in every concrete repository. Repositories with custom queries inherit and add methods.
Override BuildFindByIdQuery() or BuildQueryBase() to add
.Include() chains for eager loading.
Staging vs. Committing: Methods like Add(TAggregate), Remove(TAggregate),
and RemoveByIdAsync(TId, CancellationToken) stage changes in the EF Core change tracker but never
call SaveChanges. The commit boundary is owned by the pipeline
(see TransactionalCommandBehavior<TMessage, TResponse>) or by explicitly
calling CommitAsync(CancellationToken).
Constructors
RepositoryBase(DbContext)
Initializes a new instance of the RepositoryBase<TAggregate, TId> class.
protected RepositoryBase(DbContext context)
Parameters
Properties
Context
Gets the underlying DbContext. Use sparingly — prefer the repository methods for standard operations.
protected DbContext Context { get; }
Property Value
DbSet
Gets the DbSet<TEntity> for this aggregate type.
protected DbSet<TAggregate> DbSet { get; }
Property Value
- DbSet<TAggregate>
Methods
Add(TAggregate)
Stages a new aggregate for insertion. If the aggregate is already tracked,
this is a no-op (EF Core will detect modifications automatically).
Does not call SaveChanges — the commit is deferred to the pipeline or
CommitAsync(CancellationToken).
public virtual void Add(TAggregate aggregate)
Parameters
aggregateTAggregateThe aggregate to stage for insertion.
BuildFindByIdQuery()
Builds the base query used by FindByIdAsync(TId, CancellationToken). Override to add
.Include() chains for eager loading when finding by ID.
protected virtual IQueryable<TAggregate> BuildFindByIdQuery()
Returns
- IQueryable<TAggregate>
An IQueryable<T> starting from DbSet.
BuildQueryBase()
Builds the base query used by QueryAsync(Specification<TAggregate>, CancellationToken), ExistsAsync(Specification<TAggregate>, CancellationToken),
and CountAsync(Specification<TAggregate>, CancellationToken). Override to add .Include() chains for list/search queries.
The default query is no-tracking to avoid unnecessary change tracking for read operations.
protected virtual IQueryable<TAggregate> BuildQueryBase()
Returns
- IQueryable<TAggregate>
An IQueryable<T> starting from DbSet with no tracking.
CountAsync(Specification<TAggregate>, CancellationToken)
Counts aggregates matching the given specification. Uses BuildQueryBase() (no-tracking by default).
public virtual Task<int> CountAsync(Specification<TAggregate> specification, CancellationToken cancellationToken = default)
Parameters
specificationSpecification<TAggregate>The specification to filter by.
cancellationTokenCancellationTokenA token to observe while waiting for the task to complete.
Returns
ExistsAsync(Specification<TAggregate>, CancellationToken)
Checks whether any aggregate matches the given specification. Uses BuildQueryBase() (no-tracking by default).
public virtual Task<bool> ExistsAsync(Specification<TAggregate> specification, CancellationToken cancellationToken = default)
Parameters
specificationSpecification<TAggregate>The specification to test.
cancellationTokenCancellationTokenA token to observe while waiting for the task to complete.
Returns
ExistsAsync(TId, CancellationToken)
Checks whether an aggregate with the given identifier exists. Uses BuildQueryBase() (no-tracking, lightweight — no entity materialization) so that repository-level query customization (e.g., soft-delete or tenant filters) is respected.
public virtual Task<bool> ExistsAsync(TId id, CancellationToken cancellationToken = default)
Parameters
idTIdThe aggregate identifier to check.
cancellationTokenCancellationTokenA token to observe while waiting for the task to complete.
Returns
FindByIdAsync(TId, CancellationToken)
Finds an aggregate by its unique identifier. The returned aggregate is tracked by the change tracker (suitable for mutations). Returns None if not found.
public virtual Task<Maybe<TAggregate>> FindByIdAsync(TId id, CancellationToken cancellationToken = default)
Parameters
idTIdThe aggregate identifier to search for.
cancellationTokenCancellationTokenA token to observe while waiting for the task to complete.
Returns
QueryAsync(Specification<TAggregate>, CancellationToken)
Queries aggregates matching the given specification. Results are no-tracking by default (via BuildQueryBase()).
public virtual Task<IReadOnlyList<TAggregate>> QueryAsync(Specification<TAggregate> specification, CancellationToken cancellationToken = default)
Parameters
specificationSpecification<TAggregate>The specification to filter by.
cancellationTokenCancellationTokenA token to observe while waiting for the task to complete.
Returns
- Task<IReadOnlyList<TAggregate>>
A read-only list of matching aggregates.
Remove(TAggregate)
Stages an aggregate for deletion. The aggregate must be tracked or will be attached
before marking for deletion.
Does not call SaveChanges — the commit is deferred to the pipeline or
CommitAsync(CancellationToken).
public virtual void Remove(TAggregate aggregate)
Parameters
aggregateTAggregateThe aggregate to stage for deletion.
RemoveByIdAsync(TId, CancellationToken)
Looks up an aggregate by ID and stages it for deletion.
Uses FindAsync(object[], CancellationToken) directly
(not BuildFindByIdQuery()) to avoid loading heavy Include graphs.
Returns a not-found error if the aggregate does not exist.
Does not call SaveChanges — the commit is deferred to the pipeline or
CommitAsync(CancellationToken).
Query filters: Starting with EF Core 8, FindAsync applies global
query filters (soft-delete, multi-tenant). A row excluded by a filter is treated
as not-existing and yields Error.NotFound — the safe default. Tests
in RepositoryBaseFilterTests guard this contract. Override this method only
if you intentionally want to operate over filtered rows (in which case use
DbSet.IgnoreQueryFilters().FirstOrDefaultAsync(BuildIdPredicate(id), ct)).
public virtual Task<Result<Unit>> RemoveByIdAsync(TId id, CancellationToken cancellationToken = default)
Parameters
idTIdThe aggregate identifier to remove.
cancellationTokenCancellationTokenA token to observe while waiting for the task to complete.
Returns
- Task<Result<Unit>>
A Result<TValue> with Unit representing success (staged) or not-found failure.