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).

有帮助吗?

解决方案 2

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.

其他提示

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.

许可以下: CC-BY-SA归因
scroll top