Skip to main content

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​

ConstructorDescription
WriteRepositoryBase(DbContext dbContext)Stores the context and resolves DbContext.Set<T>(). Throws ArgumentNullException when dbContext is null.

Methods​

MethodReturnsBehavior
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)TaskLoads 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​

Assumed entity

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. Inject IReadRepositoryBase<T, TKey> when a command must load an entity first.
  • No explicit transaction API: use DbContext.Database.BeginTransactionAsync in a custom repository when you need a transaction boundary beyond a single SaveChangesAsync.
  • UpdateAsync marks the whole entity modified: EF Core writes all mapped columns, not only the ones you changed. For partial updates, use ExecuteUpdateAsync on the DbContext in a custom repository.
  • Range counts are input counts: AddRangeAsync and UpdateRangeAsync return the number of entities passed in, not rows affected (nothing is saved yet).