Search code examples
c#asp.net-corerepository-patterncomposition

Compositon and Repository pattern


I'm want to use BloggingRepository class inside of CategoryRepository by injection, and accessing the BloggingRepository methods in controllers via CategoryRepository

But I couldn't achive this because I can't access to bloggingRepo field in CategoryController, So I decided to do it by inheritance.

Due to I'm inexperienced in oop, I will appreciate if you could guide me why this approach not working and what is the appropriate way

In the image the first design is working but the second not class diagram image

I wanna use the Add method of BloggingRepository in SaveCategory of CategoryController

public interface IBloggingRepository
    {
        void Add<T>(T entity) where T : class;
        void Delete<T>(T entity) where T : class;
        Task<bool> SaveAll();
    }


 public interface ICategoryRepository : IBloggingRepository
    {
        Task<Category> GetCategory(int id);
    }


public class BloggingRepository : IBloggingRepository
    {
        private readonly DataContext _context;

        public BloggingRepository(DataContext context )
        {
            _context = context;
        }
        public void Add<T>(T entity) where T : class
        {
            _context.Add(entity);
        }

    }


 public class CategoryRepository : ICategoryRepository
    {
        private readonly DataContext _context;

        public readonly IBloggingRepository bloggingRepo;

        public CategoryRepository(DataContext context, IBloggingRepository bloggingRepository) 
        {
            _context = context;
            bloggingRepo = bloggingRepository;
        }
    }


public class CategoryController : Controller
    {
        private readonly ICategoryRepository _categoryRepo;

        public CategoryController(ICategoryRepository categoryRepo)
        {
            _categoryRepo = categoryRepo;
        }

        public async Task<IActionResult> SaveCategory()
        {
           // _categoryRepo.bloggingRepo.Add();
        }
    }



//Startup.cs
services.AddScoped<IBloggingRepository, BloggingRepository>();
services.AddScoped<ICategoryRepository, CategoryRepository>();

Solution

  • Your interface should describe all the methods you wish to be exposed. Your interfaces describe what a given class will implement, not how its implemented, thus not exposing the state of the class. This is good as it allows you to have several classes inherit from this interface and implement the same methods in different ways.

    In your case, you want the ability to "Add" from your CategoryRepository. Luckily for you, you already have an interface for this, IBloggingRepository.

    If you want the same methods found in IBloggingRepository in your ICategoryRepository, just implement that interface as well! Its that easy.

    METHOD 1 (does not work): In this method, the CategoryRepository is going to implement both interfaces, so the CategoryRepository MUST expose the Add method. This also provides the ability to hide the IBloggingRepository from outside the class so that the state is hidden. However, this doesn't work. Why? Only the CategoryRepository class implements both ICategoryRepository and IBloggingRepository. The ICategoryRepository doesn't implement IBloggingRepository, so the Add method is not exposed in the ICategoryRepository interface, and that is what is being used in your controller.

    // Implementation of the repository
    // Does not expose Add to the ICategoryRepository !!!
    public class CategoryRepository : ICategoryRepository, IBloggingRepository
    {
        private readonly DataContext _context;
    
        private readonly IBloggingRepository _bloggingRepo;
    
        public CategoryRepository(DataContext context, IBloggingRepository bloggingRepository) 
        {
            _context = context;
            _bloggingRepo = bloggingRepository;
        }
        // The implementation of the Add method
        public void Add<T>(T entity) where T : class
        {
            _bloggingRepo.Add(entity);
        }
    }
    

    METHOD 2 (Correct answer): In this way, we enforce that EVERY ICategoryRepository made MUST implement the IBloggingRepository. This is different than METHOD 1, as the first method doesn't imply that every ICategoryRepository will implement IBloggingRepostiory.

    // Exposes the IBloggingRepository methods to ICategoryRepository
    public interface ICategoryRepository : IBloggingRepository
    {
    }
    
    // Implementation of the repository
    public class CategoryRepository : ICategoryRepository
    {
        private readonly DataContext _context;
    
        private readonly IBloggingRepository _bloggingRepo;
    
        public CategoryRepository(DataContext context, IBloggingRepository bloggingRepository) 
        {
            _context = context;
            _bloggingRepo = bloggingRepository;
        }
        // The implementation of the Add method
        public void Add<T>(T entity) where T : class
        {
            _bloggingRepo.Add(entity);
        }
    }
    

    DEMONSTRATION:

    // Some model to be added to the repository
    // Just for demonstration purposes
    public class Blog
    {
        public Blog() { }
    }
    
    
    public class CategoryController : Controller
    {
        private readonly ICategoryRepository _categoryRepo;
    
        public CategoryController(ICategoryRepository categoryRepo)
        {
            _categoryRepo = categoryRepo;
        }
    
        public async Task<IActionResult> SaveCategory()
        {
           // _categoryRepo.bloggingRepo.Add();
           return await Task.Run(() => 
           {
               Blog blog = new Blog();
               _categoryRepo.Add(blog);
               // return IActionResult;
    
           }).ConfigureAwait(false);
        }
    }
    

    Notes about why I used ConfigureAwait(false):

    As a general rule, every piece of code that is not in a view model and/or that does not need to go back on the main thread should use ConfigureAwait false.