0

Consider the following generic Repository interface and its implementation:

public interface IRepository<T>
{
    IQueryable<T> FindAll(List<string> includes = null);

    void Add(T entity);

    void Update(T entity);

    void Remove(T entity);
}

public class Repository<T> : IRepository<T> where T : class
{
    protected DatabaseContext _dbContext { get; set; }

    protected ILogger<Repository<T>> _logger { get; set; }
    
    public Repository(DatabaseContext dbContext, ILogger<Repository<T>> logger)
    {
        _logger = logger;
        _dbContext = dbContext;
    }

    public IQueryable<T> FindAll(List<string> includes = null)
    {
        try
        {
            IQueryable<T> items = _dbContext.Set<T>().AsNoTracking();

            if (includes != null && includes.Any())
            {
                includes.Where(i => i != null).ToList().ForEach(i => { items = items.Include(i); });
            }

            return items;
        }
        catch (Exception e)
        {
            _logger.LogError(e, "{Repo} FindAll function error", typeof(Repository<T>));
            return null;
        }
    }

    public void Add(T entity)
    {
        try
        {
            _dbContext.Set<T>().Add(entity);
        }
        catch (Exception e)
        {
            _logger.LogError(e, "{Repo} Add function error", typeof(Repository<T>));
        }
    }

    public void Update(T entity)
    {
        try
        {
            _dbContext.Set<T>().Update(entity);
        }
        catch (Exception e)
        {
            _logger.LogError(e, "{Repo} Update function error", typeof(Repository<T>));
        }
    }

    public void Remove(T entity)
    {
        try
        {
            _dbContext.Set<T>().Remove(entity);
        }
        catch (Exception e)
        {
            _logger.LogError(e, "{Repo} Remove function error", typeof(Repository<T>));
        }
    }
}

In order to use this with a database entity like User, I have two choices.

Either inherit UserRepository implementation from generic Repository:

public class UserRepository : Repository
{
    public UserRepository(MyDbContext context) : base(context)
    {
    }
}

or create an instance of generic IRepository interface in the UserRepository implementation and use dependency injection:

public class UserRepository : IUserRepository
{
    private readonly IRepository _repo;

    public UserRepository(IRepository repo)
    {
        _repo = repo;
    }
}

As per my limited experience with the Repository pattern in ASP.NET Core, I can use either of these and achieve the same results in a CRUD app that uses a database.

My question though is, which one should be used in which scenario?

Update:

I can still create and inject IUserRepository by using the inheritance approach.

public partial interface IUserRepository : IRepository
{
}

public class UserRepository : Repository, IUserRepository
{
    public UserRepository(MyDbContext context) : base(context)
    {
    }
}
Junaid
  • 941
  • 2
  • 14
  • 38
  • You may want to read: https://stackoverflow.com/questions/50457007/in-memory-database-doesnt-save-data/50457230#50457230 – Camilo Terevinto Jan 06 '22 at 10:24
  • I've generally seen it being implemented as in this article => https://dev.to/fullstackcodr/net-generic-repository-pattern-4gf – berkansasmaz Jan 06 '22 at 10:29
  • You should not be using the Generic Repository Anti-Pattern with EF: your `DbContext` and `DBSet` types **are** your "repository", aieeeeee. – Dai Jan 06 '22 at 10:32
  • @berkansasmaz The "repository pattern" [is an **anti-pattern** when used with Entity Framework](https://rob.conery.io/2014/03/04/repositories-and-unitofwork-are-not-a-good-idea/). There is no legitimate need to use it in this scenario. – Dai Jan 06 '22 at 10:33
  • @Dai it is the client's requirement to use this pattern. Lol – Junaid Jan 06 '22 at 10:58
  • @Junaid You have a _professional responsibility_ to correct the client and push-back when they're asking for something which is a bad idea. – Dai Jan 06 '22 at 11:25
  • @Dai the client is a senior software engineer himself. Lol... – Junaid Jan 06 '22 at 14:19
  • 1
    @Dai One more question, and also, pardon my ignorance, one of the reasons I would want to write `Repository` is to hide the usage of the ORM framework in the controller. It would not be the case if I use the DbContext instance directly in Controller. If I want to change ORM I would only need to change the repository implementation and not the code in all controllers/services. What do you think about that? – Junaid Jan 06 '22 at 14:19
  • @Junaid You cannot realistically abstract an ORM. In a typical CRUD app today the ORM you're using is _fundamental_ to your entire application, so there's no point trying to make it easy to swap-in and swap-out - and if you do you'll see quickly that it's a _leaky abstraction_ and you'll have wasted hundreds of hours. If you want testability then use an EF `DbContext` scaffolding tool that generates interfaces and fake/stub/mock implementations for you (EF Core's built-in scaffolding is so anemic it's almost useless). – Dai Jan 06 '22 at 14:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/240794/discussion-between-junaid-and-dai). – Junaid Jan 06 '22 at 14:53
  • 1
    One question to consider: If you decide to change anything related to how queries are executed, which approach will require you to modify fewer classes? Inheritance is more likely to couple code to implementation details than dependency injection. – Scott Hannen Jan 06 '22 at 16:35

1 Answers1

1

I can't help but notice that the IRepository lost its genetic parameter.

The question to ask here is what you can gain by injecting the IRepository inside your repositories. Are there going to be multiple implementations of the IRepository in your applications lifetime?

Your abstraction here is the repository itself and that's what needs to be injected, not the IRepository itself that I can't see any reason to have multiple or alternate implementations.

As it is also mentioned by Dai in the comments, you should not use it at all. The Generic repository has no point with EF and some people think there is no point at all.

Athanasios Kataras
  • 25,191
  • 4
  • 32
  • 61
  • Thanks for your answer. I am not an expert on this pattern, just trying to understand it as I have seen it being used in both of the aforementioned ways. So, if I understood this right, you made two points. I should use `Repository` directly in my controllers/services (a) or I should not use it altogether and use the DbContext directly in my controllers/services(b). Right? – Junaid Jan 06 '22 at 11:01
  • Pardon my ignorance, but one reason to write `Repository` is to not expose the usage of the ORM framework in controllers. If I want to change ORM I would only need to change the repository implementation and not the code in controllers/services. Kindly shed some light on it. – Junaid Jan 06 '22 at 14:22
  • Sure, in your case that's the IUserRepository. The IRepository itself is the abstraction I don't understand. Especially without the generic parameter. – Athanasios Kataras Jan 06 '22 at 14:56