WriteRepositoryBase
Namespace: RA.Utilities.Data.EntityFramework
WriteRepositoryBase<T, TKey> is the EF Core implementation of IWriteRepositoryBase<T, TKey>. It provides commands for any entity derived from CoreEntity<TKey> and exposes no query methods.
🎯 Purpose
Use this repository on the command side of your application. It adds, updates, and deletes entities in the EF Core change tracker and saves them through SaveChangesAsync. Mutations follow the mark-then-save model: nothing reaches the database until SaveChangesAsync runs.
SaveChangesAsync commits every pending change in the scoped DbContext, not just the entity passed to the last repository call. That is what makes multiple repositories sharing one context behave as a single unit of work.
Class signature
public class WriteRepositoryBase<T, TKey> : IWriteRepositoryBase<T, TKey>
where T : CoreEntity<TKey>
where TKey : notnull
Constructor
| Constructor | Description |
|---|---|
WriteRepositoryBase(DbContext dbContext) | Stores the context and resolves DbContext.Set<T>(). Throws ArgumentNullException when dbContext is null. |
Methods
| Method | Returns | Behavior |
|---|---|---|
AddAsync(T entity, CancellationToken cancellationToken = default) | Task<T> | Marks the entity Added and returns it. The key is generated by EF Core on save when the provider supports it. |
AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default) | Task<int> | Marks every entity Added and returns the number of entities supplied. |
UpdateAsync(T entity, CancellationToken cancellationToken = default) | Task<T> | Marks the entity Modified and returns it. |
UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default) | Task<int> | Marks every entity Modified and returns the number of entities supplied. |
DeleteAsync(TKey id, CancellationToken cancellationToken = default) | Task | Loads the entity by key and marks it Deleted. No-op when the key is not found; no exception is thrown. |
DeleteRangeAsync(IEnumerable<TKey> ids, CancellationToken cancellationToken = default) | Task<int> | Loads the matching entities, marks them Deleted, and returns how many were found. |
SaveChangesAsync(CancellationToken cancellationToken = default) | Task<int> | Calls DbContext.SaveChangesAsync; returns the number of state entries written. |
Delete semantics
Deletes are deferred. DeleteAsync and DeleteRangeAsync only change the entity state; call SaveChangesAsync to persist the deletion. Because the work happens in the shared context, a delete can be combined with inserts and updates in the same transaction:
await repository.DeleteAsync(oldId, cancellationToken);
await repository.AddAsync(replacement, cancellationToken);
// One transaction: delete + insert
await repository.SaveChangesAsync(cancellationToken);
🚀 How to Use
The examples below assume public sealed class Product : CoreEntity<Guid> { /* properties */ } and a registered AppDbContext. See Getting Started for the full setup.
Register the repository
using RA.Utilities.Data.EntityFramework.Extensions;
builder.Services.AddDbContext<DbContext, AppDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddWriteRepositoryBase();
Execute a command
using RA.Utilities.Data.Abstractions;
using RA.Utilities.Data.Entities;
public sealed class RegisterProduct
{
private readonly IReadRepositoryBase<Product, Guid> _reads;
private readonly IWriteRepositoryBase<Product, Guid> _writes;
public RegisterProduct(
IReadRepositoryBase<Product, Guid> reads,
IWriteRepositoryBase<Product, Guid> writes)
{
_reads = reads;
_writes = writes;
}
public async Task RenameAsync(Guid id, string name, CancellationToken cancellationToken = default)
{
// Commands often need a lookup; use the read repository for it.
Product? product = await _reads.GetByIdAsync(id, cancellationToken);
if (product is null)
{
return;
}
product.Name = name;
await _writes.UpdateAsync(product, cancellationToken);
await _writes.SaveChangesAsync(cancellationToken);
}
}
⚠️ Constraints
- No queries:
IWriteRepositoryBase<T, TKey>exposes only commands. InjectIReadRepositoryBase<T, TKey>when a command must load an entity first. - No explicit transaction API: use
DbContext.Database.BeginTransactionAsyncin a custom repository when you need a transaction boundary beyond a singleSaveChangesAsync. UpdateAsyncmarks the whole entity modified: EF Core writes all mapped columns, not only the ones you changed. For partial updates, useExecuteUpdateAsyncon theDbContextin a custom repository.- Range counts are input counts:
AddRangeAsyncandUpdateRangeAsyncreturn the number of entities passed in, not rows affected (nothing is saved yet).