Table of Contents

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

TAggregate

The aggregate root type.

TId

The 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

context DbContext

The DbContext used for persistence.

Properties

Context

Gets the underlying DbContext. Use sparingly — prefer the repository methods for standard operations.

protected DbContext Context { get; }

Property Value

DbContext

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

aggregate TAggregate

The 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

specification Specification<TAggregate>

The specification to filter by.

cancellationToken CancellationToken

A token to observe while waiting for the task to complete.

Returns

Task<int>

The number of matching aggregates.

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

specification Specification<TAggregate>

The specification to test.

cancellationToken CancellationToken

A token to observe while waiting for the task to complete.

Returns

Task<bool>

true if at least one aggregate matches; otherwise false.

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

id TId

The aggregate identifier to check.

cancellationToken CancellationToken

A token to observe while waiting for the task to complete.

Returns

Task<bool>

true if an aggregate with the given ID exists; otherwise false.

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

id TId

The aggregate identifier to search for.

cancellationToken CancellationToken

A token to observe while waiting for the task to complete.

Returns

Task<Maybe<TAggregate>>

A Maybe<T> containing the aggregate, or None if not found.

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

specification Specification<TAggregate>

The specification to filter by.

cancellationToken CancellationToken

A 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

aggregate TAggregate

The 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

id TId

The aggregate identifier to remove.

cancellationToken CancellationToken

A 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.