Search code examples
c#entity-framework.net-corerepositoryunit-of-work

Implementing repositories with EF Core without creating multiples methods


I've been using EF core in my project for years without repositories layer and now I decided to implement repositories pattern for one of my projects which became very big. We have more than 30 entity models and a huge list of API endpoints.

The thing is, each endpoint returns to the client the necessary data from DB formatted by the frontend needs. Some times we want just a list of an entity, other times the same list with some related data and sometimes use some SQL aggregate functions to do some calculations.

We just use the DBContext directly in each endpoint to perform the queries as we need, but when implementing the repositories, we faced an effort obstacle which is coding several methods to get the different data formatted to our needs. Not only basic CRUD and some more operations.

My question is, this really how thing are done (creating as much methods as needed) or is there any best practices to this? Is there some way "rewrite" the DBContext so that I can use expressions and turn it generic avoiding creating so mach methods?

Thank you very much!


Solution

  • Share my actual BaseRepo

    public class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class
    {
        internal ApplicationDbContext Context;
        internal DbSet<TEntity> dbSet;
    
        public BaseRepository(ApplicationDbContext context)
        {
            this.Context = context;
            this.dbSet = context.Set<TEntity>();
        }
    
        public virtual async Task AddAsync(TEntity entity)
        {
            await dbSet.AddAsync(entity);
            await SaveAsync();
        }
    
        public virtual async Task AddRangeAsync(IEnumerable<TEntity> entities)
        {
            await dbSet.AddRangeAsync(entities);
            await SaveAsync();
        }
    
        public virtual async Task<IEnumerable<TEntity>> GetAllAsync()
        {
            return await dbSet.ToListAsync();
        }
    
        public virtual async Task<IEnumerable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
        {
            IQueryable<TEntity> query = dbSet;
    
            if (filter != null)
                query = query.Where(filter);
    
            foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }
    
            if (orderBy != null)
                return await orderBy(query).ToListAsync();
            else
                return await query.ToListAsync();
        }
    
        public virtual async Task<TEntity> GetByIdAsync(int? id)
        {
            return await dbSet.FindAsync(id);
        }
    
        public async Task Remove(TEntity entity)
        {
            dbSet.Remove(entity);
            await SaveAsync();
        }
    
        public async Task RemoveRange(IEnumerable<TEntity> entities)
        {
            dbSet.RemoveRange(entities);
            await SaveAsync();
        }
    
        public virtual async Task<TEntity> SingleOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await dbSet.SingleOrDefaultAsync(predicate);
        }
    
        public virtual async Task Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            Context.Entry(entityToUpdate).State = EntityState.Modified;
            await SaveAsync();
        }
    
        public virtual async Task UpdateRange(IEnumerable<TEntity> entitiesToUpdate)
        {
            dbSet.AttachRange(entitiesToUpdate);
            Context.Entry(entitiesToUpdate).State = EntityState.Modified;
            await SaveAsync();
        }
    
        public async Task SaveAsync()
        {
            await Context.SaveChangesAsync();
        }
    
        public virtual async Task AddUpdateOrDeleteRange(IEnumerable<TEntity> entitiesToAddOrUpdate)
        {
            await Context.BulkInsertOrUpdateOrDeleteAsync<TEntity>(entitiesToAddOrUpdate.ToList(), new BulkConfig { SetOutputIdentity = false });
            await SaveAsync();
        }
        public virtual async Task AddOrUpdateRange(IEnumerable<TEntity> entitiesToAddOrUpdate)
        {
            await Context.BulkInsertOrUpdateAsync<TEntity>(entitiesToAddOrUpdate.ToList(), new BulkConfig { SetOutputIdentity = false });
            await SaveAsync();
        }
    }
    

    The bulk ones are extensions from EFCore.BulkExtensions;

    Unit of Work

    public class UnitOfWork : IUnitOfWork, IDisposable, IAsyncDisposable
    {
        private readonly ApplicationDbContext _context;
        private ExampleRepository _exampleRepository;
    
        IDisposable _disposableResource = new MemoryStream();
        IAsyncDisposable _asyncDisposableResource = new MemoryStream();
    
        public UnitOfWork(ApplicationDbContext context)
        {
            _context = context;
        }
        public IExampleRepository ExampleRepository=> _exampleRepository = _exampleRepository ?? new ExampleRepository(_context);
    
        public async Task<int> CommitAsync()
        {
            return await _context.SaveChangesAsync();
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        public async ValueTask DisposeAsync()
        {
            await DisposeAsyncCore();
            Dispose(disposing: false);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _disposableResource?.Dispose();
                (_asyncDisposableResource as IDisposable)?.Dispose();
            }
    
            _disposableResource = null;
            _asyncDisposableResource = null;
        }
    
        protected virtual async ValueTask DisposeAsyncCore()
        {
            if (_asyncDisposableResource is not null)
            {
                await _asyncDisposableResource.DisposeAsync().ConfigureAwait(false);
            }
    
            if (_disposableResource is IAsyncDisposable disposable)
            {
                await disposable.DisposeAsync().ConfigureAwait(false);
            }
            else
            {
                _disposableResource?.Dispose();
            }
    
            _asyncDisposableResource = null;
            _disposableResource = null;
        }
    }
    

    ApplicationDbContext:

    public class ApplicationDbContext : DbContext
    {
        public DbSet<Example> Examples { get; set; }
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder) 
        {
        }
    }
    

    Hope it help's you!