Newer
Older
Warehouse / src / Infrastructure / ApplicationDependencies / DataAccess / Repositories / Common / RepositoryBase.cs
@Derek Comartin Derek Comartin on 22 Aug 2023 3 KB Init
using MyWarehouse.Domain.Common;
using Microsoft.EntityFrameworkCore;
using MyWarehouse.Infrastructure.Persistence.Context;
using MyWarehouse.Application.Common.Dependencies.DataAccess.Repositories.Common;
using MyWarehouse.Application.Common.Mapping;

namespace MyWarehouse.Infrastructure.ApplicationDependencies.DataAccess.Repositories.Common;

/// <summary>
/// Generic base repository with implementations of basic operations.
/// Concrete derived repositories should extend it with custom querying requirements for the given entity type.
/// </summary>
internal abstract class RepositoryBaseEF<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
{
    protected ApplicationDbContext _context;
    protected DbSet<TEntity> _set;
    private readonly IMapper _mapper;

    /// <summary>
    /// Defines the base query for the given entity used by all operations.
    /// Concrete implementations should apply all necessary includes and pre-filters here.
    /// </summary>
    protected abstract IQueryable<TEntity> BaseQuery { get; }

    public RepositoryBaseEF(ApplicationDbContext context, IMapper mapper)
    {
        _context = context;
        _set = context.Set<TEntity>();
        _mapper = mapper;
    }

    public virtual async Task<TEntity?> GetByIdAsync(int id)
        => await BaseQuery.SingleOrDefaultAsync(e => e.Id == id);

    public async Task<IEnumerable<TEntity>> GetFiltered(Expression<Func<TEntity, bool>> filter, bool readOnly = false)
        => await (readOnly ? BaseQuery.AsNoTracking() : BaseQuery).Where(filter).ToListAsync();

    public virtual async Task<TDto?> GetProjectedAsync<TDto>(int id, bool readOnly = false) where TDto : IMapFrom<TEntity>
        => await (readOnly ? BaseQuery.AsNoTracking() : BaseQuery)
            .Where(x => x.Id == id)
            .ProjectTo<TDto>(_mapper.ConfigurationProvider)
            .SingleOrDefaultAsync();

    public virtual async Task<IListResponseModel<TDto>> GetProjectedListAsync<TDto>(ListQueryModel<TDto> model, Expression<Func<TEntity, bool>>? additionalFilter = null, bool readOnly = false) where TDto : IMapFrom<TEntity>
    {
        var query = readOnly ? BaseQuery.AsNoTracking() : BaseQuery;

        if (additionalFilter != null)
        {
            query = query.Where(additionalFilter);
        }

        IQueryable<TDto>? filteredDtoQuery = default;
        try
        {
            filteredDtoQuery = query
                .ProjectTo<TDto>(_mapper.ConfigurationProvider)
                .ApplyFilter(model.Filter);
        }
        catch(FormatException fe)
        {
            model.ThrowFilterIncorrectException(fe.InnerException);
        }

        var totalRowCount = await filteredDtoQuery!.CountAsync();

        IEnumerable<TDto>? resultPage = default;
        try
        {
            resultPage = await filteredDtoQuery!
                .ApplyOrder(model.OrderBy)
                .ApplyPaging(model.PageSize, model.PageIndex)
                .ToListAsync();
        }
        catch(FormatException fe)
        {
            model.ThrowOrderByIncorrectException(fe.InnerException);
        }

        return new ListResponseModel<TDto>(model, totalRowCount, resultPage!);
    }

    public virtual void Add(TEntity entity)
        => _set.Add(entity);

    public virtual void AddRange(IEnumerable<TEntity> entities)
        => _set.AddRange(entities);

    public virtual void StartTracking(TEntity detachedEntity)
        => _set.Update(detachedEntity);

    /// <summary>
    /// Removes the entity. Concrete implementations should decide if this is a hard removal or a deletion flag.
    /// </summary>
    public abstract void Remove(TEntity entityToDelete);

    /// <summary>
    /// Removes the entities. Concrete implementations should decide if this is a hard removal or a deletion flag.
    /// </summary>
    public abstract void RemoveRange(IEnumerable<TEntity> entitiesToDelete);
}