Search code examples
.net.net-coredependency-injectionrepository-patternunit-of-work

Add Genegic interface into service. How does this code work?


I'm trying to implement Repository and Unit Of Work in my Dotnet core project.

I have a line code like this:

 services.AddScoped(typeof(IEfRepository<>), typeof(EfRepository<>));
public interface IEfRepository<TEntity> where TEntity : class
    {
        /// <summary>
        /// SingleOrDefault expression
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        Task<TEntity> SingleOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);
...

And implement:

 public class EfRepository<TEntity> : IEfRepository<TEntity> where TEntity : class
    {
        /// <summary>
        /// Fields
        /// </summary>
        private readonly DbContext _dbContext;
        private DbSet<TEntity> _entities;

        /// <summary>
        /// Initialize a new instance of the <see cref=" EfRepository{TEntity}"/> class
        /// </summary>
        /// <param name="dbContext"></param>
        public EfRepository(DbContext dbContext)
        {
            _dbContext = dbContext;
        }

When request is sent from mediator. It is handled in Handler class and service is injected from contructor:

public class KiemtracongtrinhQueryHandler : IRequestHandler<GetKiemtracongtrinhQuery, GKiemtracongtrinhDto>,
                                                IRequestHandler<ListKiemtracongtrinhQuery, IEnumerable<GKiemtracongtrinhDto>>
    {

        private readonly IGKiemtracongtrinhRepository _gKiemtracongtrinhRepository;
        private readonly IMapper _mapper;

        /// <summary>
        /// Initialize a new instance of the <see cref="KiemtracongtrinhQueryHandler"/> class
        /// </summary>
        /// <param name="gKiemtracongtrinhRepository"></param>
        /// <param name="mapper"></param>
        public KiemtracongtrinhQueryHandler(IGKiemtracongtrinhRepository gKiemtracongtrinhRepository,
                                            IMapper mapper)
        {
            _gKiemtracongtrinhRepository = gKiemtracongtrinhRepository ?? throw new ArgumentNullException(nameof(gKiemtracongtrinhRepository));
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
        }

Solution

  • I think you are on the right way. Some fixes would be:

    1. In the EfRepository class.
     public class EfRepository<TEntity> : IEfRepository<TEntity> where TEntity : class
        {
            /// <summary>
            /// Fields
            /// </summary>
            private readonly DbContext _dbContext;
    
            /// <summary>
            /// Initialize a new instance of the <see cref=" EfRepository{TEntity}"/> class
            /// </summary>
            /// <param name="dbContext"></param>
            public EfRepository(DbContext dbContext)
            {
                _dbContext = dbContext;
            }
    
            /// <summary>
            /// Initialize a new instance of the <see cref=" EfRepository{TEntity}"/> class
            /// </summary>
            /// <param name="dbContext"></param>
            public EfRepository(DbContext dbContext)
            {
                _dbContext = dbContext;
            }
            ///Example method
            public async Task AddItem(T item)
            {
                _context.Set<T>().Add(item);
                return await _context.SaveChangesAsync();
            }
            ///Example method
            public async Task<List<T>> GetAllRecords(T exampleItem)
            {
                return _context.Set<T>().ToList();
            }
    

    I can not see the reason DbEtities should be here.

    2) Your handler:

    public class KiemtracongtrinhQueryHandler : IRequestHandler<GetKiemtracongtrinhQuery, GKiemtracongtrinhDto>,
                                                    IRequestHandler<ListKiemtracongtrinhQuery, IEnumerable<GKiemtracongtrinhDto>>
        {
    
            private readonly IEfRepository<GKiemtracongtrinhRepository> _gKiemtracongtrinhRepository;
            private readonly IMapper _mapper;
    
            /// <summary>
            /// Initialize a new instance of the <see cref="KiemtracongtrinhQueryHandler"/> class
            /// </summary>
            /// <param name="gKiemtracongtrinhRepository"></param>
            /// <param name="mapper"></param>
            public KiemtracongtrinhQueryHandler(IEfRepository<GKiemtracongtrinhRepository> gKiemtracongtrinhRepository,
                                                IMapper mapper)
            {
                _gKiemtracongtrinhRepository = gKiemtracongtrinhRepository ?? throw new ArgumentNullException(nameof(gKiemtracongtrinhRepository));
                _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
            }
    

    In the handler you need to pass the generic repository.

    If you want you can take a look at this example: https://github.com/steliosgiakoumidis/NotificationDatabase

    Regarding the UnitOfWork, the way you can use it is the following:

    
        public interface IUnitOfWork
        {
            IRepository2 Repository1{ get; }
            IRepository2 Repository2 { get; }
            IRepository3 Repository3 { get; }
    
            Task<bool> SaveChangesAsync();
        }
    
     public class UnitOfWork : IUnitOfWork
        {
            public IRepository1 Repository1 { get; }
            public IRepository2 Repository2 { get; }
            public IRepository3 Repository3 { get; }
    
            private readonly _dbContext;
    
            public UnitOfWork(IRepository1  repository1, IRepository2 repository2,
                IRepository3 repository3)
            {
                Repository1 = repository1;
                Repository2 = repository2;
                Repository3 = repository3;
            }
    
            public async Task<bool> SaveChangesAsync()
            {
                try
                {
                    await _context.SaveChangesAsync();
                }
                catch (Exception ex)
                {
                    Log.Error($"An error occured when saving changes to the database.)
                }
            }
    }
    

    It is a good practice to pass the SaveChanges in the UnitOfWork and execute the method after all database actions are complete. SaveChanges method is usually called not in the database/infrastructure layer of the application layer instead. Using the unit of work you can save quite a lot of injections. Since you bundle all your repositories into one UoW and you inject this instead.

    Myself I have not used generic repostiries and UoW, but I prefered to use an abstract class instead, but the main idea is the same.