Search code examples
c#.netdependency-injectionautofac

When does IIndex<K,V> instanciates its resolved components?


I searched about topics related to this question, but I didnt find the answer i was looking for. Sorry in advance if this has already been said.

Let's present my question : I've a concrete class, let's call it BLLClass, on which I inject by constructor two implementations of IRepository. Each of thoses implementations has its own DbContext type. I want to have access to both IRepository at anytime, to be able to perform actions on any database at anytime, and to dispose them once i've finished. Originally, I went with something like that :

public BLLClass : IDisposable
{
    private Dictionary<eBaseSQL,IRepository<SomeObjectType>> Repositories;

    public BLLClass(IIndex<eBaseSql, IRepository<SomeObjectType> repositories)
    {
        Repositories = new Dictionary<eBaseSQL, IRepository<SomeObjectType>>();
        Repositories.Add(eBaseSQL.DatabaseOne, repositories[eBaseSQL.DatabaseOne]);
        Repositories.Add(eBaseSQL.DatabaseTwo, repositories[eBaseSQL.DatabaseTwo]);
    }

    public void Dispose()
    {
        foreach(IRepository<SomeObjectType> Repository in Repositories.Values)
        {
            Repository.Dispose();
        }   
    }
}

This is working fine and doing exactly what I want. But, I find it kinda ugly to add my implementations into a Dictionary while Autofac provides IIndex, which, is not a dictionary but LOOKS LIKE to act like one.

So here's my questions : Can i use IIndex like I do with Dictionary ? When does my concrete implementation of IRepository gets instanciated ? Will it create a new object eachtime I do

// Let's say that "repositories" is a IIndex<K,V>
IRepository<SomeObjectType> someRepository = repositories[someEnum.SomeValue];

Or it will give always the same object ? This really concerns me because, if IIndex instanciates a new Repository everytime I use one, I won't be able to dispose them afterwards.

Thanks for your time, and sorry for my english that is clearly, not the best.


Solution

  • The injected IIndex does act like a factory, as you say. With

    // Let's say that "repositories" is a IIndex<K,V>
    IRepository<SomeObjectType> someRepository = repositories[someEnum.SomeValue];
    

    That will return either a new or existing object, depending on how everything has been registered. By default, you would get a new object every time repositories[someEnum.SomeValue] is called.

    Regarding disposal, I think you can leverage one of several built-in Autofac options to get the behaviour you want, if you switch your Repositories field to this:

    public BLLClass : IDisposable
    {
        private IIndex<eBaseSQL,IRepository<SomeObjectType>> Repositories;
    

    First question: is it important to have the same repo of each type for all operations which the BLLClass executes in its lifetime? The overhead creating objects such as your repos each time they're used is generally pretty small, and using shorter-lived objects can help keep things simple and clean. In that case, if your registrations for the repos are InstancePerDependency (this is also the default), in your BLLClass, operations would just look like:

    public void DoStuff()
    {
        using (var repo = Repositories[eBaseSql.SomeKey])
        {
        }
    }
    

    So the repo would be recreated every time you request it and you can safely dispose of each instance yourself with using. That would still work resolving the repos multiple times, like:

    public void DoStuff()
    {
        using (var repo = Repositories[eBaseSql.SomeKey]) { }
        using (var sameRepoAgainButNew = Repositories[eBaseSql.SomeKey]) { }
    }
    

    The other approach, if you do need a single instance of each repo for any calls within the lifetime of a single BLLClass, you can just register your IRepository implementation using Autofac's .InstancePerLifetimeScope:

    builder.RegisterType<Repository<SomeObjectType>>()
        .Keyed<IRepository<SomeObjectType>>(eBaseSql.SomeKey)
        .InstancePerLifetimeScope();
    

    Then if in BLLClass you have:

    public void DoStuff()
    {
        var repo = Repositories[eBaseSql.SomeKey];
    }
    

    that repo instance should be disposed at the end of the life of the BLLClass. The assumption is that the BLLClass itself created from a scope which is disposed one way or another, a very simple example being:

    using (var scope = container.BeginLifetimeScope())
    {
        var bll = scope.Resolve<BLLClass>();
        bll.DoStuff();
    }
    

    The implementation of IIndex<> which is injected into the BLLClass stores a reference to a component context which is disposed when that outer scope is disposed, which in turn will dispose of any child objects which implement IDisposable (see https://autofaccn.readthedocs.io/en/latest/lifetime/disposal.html#automatic-disposal). In this case, that's the repositories we've generated. For example:

    using (var scope = container.BeginLifetimeScope())
    {
        var bll = scope.Resolve<BLLClass>();
        bll.DoStuff();      // do something using our first repo implementation
        bll.DoMoreStuff();  // do something else using the same type of repo
    }                       // here the scope is disposed, taking any generated repos with it
    
    public void DoStuff()
    {
        var repo = _repositories[eBaseSql.SomeKey];
        // some actions here
    }
    
    public void DoMoreStuff()
    {
        var repo = _repositories[eBaseSql.SomeKey];    // the same repo as used in DoStuff will be returned here because of the .InstancePerLifetimeScope registration
    }
    

    Dynamically creating objects with one of these approaches using Autofac's built-in factories also means that you won't create any repos you end up not using, which looks to be possible in your initial implementation.