-1

How to dependency inject repositories that inherit an abstract class (that inherits another abstract class) which implements the interface?

I will give you a simplified version of my current architecture.

Architecture (simplified):

public interface IConcreteRepository<TEntity, TId> where TEntity : class
{
     // a lot of methods here, that are common for Repository Pattern
}

public abstract class BaseEntityFrameworkRepository<TEntity, TId> : IConcreteRepository<TEntity, TId> where TEntity : class
{
    protected ApplicationDbContext Context { get; private set; }
    protected DbSet<TEntity> Entities { get; private set; }

    public BaseEntityFrameworkRepository(ApplicationDbContext context)
    {
        Context = context;
        Entities = context.Set<TEntity>();
    }
    public abstract Task<TEntity> GetByIdAsync(TId id);

    // a lot of implemented and abstract methods here
}

The most important class for my problem:

public abstract class BaseEntityRepository<TEntity, TId> : BaseEntityFrameworkRepository<TEntity, TId> where TEntity : BaseEntity<TId> where TId : struct
{
    public BaseEntityRepository(ApplicationDbContext context) : base(context)
    {
    }

    // this is the abstract class that I directly inherit when creating the repositories for each entity
}

Similar to the Base Entity Repository, but the ID is a composite key (ITuple constraint):

public abstract class BaseAssociativeEntityRepository<TEntity, TId> : BaseEntityFrameworkRepository<TEntity, TId> where TEntity : BaseAssociativeEntity where TId : ITuple
{
    public BaseAssociativeEntityRepository(ApplicationDbContext context) : base(context)
    {
    }

    // similar to the BaseEntityRepository, but uses a Tuple ID (composite key)
}

This is an example for a concrete repository in my architecture:

public class CurriculumVitaeRepository : BaseEntityRepository<CurriculumVitae, int>
{
    public CurriculumVitaeRepository(ApplicationDbContext context) : base(context)
    {
    }

    public sealed override async Task<CurriculumVitae> GetByIdAsync(int id)
    {
        // extra logic or different logic goes here
    }

    // this is where I put the extra classes that this concrete repository needs or override virtual methods from the base abstract class
}

This is my architecture. The problem is that in the current architecture I can't dependency inject the repository in a Service class with the IConcreteRepository interface, because it's lacking a lot of methods.

The question:

Are there any design patterns that could help me with the problem? Is replacing the BaseEntityFramework abstract class with 2 interfaces IEntityRepository and IAssociativeEntityRepository that will be implemented directly to BaseEntityRepository and BaseAssociativeEntityRepository the solution here? If I remove entirely BaseEntityFrameworkRepository, I will have to basically copy-paste a lot of logic into the other 2 classes (that are his children now).

  • Why is IIdentityUserRepository mentioned here? I don't see it being used anywhere. Or did I miss it? – Flater Jun 4 at 9:54
  • 1
    "The problem is that in the current architecture I can't dependency inject the repository in a Service class with the IConcreteRepository interface, because it's lacking a lot of methods." It's really hard to understand your problem if you don't actually show where your GetByIdAsync method was originally defined. Based on your comments I would guess that this method is defined in the IConcreteRepository or any of its ancestors, but your question seems to assert that that's not the case. A concrete example would really help in identifying the problem. – Flater Jun 4 at 9:57
  • 1
    Yes, it's not important. I added it to show why I split the IRepository and IConcreteRepository on 2 different interfaces. But you're right. – Nikolai Jun 4 at 9:58
  • I suggest removing it then, as this question is quite the jungle of inheritance already. Boiling things down to their core essentials should always be the first step before even posting an answer as it often reveals the problem/solution by itself (and if it doesn't, the question you end up posting will be a lot clearer for others). – Flater Jun 4 at 10:00
  • 1
    @Flater Most of the methods are declared in either BaseEntityFrameworkRepository or its child BaseEntityRepository. IConcreteRepository is lacking a lot of methods. – Nikolai Jun 4 at 10:02
1

I can't dependency inject the repository in a Service class with the IConcreteRepository interface, because it's lacking a lot of methods

Phrasing matters here. You can inject the repository as an IConcreteRepository, but you can't do much with the IConcreteRepository after it's injected, since its contract is lacking many of the methods that you'd want to make use of.

The solution is quite simple. Either:

  • Add the necessary methods to the contract of the type you're injecting
  • Use an appropriate injected type that contains the methods you're looking for (BaseEntityFrameworkRepository would be the preferred type in your example as it contains the things you're looking for)

I'm unsure why you haven't just moved the necessary method definitions to the interface, i.e. in your current example:

public interface IConcreteRepository<TEntity, TId> where TEntity : class
{
     Task<TEntity> GetByIdAsync(TId id);
}

But this solution is so straightforward compared to the much more complex architecture you've set up that I'm genuinely unsure whether there's a more advanced consideration you haven't communicated yet, or whether you've bitten off more than you can chew and have massively overengineered your architecture. My gut is telling me the latter.

| improve this answer | |
0

The answer is Decorator pattern.

Creating 2nd implementation of IConcreteRepository but this time wrapping already existing repository (the other implementation of IConcreteRepository). Injecting already existing repository through the constructor allows the abstract BaseDecoratedRepository to wrap all of the IConcreteRepository methods by calling them through the injected private repository. Then it's easy to create a DecoratedConcreteRepository implementation that will have all of the wrapped methods and can add the EXTRA methods declared in its own IDecoratedConcreteRepository interface that implements IConcreteRepository.

This way the decorated class will have his own interface and the default implementation will remain Closed for modification and extended with the decorated variant. Open-closed principle not violated. Problem solved.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.