Search code examples
c#dependency-injectionsolid-principles

Unity Container - multiple databases with generic unity of work


I am using generic unity of work for EF6 from here: https://genericunitofworkandrepositories.codeplex.com/

I have an application which uses two databases. I've created additional UnitOfWork interface and class that implements original unit of work interface:

namespace Repository.Pattern.UnitOfWork
{
    public interface ILotteryBackOfficeUnitOfWorkAsync : IUnitOfWorkAsync
    {
    }
}

Second unit of work type for second database initialization:

namespace Repository.Pattern.Ef6
{
    public class LotteryBackOfficeUnitOfWork : UnitOfWork, ILotteryBackOfficeUnitOfWorkAsync
    {
        public LotteryBackOfficeUnitOfWork(IDataContextAsync dataContext)
            : base(dataContext)
        { }
    }
}

In unity I register both unit of works for different data contexts:

public static void RegisterTypes(IUnityContainer container)
{
    // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
    // container.LoadConfiguration();

    // TODO: Register your types here
    // container.RegisterType<IProductRepository, ProductRepository>();

    var purusLotteryConnectionString = WebConfigurationManager.ConnectionStrings["PurusLotteryContext"].ConnectionString;
    var purusLotteryBackOfficeConnectionString = WebConfigurationManager.ConnectionStrings["PurusLotteryBackOfficeContext"].ConnectionString;

    container.RegisterType<IDataContextAsync, PurusLotteryContext>(new InjectionConstructor(purusLotteryConnectionString));
    container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(new HierarchicalLifetimeManager());

    container.RegisterType<IDataContextAsync, PurusLotteryBackOfficeContext>("LotteryBackOfficeContext", new InjectionConstructor(purusLotteryBackOfficeConnectionString));
    container.RegisterType<ILotteryBackOfficeUnitOfWorkAsync, LotteryBackOfficeUnitOfWork>(new HierarchicalLifetimeManager(),
        new InjectionConstructor(container.Resolve<IDataContextAsync>("LotteryBackOfficeContext")));

    container.RegisterType<IHomeService, HomeService>();
}

It works, but is this correct procedure?


Solution

  • One error I see is that you resolve during the registration phase. Not only is this dangerous (as is explained in the documentation of a different DI libary), in your case it causes the PurusLotteryBackOfficeContext to be used as constant and therefore injected as Singleton into the LotteryBackOfficeUnitOfWork. In other words, while this might seem to work during development, this will not work, because a DbContext can't be used as singleton.

    Instead, you should use Unity's auto-wiring abilities as much as you can, otherwise a DI library has no advantage (and only disadvantages) over building up object graphs by hand.

    Besides this, you are violating the Liskov Substitution Principle (LSP) in your design and this is causing you trouble in your DI configuration. You are violating the LSP because you have two incompatible implementations of the same abstraction. Both PurusLotteryContext and PurusLotteryBackOfficeContext implement IDataContextAsync, but they are incompatible, because they can't be interchanged, because they both work on a completely different database schema. Although they might seem to share the same interface, they don't share the same contract. Just look what happens when you inject a PurusLotteryContext into some class that needs to work with the backoffice. The application will break and this means you are violating the LSP.

    Solution is to give them both their own independent abstraction. At first this might seem a weird thing to do, because they both have the same methods. But remember that an interface is much more than a set of method signatures; an interface describes a contract and behavior and since both implementations work on a completely different database schema they have a completely different contract. When you separate this, your code would look like this:

    public class PurusLotteryContext : IPurusLotteryDataContextAsync { 
        public PurusLotteryContext(string conString) : base(conString) { }
    }
    
    public class LotteryUnitOfWork : ILotteryUnitOfWorkAsync {
        public LotteryUnitOfWork(IPurusLotteryDataContextAsync dc) { }
    }
    
    public class PurusLotteryBackOfficeContext : IPurusLotteryBackOfficeDataContextAsync { 
        public PurusLotteryBackOfficeContext(string conString) : base(conString) { }
    }
    
    public class LotteryBackOfficeUnitOfWork : ILotteryBackOfficeUnitOfWorkAsync {
        public LotteryBackOfficeUnitOfWork(IPurusLotteryBackOfficeDataContextAsync dc) { }
    }
    

    And this allows you to have the following registration:

    container.Register<IPurusLotteryDataContextAsync>(new HierarchicalLifetimeManager(),
        new InjectionFactory(c => new PurusLotteryContext(purusLotteryConnectionString)));
    
    container.Register<IPurusLotteryBackOfficeDataContextAsync>(
        new HierarchicalLifetimeManager(),
        new InjectionFactory(c => new PurusLotteryBackOfficeContext(
            purusLotteryBackOfficeConnectionString)));
    
    container.RegisterType<ILotteryUnitOfWorkAsync, LotteryUnitOfWork>(
        new HierarchicalLifetimeManager());
    container.RegisterType<ILotteryBackOfficeUnitOfWorkAsync, LotteryBackOfficeUnitOfWork>(
        new HierarchicalLifetimeManager());
    

    Notice a few things about this registration:

    1. The data context implementations are registered as hierarchical, because you typically want the Entity Framework DbContext to have a 'per request' lifestyle.
    2. The data context implementations are registered using a factory, compared to using the container's auto-wiring ability. This is because auto-wiring doesn't help for these classes and a factory delegate will not only make the registration simpler, because also more type-safe (the compiler will help us if we make an error).