Generic Repository with Extended Interface
Repositories are perfect examples for implementing Generics in EF Core framework! Here is an example of a generic repository that we also extend.
public interface IGenericRepository<T> where T : class
{
Task<T> Add(T entity);
Task<T?> GetById(Guid id);
IQueryable<T> GetAll();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
Task BatchInsertAsync(List<T> entities, int batchSize = 100);
Task BatchUpdateAsync(List<T> entities, int batchSize = 100);
Task BatchDeleteAsync(List<T> entities, int batchSize = 100);
Task SaveChangesAsync();
}
public abstract class GenericRepository<T>(ApplicationDbContext dbContext) : IGenericRepository<T> where T : class
{
private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
public async Task<T> Add(T entity)
{
var dbEntity = await _dbContext.AddAsync(entity);
return dbEntity.Entity;
}
public T Update(T entity) => _dbContext.Update(entity).Entity;
public async Task<T?> GetById(Guid id) => await _dbContext.FindAsync<T>(id);
public IQueryable<T> GetAll() => _dbContext.Set<T>().AsQueryable();
public IQueryable<T> Find(Expression<Func<T, bool>> predicate) => _dbContext.Set<T>().Where(predicate);
public async Task BatchInsertAsync(List<T> entities, int batchSize = 100)
{
for (var i = 0; i < entities.Count; i += batchSize)
{
var batch = entities.GetRange(i, Math.Min(batchSize, entities.Count - i));
await _dbContext.Set<T>().AddRangeAsync(batch);
await _dbContext.SaveChangesAsync();
}
}
public async Task BatchUpdateAsync(List<T> entities, int batchSize = 100)
{
for (var i = 0; i < entities.Count; i += batchSize)
{
var batch = entities.GetRange(i, Math.Min(batchSize, entities.Count - i));
_dbContext.Set<T>().UpdateRange(batch);
await _dbContext.SaveChangesAsync();
}
}
public async Task BatchDeleteAsync(List<T> entities, int batchSize = 100)
{
for (var i = 0; i < entities.Count; i += batchSize)
{
var batch = entities.GetRange(i, Math.Min(batchSize, entities.Count - i));
_dbContext.Set<T>().RemoveRange(batch);
await _dbContext.SaveChangesAsync();
}
}
public async Task SaveChangesAsync()
{
await _dbContext.SaveChangesAsync();
}
}
public interface IAddressRepository : IGenericRepository<Address>
{
// Placeholder to extend generic repository
IQueryable<Address> GetAllWithNested();
}
public class AddressRepository(ApplicationDbContext dbContext) : GenericRepository<Address>(dbContext), IAddressRepository
{
private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
// extended implementation
public IQueryable<Address> GetAllWithNested()
{
return _dbContext.Addresses.Include(x => x.Estates);
}
}
Now we can create a business service to use them like this:
public interface IAddressService
{
Task<List<Address>> GetAddressesAsync(bool withNestedObjects = false);
}
public class AddressService(IAddressRepository addressRepository) : IAddressService
{
private readonly IAddressRepository _addressRepository = addressRepository ?? throw new ArgumentNullException(nameof(addressRepository));
public async Task<List<Address>> GetAddressesAsync(bool withNestedObjects = false)
{
return withNestedObjects ? await _addressRepository.GetAllWithNested().ToListAsync()
: await _addressRepository.GetAll().ToListAsync();
}
}
We register the services like this:
builder.Services.AddTransient<IAddressRepository, AddressRepository>();
builder.Services.AddTransient<IAddressService, AddressService>();
Since the IAddressRepository is derived from the IGenericRepository, we do not have to register it. Also notice that the implementation for the IAddressRepository is derived from both the IAddressRepository and the GenericRepository<Address> as well!
No files yet, migration hasn't completed yet!