Search code examples
c#asp.net-coreautomapper

How to map Db class to Entity class in generic repository?


I created a generic repository class where I implement an interface. I follow clean architecture and located UserDbTbl, BaseRepository and MappingProfile classes in Infrastructure project, rest of the classes in Domain and Application projects. What's important here is that I use db-first approach and it can't be changed.

I retrieved UserDbTbl from db and want to map it to entity User in BaseRepository. It wouldn't be a problem if I mapped it in non-generic, dedicated repository (let's say UserRepository). Then I could easily map fields from UserDbTbl to Users. The issue is that I want to make mapping in generic repository BaseRepository which implements general methods applicable to various entities (Users, Products and any others).

I don't know how to map it properly in different BaseRepository methods using AutoMapper. As shown in the example below - let's say that, using ListAllAsync() method, I would like to extract data from some table using _dbContext.Set<T>().ToListAsync() and then try to map it to generic entity type <T> - in my code I just map it to IReadOnlyList<T>. Not sure if this is ok at all. I have even more problems with other methods e.g. DeleteAsync. And the final issue - how to create mapping profile (class MappingProfile)? I have no idea how could I map it generically. The more that User class have navigation property to Address class.

    // Db Class
    public partial class UserDbTbl
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Division { get; set; }
        public string CreatedBy { get; set; }
        public virtual Address Address { get; set; }
    }

    // Entity class
    public class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public Address Address { get; set; }
    }

    // Generic interface
    public interface IAsyncRepository<T> where T : class
    {
        Task<T> GetByIdAsync(int id);
        Task<IReadOnlyList<T>> ListAllAsync();
        Task<T> AddAsync(T entity);
        Task UpdateAsync(T entity);
        Task DeleteAsync(T entity);
    }

    // Interface implementation
    public class BaseRepository<T> : IAsyncRepository<T> where T : class
    {
        protected readonly RegionManagementDbContext _dbContext;
        private readonly IMapper _mapper;

        public BaseRepository(RegionManagementDbContext dbContext, IMapper mapper)
        {
            _dbContext = dbContext;
            _mapper = mapper;
        }

        public async Task<IReadOnlyList<T>> ListAllAsync()
        {
            var dbDataList = await _dbContext.Set<T>().ToListAsync();
            var domainMap = _mapper.Map<IReadOnlyList<T>>(dbDataList);

            return domainMap;
        }

        public async Task<T> AddAsync(T entity)
        {
            var dbDataAdd = await _dbContext.Set<T>().AddAsync(entity);
            var domainMap = _mapper.Map<T>(dbDataAdd);

            return domainMap;
        }

        public virtual async Task<T> GetByIdAsync(int id)
        {
            var dbDataGetId = await _dbContext.Set<T>().FindAsync(id);
            var domainMap = _mapper.Map<T>(dbDataGetId);

            return domainMap;
        }

        public async Task DeleteAsync(T entity)
        {
            var dbDataDelete = _dbContext.Set<T>().Remove(entity);
            await _mapper.Map<T>(dbDataDelete);
        }

        public async Task UpdateAsync(T entity)
        {
            var dbDataUpdate = _dbContext.Set<T>().Update(entity);
            await _mapper.Map<T>(dbDataUpdate);
        }
    }

    // add mapping profile
    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<typeof(IAsyncRepository<>), typeof(BaseRepository<>)>();
        }
    }

Solution

  • It might work with having two generic entities for your generic repository, the DB entity and the domain entity.

    Something like this:

    public class BaseRepository<TDbEntity, TDomainEntity>
        where TDbEntity : class
        where TDomainEntity : class
    {
    
        public virtual async Task<TDomainEntity> GetByIdAsync(int id)
        {
            var dbDataGetId = await _dbContext.Set<TDbEntity>().FindAsync(id);
            var domainMap = _mapper.Map<TDomainEntity>(dbDataGetId);
    
            return domainMap;
        }
    }
    

    , and then having in the mapping profile configurations for each pair of entities, like UserDbTbl to User.