Search code examples
c#asp.net-mvcdependency-injectionsimple-injector

How to register collection of instances in Simple injector


In my project I have more than one database contexts, so I have created a provider for getting the context objects based on need. My provider is looks like this.

public class DbContextProvider
{
    public Func<AccountingContext> AccountingDbContextResolver { get; set; }

    public Func<ActiveDirectryContext> ActiveDirectryDbContextResolver { get; set; }

    public AccountingContext GetAccountingDbContext() =>
        this.AccountingDbContextResolver();

    public ActiveDirectryContext GetActiveDirectryDbContext() =>
        this.ActiveDirectryDbContextResolver();
}

One ServiceBase class I have created for getting the provider

public class ServiceBase
{
    public ServiceBase(DbContextProvider contextProvider)
    {
        this.ContextProvider = contextProvider;
    }

    protected DbContextProvider ContextProvider { get; }

    public AccountingContext AccountingDbContext =>
        this.ContextProvider.GetAccountingDbContext();

    public ActiveDirectryContext ActiveDirectryDbContext =>
        this.ContextProvider.GetActiveDirectryDbContext();
}

I am using simple injector and I need to create instance of both Database contexts.

For getting the instance I have created two static methods with delegate

private static DbContextProvider CreateActiveDirectryDbContextProvider(Container container)
{
    return new DbContextProvider
    {
        ActiveDirectryDbContextResolver =
            () => container.GetInstance<ActiveDirectryContext>();
    };
}

private static DbContextProvider CreateAccountingDbContextProvider(Container container)
{
    return new DbContextProvider
    {
        AccountingDbContextResolver = () => container.GetInstance<AccountingContext>();
    };
}

And for registrations I have used the below code

var accountingProvider = CreateAccountingDbContextProvider(container);
container.RegisterInstance(accountingProvider);
var activeDirectryProvider = CreateAccountingDbContextProvider(container);
container.RegisterInstance(activeDirectryProvider);

If i run the code then I am getting an error like below

System.InvalidOperationException: 'Type DbContextProvider has already been registered. If your intention is to resolve a collection of DbContextProvider implementations, use the Container.Collection.Register overloads. For more information, see https://simpleinjector.org/coll1. If your intention is to replace the existing registration with this new registration, you can allow overriding the current registration by setting Container.Options.AllowOverridingRegistrations to true. For more information, see https://simpleinjector.org/ovrrd.'

But everything is working fine when I try with only one context that is ,

var accountingProvider = CreateAccountingDbContextProvider(container);
container.RegisterInstance(accountingProvider);

I have tried to register it using container.Collection.Register, no error is coming but I am not getting the instance in the service layer , I am getting a null reference exception there.

can someone help me to resolve this ?


Solution

  • You only have one type (DbContextProvider) that is responsible for constructing both AccountingContext and ActiveDirectryContext. From that perspective, it is really strange to create two DbContextProvider instances that are each partly initialized. A consumer would not expect GetAccountingDbContext() to return null or throw a NullReferenceException. So instead, you should create one single instance that can be used for both cases:

    container.Register<ActiveDirectryContext>(Lifestyle.Scoped);
    container.Register<AccountingContext>(Lifestyle.Scoped);
    
    container.RegisterInstance<DbContextProvider>(new DbContextProvider
    {
        ActiveDirectryDbContextResolver = () => container.GetInstance<ActiveDirectryContext>(),
        AccountingDbContextResolver = () => container.GetInstance<AccountingContext>()
    });
    

    Or better, make DbContextProvider immutable:

    container.RegisterInstance<DbContextProvider>(new DbContextProvider(
        activeDirectryDbContextResolver: () => container.GetInstance<ActiveDirectryContext>(),
        accountingDbContextResolver: () => container.GetInstance<AccountingContext>()));
    

    This fixes the problem, because there is no only one registration for DbContextProvider. This removes the ambiguity, prevents possible bugs, and is a simpler solution.

    But while this would work, I would like to suggest a few changes to your design.

    Composition over Inheritance

    First of all, you should get rid of the ServiceBase base class. Although base classes are not bad per see, when they start to get dependencies of their own, they likely start to violate the Single Responsibility Principle, and their derivatives the Dependency Inversion Principle and the Open/Closed Principle:

    • Base classes with dependencies often become a hodgepodge of functionality—often cross-cutting concerns. The base class becomes an ever-growing class. Ever-growing classes are an indication of a Single Responsibility Principle violation.
    • When the base class starts to contain logic, the derivatives automatically depend on that behavior—A derivative is always strongly coupled to its base class. This makes it hard to test the derivative in isolation. In case you ever want to replace or mock the behavior of the base class, it means that its behavior is Volatile. When a class is tightly coupled with a Volatile Dependency, it means you are violating the Dependency Inversion Principle.
    • The base class's constructor dependencies need to be supplied by the derived class's constructor. This will cause sweeping changes when the base class requires a new dependency, because all derived constructors need to be updated as well.

    Instead, of using base classes, do the following:

    • Instead of forwarding dependencies from the derived class to the base class constructor, the derived class should store the dependency itself in a private field. It can use that dependency directly.
    • In case the base class contains behavior besides code:
      • In case that behavior is Volatile, wrap the logic in a class, hide that class behind an abstraction and inject the class (through its abstraction) into the constructor of the derived class. When doing this, it becomes very easy to see what dependencies the derived class has.
      • In case the behavior is Stable, you can use static helper classes or extension methods to make the base class's behavior reusable.
      • In case the behavior concerns a cross-cutting concern, consider the use of Decorators or Dynamic Interception as an alternative to base classes.

    When you follow this advice, what you end up with is a set of (derived) service classes that depend on a base class that is nothing more than an empty shell. This is when you can remove the base class altogether. What you now achieved is Composition over Inheritance. Composition typically leads to more maintainable systems than inheritance does.

    Closure Composition Model

    As JoostK mentioned, you can also inject the DbContext types directly into consumers. Whether or not you want to do this, however, depends on the type of composition model you decided to use. What this means is that, when you choose to apply the Closure Composition Model, you should typically inject the DbContext implementations directly into your consumers.

    public class ProduceIncomeTaxService : IHandler<ProduceIncomeTax>
    {
        private readonly AccountingContext context;
    
        // Inject stateful AccountingContext directly into constructor
        public ProduceIncomeTaxService(AccountingContext context) => this.context = context;
    
        public void Handle(ProduceIncomeTax command)
        {
            var record = this.context.AccountingRecords
                .Single(r => r.Id == command.Id);
            
            var tax = CalculateIncomeTax(record);
            
            FaxIncomeTax(tax);
            
            this.context.SaveChanges();
        }
        
        ...
    }
    

    This simplifies the registrations of your system, because now you just register the DbContext implementaions and you're done:

    container.Register<ActiveDirectryContext>(Lifestyle.Scoped);
    container.Register<AccountingContext>(Lifestyle.Scoped);
    
    // Of course don't forget to register your service classes.
    

    Interface Segregation Principle

    Your current DbContextProvider seems to designed around the Ambient Composition Model. There are advantages of both composition models, and you might have chosen deliberately for the Ambient Composition Model.

    Still, however, the DbContextProvider exposes many (10) properties—one for each DbContext. Classes and abstractions with many methods can cause a number of problems concerning maintainability. This stems from the Interface Segregation Principle that prescribes narrow abstractions. So instead of injecting one wide provider implementation that gives access to a single DbContext type. Implementations would typically only require access to a single DbContext type. If they require multiple, the class should almost certainly be split up into smaller, more-focused classes.

    So what you can do instead is create a generic abstraction that allows access to a single DbContext type:

    public interface IDbContextProvider<T> where T : DbContext
    {
        T Context { get; }
    }
    

    When used in a consumer, this would look as follows:

    public class ProduceIncomeTaxService : IHandler<ProduceIncomeTax>
    {
        private readonly IDbContextProvider<AccountingContext> provider;
    
        // Inject an ambient context provider into the constructor
        public ProduceIncomeTaxService(IDbContextProvider<AccountingContext> provider)
            => this.provider = provider;
    
        public void Handle(ProduceIncomeTax command)
        {
            var record = this.provider.Context.AccountingRecords
                .Single(r => r.Id == command.Id);
            
            var tax = CalculateIncomeTax(record);
            
            FaxIncomeTax(tax);
            
            this.provider.Context.SaveChanges();
        }
        
        ...
    }
    

    There are multiple ways to implement IDbContextProvider<T>, but you can, for instance, create an implementation that directly depends on Simple Injector:

    public sealed class SimpleInjectorDbContextProvider<T> : IDbContextProvider<T>
        where T : DbContext
    {
        private readonly InstanceProducer producer;
    
        public SimpleInjectorDbContextProvider(Container container)
        {
            this.producer = container.GetCurrentRegistrations()
                .FirstOrDefault(r => r.ServiceType == typeof(T))
                ?? throw new InvalidOperationException(
                    $"You forgot to register {typeof(T).Name}. Please call: " +
                    $"container.Register<{typeof(T).Name}>(Lifestyle.Scope);");
        }
        
        public T Context => (T)this.producer.GetInstance();
    }
    

    This class uses the injected Container to pull the right InstanceProducer registration for the given DbContext type. If this is not registered, it throws an exception. The InstanceProducer is then used to get the DbContext when Context is called.

    Since this class depends on Simple Injector, it should be part of your Composition Root.

    You can register it as follows:

    container.Register<ActiveDirectryContext>(Lifestyle.Scoped);
    container.Register<AccountingContext>(Lifestyle.Scoped);
    
    container.Register(
        typeof(IDbContextProvider<>),
        typeof(SimpleInjectorDbContextProvider<>),
        Lifestyle.Singleton);