I am writting an API backend application using .NET Core
BaseRepository:
public class BaseRepository<T, TPrimaryKey> : IBaseRepository<T, TPrimaryKey> where T : class where TPrimaryKey : struct
{
private readonly DatabaseContext _dbContext;
public BaseRepository(DatabaseContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IEnumerable<T>> GetAll()
{
return await _dbContext.Set<T>().ToListAsync();
}
public IQueryable<T> GetQueryable()
{
return _dbContext.Set<T>();
}
public async Task<T> Find(TPrimaryKey id)
{
return await _dbContext.Set<T>().FindAsync(id);
}
public async Task<T> Add(T entity, bool saveChanges = true)
{
await _dbContext.Set<T>().AddAsync(entity);
if (saveChanges)
await _dbContext.SaveChangesAsync();
return await Task.FromResult(entity);
}
public async Task Edit(T entity, bool saveChanges = true)
{
_dbContext.Entry(entity).State = EntityState.Modified;
if (saveChanges)
await _dbContext.SaveChangesAsync();
}
public async Task Delete(T entity, bool saveChanges = true)
{
if (entity == null)
throw new NullReferenceException();
_dbContext.Set<T>().Remove(entity);
if (saveChanges)
await _dbContext.SaveChangesAsync();
}
public async Task<IEnumerable<T>> BulkInsert(IEnumerable<T> entities, bool saveChanges = true)
{
foreach (T entity in entities)
{
await _dbContext.Set<T>().AddAsync(entity);
}
if (saveChanges)
await _dbContext.SaveChangesAsync();
return await Task.FromResult(entities);
}
public async Task BulkUpdate(IEnumerable<T> entities, bool saveChanges = true)
{
foreach (T entity in entities)
{
_dbContext.Entry(entity).State = EntityState.Modified;
}
if (saveChanges)
await _dbContext.SaveChangesAsync();
}
public async Task Save()
{
await _dbContext.SaveChangesAsync();
}
}
IBaseRepository:
public interface IBaseRepository<T, E> where T : class where E : struct
{
Task<IEnumerable<T>> GetAll();
IQueryable<T> GetQueryable();
Task<T> Find(E id);
Task<T> Add(T entity, bool saveChanges = true);
Task Edit(T entity, bool saveChanges = true);
Task Delete(T entity, bool saveChanges = true);
Task<IEnumerable<T>> BulkInsert(IEnumerable<T> entities, bool saveC
Task BulkUpdate(IEnumerable<T> entities, bool saveChanges = true);
Task Save();
}
IServiceBase:
public interface IServiceBase<TEntity, TPrimaryKey>
{
Task<TEntity> GetById(TPrimaryKey id);
Task<TEntity> GetSingle(Expression<Func<TEntity, bool>> whereCondition);
Task<IEnumerable<TEntity>> GetAll();
IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> whereCondition);
IQueryable<TEntity> GetAllQueryable();
IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> whereCondition);
Task<TEntity> Create(TEntity entity);
Task Delete(TEntity entity);
Task Update(TEntity entity);
Task<long> Count(Expression<Func<TEntity, bool>> whereCondition);
Task<long> Count();
Task<IEnumerable<TEntity>> BulkInsert(IEnumerable<TEntity> entities);
Task BulkUpdate(IEnumerable<TEntity> entities);
}
for example IAddressServices:
public interface IAddressService : IServiceBase<Address, Guid>
{
Task<Address> VerifyAddress(Address address);
}
ServiceBase:
public abstract class ServiceBase<TEntity, TRepository, TPrimaryKey> : IServiceBase<TEntity, TPrimaryKey>
where TEntity : class
where TPrimaryKey : struct
where TRepository : IBaseRepository<TEntity, TPrimaryKey>
{
public TRepository Repository;
public ServiceBase(IBaseRepository<TEntity, TPrimaryKey> rep)
{
Repository = (TRepository)rep;
}
public virtual async Task<TEntity> GetById(TPrimaryKey id)
{
return await Repository.Find(id);
}
public async Task<TEntity> GetSingle(Expression<Func<TEntity, bool>> whereCondition)
{
return await Repository.GetQueryable().Where(whereCondition).FirstOrDefaultAsync();
}
public async Task<IEnumerable<TEntity>> GetAll()
{
return await Repository.GetAll();
}
public IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> whereCondition)
{
return Repository.GetQueryable().Where(whereCondition);
}
public IQueryable<TEntity> GetAllQueryable()
{
return Repository.GetQueryable();
}
public IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> whereCondition)
{
return Repository.GetQueryable().Where(whereCondition);
}
public virtual async Task<TEntity> Create(TEntity entity)
{
return await Repository.Add(entity);
}
public virtual async Task Delete(TEntity entity)
{
await Repository.Delete(entity);
}
public virtual async Task Update(TEntity entity)
{
await Repository.Edit(entity);
}
public async Task<long> Count(Expression<Func<TEntity, bool>> whereCondition)
{
return await Repository.GetQueryable().Where(whereCondition).CountAsync();
}
public async Task<long> Count()
{
return await Repository.GetQueryable().CountAsync();
}
public async Task<IEnumerable<TEntity>> BulkInsert(IEnumerable<TEntity> entities)
{
return await Repository.BulkInsert(entities);
}
public async Task BulkUpdate(IEnumerable<TEntity> entities)
{
await Repository.BulkUpdate(entities);
}
}
and concrete implementations of services:
AddressService:
public class AddressService : ServiceBase<Address, IBaseRepository<Address, Guid>, Guid>, IAddressService
{
public AddressService(IBaseRepository<Address, Guid> rep) : base(rep)
{
}
public async Task<Address> VerifyAddress(Address address)
{
//logic
}
}
ProductController:
public class ProductController : ControllerBase
{
private readonly IProductService _productService;
private readonly IAddressService _addressService;
private readonly ILogger _logger;
private readonly IMapper _mapper;
public ProductController (IProductService productService,
IAddressService addressService,
ILogger<ProductController> logger,
IMapper mapper)
{
_packageService = packageService;
_addressService = addressService;
_logger = logger;
_mapper = mapper;
}
[HttpGet]
public async Task<IActionResult> GetAllProductsWithAddresses()
{
try
{
var products = await _productService.GetAllQueryable().Include(x => x.Address).ToListAsync();
return Ok(_mapper.Map<List<ProductResponse>>(products));
}
catch (Exception e)
{
_logger.LogError($"An unexpected error occured: ${e}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
Lets say for example if I had an POST endpoint in ProductController where I need to insert data in 3 different database tables: Address, ProductSize and ProductImage. I would have 3 services and I would call _addressService.Add(address), _productSize.Add(productSize) and _productImageService(image) in my controller. How can I support transactions here if DatabaseContext is located in BaseRepository, what is the best practice?