Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Orbis25/FoundationKit/llms.txt
Use this file to discover all available pages before exploring further.
BaseRepository<TContext, TModel> is the abstract class that backs the IBaseRepository<TModel> interface with a full EF Core implementation. Inherit from it, pass your DbContext subclass as TContext and your entity as TModel, and you immediately gain a transaction-wrapped CRUD layer with ordered, filterable, paginated queries — no boilerplate required. All virtual methods can be overridden if you need to customise behaviour for a specific entity.
Because the class is abstract it can never be instantiated directly; you must create a concrete subclass for each entity type you want to manage. Dependency injection is typically set up by registering the concrete class against its custom interface (or IBaseRepository<TModel> directly) in your service container.
Class Signature
TContextmust be a class deriving fromMicrosoft.EntityFrameworkCore.DbContext.TModelmust be a class deriving fromFoundationKit.Domain.Models.BaseModel, which providesId(Guid),CreatedAt,UpdatedAt,CreatedBy,UpdatedBy, andIsDeleted.
Constructor
The EF Core
DbContext instance. Inject this via the constructor of your concrete repository class. BaseRepository holds it in a private readonly field; derived classes do not have direct access to _context — all persistence goes through the base methods.Method Implementations
CreateAsync
Adds the entity to theDbSet<TModel> and calls CommitAsync to persist it inside a database transaction.
CreatedBy from IHttpContextAccessor) before the insert.
GetAll
Builds anIQueryable<TModel> with optional Where, Include, and OrderBy/OrderByDescending operators applied in that order. No database round-trip occurs until the query is enumerated.
| Condition | Applied ordering |
|---|---|
ordered != null and orderDesc == true | OrderByDescending(ordered) |
ordered != null and orderDesc == false | OrderBy(ordered) |
ordered == null and orderDesc == true | OrderByDescending(x => x.CreatedAt) (default) |
ordered == null and orderDesc == false | OrderBy(x => x.CreatedAt) |
CreatedAt descending means the most recently created records appear first without any caller configuration, which suits most list endpoints.
GetPaginatedListAsync
CallsGetAll to build the filtered, ordered query, then applies Skip/Take based on Paginate.Page and Paginate.Qyt. When Paginate.NoPaginate is true, paging is skipped and all matching rows are returned in the Results collection.
NoPaginate is false:
| Field | Value |
|---|---|
ActualPage | paginate.Page |
Qyt | paginate.Qyt |
PageTotal | Math.Ceiling(total / paginate.Qyt) |
Total | results.Count() (before Skip/Take) |
Results | The paged items (AsNoTracking) |
NoPaginate is true, only Results is populated; all numeric metadata fields are zero. This is useful for export endpoints or dropdowns that need all records but want to reuse the same API shape.
GetListAsync
Delegates toGetAll and materialises the result with ToListAsync. This is the simplest way to retrieve a filtered, ordered, in-memory collection without dealing with IQueryable composition.
GetAll: orderDesc comes first so that the most common customisation (changing sort direction) does not require naming the other arguments.
GetByIdAsync
CallsGetAll with a CreatedAt descending sort, optionally switches to AsNoTracking, then executes FirstOrDefaultAsync for the given Id.
Primary key of the entity to retrieve.
When
true, calls AsNoTracking() before executing — suitable for read-only lookups that will not be modified.Propagates cancellation.
Navigation-property selectors for eager loading.
The matched entity, or
null when no record with the given Id exists.GetOneAsync
Returns the first entity in theDbSet that matches the predicate, using EF Core’s FirstOrDefaultAsync.
GetByIdAsync, no includes or ordering are applied — the query goes directly to FirstOrDefaultAsync. Use this for simple single-row lookups by a non-primary-key predicate.
UpdateAsync
AppliesDbSet<TModel>.Update(model) and commits. The verifyEntity flag controls whether a pre-fetch guards against overwriting audit fields.
verifyEntity == true:
GetByIdAsync(model.Id, asNotTraking: true)is called to retrieve the current stored entity.- If the entity is not found,
nullis returned immediately (no update is attempted). CreatedByandCreatedAtfrom the stored entity are copied ontomodelto prevent accidental audit-field corruption.Update(model)andCommitAsyncare called.
verifyEntity == false the pre-fetch is skipped entirely — use this when you have already loaded the entity in the same unit of work and can guarantee the audit fields are correct.
A two-parameter convenience overload is also available; it delegates to the full overload with verifyEntity: false:
UpdatePartialEntityAsync
Attaches the entity to the change tracker without loading it from the database, then marks only the properties inupdateExpression as EntityState.Modified. EF Core generates an UPDATE statement targeting only those columns.
SaveChangesAsync directly rather than going through CommitAsync (no explicit transaction wrapper). If you need transactional guarantees, call CommitAndResultAsync or manage the transaction externally.
Example — update only the Price column:
SoftRemoveAsync
Fetches the entity byId, sets IsDeleted = true, then calls Update + CommitAsync. The row is retained in the database with IsDeleted flagged so queries can filter it out.
false immediately (without touching the database) when no entity with the given Id exists.
RemoveAsync
Permanently deletes the entity by fetching it withGetByIdAsync(id, asNotTraking: true) and then calling DbSet<TModel>.Remove followed by CommitAsync.
false immediately when no entity with the given Id exists. This operation cannot be undone.
ExistAsync
Returns whether at least one entity matching the optional predicate exists. Whenexpression is null, calls AnyAsync() without a predicate — returning true if the table has any rows.
Optional filter predicate. When
null, returns true if the table contains any rows.Propagates cancellation.
true if at least one matching row exists; otherwise false.CountAsync
Chains zero or moreWhere predicates on the DbSet<TModel> and returns the row count.
Propagates cancellation.
Zero or more filter predicates. Each is applied as a successive
Where call (logical AND). Passing no expressions counts all rows.The row count satisfying all predicates.
CommitAsync
WrapsSaveChangesAsync in an explicit database transaction. On success the transaction is committed; on any exception the transaction is rolled back and a DbUpdateException is thrown with the base exception’s message.
CommitAsync is public and can be called directly from a concrete repository when you have staged multiple change-tracker mutations (e.g., Add + Update on different entity sets) that must commit atomically. All of the CreateAsync, UpdateAsync, SoftRemoveAsync, and RemoveAsync methods call CommitAsync internally — they each run in their own transaction. If you need multiple operations in a single transaction, stage them manually and call CommitAsync once at the end.CommitAndResultAsync
Identical in behaviour toCommitAsync but returns a string? instead of throwing on failure. Returns null when the commit succeeds, or the error message string when it fails (transaction is still rolled back).
Minimal Implementation Example
Every
CreateAsync, UpdateAsync, SoftRemoveAsync, and RemoveAsync call wraps its SaveChangesAsync in BeginTransaction / CommitAsync / RollbackAsync. If your database provider does not support explicit transactions (e.g., some in-memory providers used in unit tests), switch to CommitAndResultAsync or mock CommitAsync in your test setup.