Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity: Injection of multiple PerResolveLifetimeManager registered types

I am using the repositories which use the unit of work pattern:

public BaseRepository(IUnitOfWork unitOfWork, IEntityFactory factory) { ... }

Previously I have only ever needed one IUnitOfWork instance injected into the repositories (using Unity), as shown below:

// Unit of work for the UserDbContext
container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>(new PerResolveLifetimeManager(), new InjectionConstructor(new UserDbContext()));

container.RegisterType<IUserRepository, UserRepository>();
container.RegisterType<ITokenRepository, TokenRepository>();

Now I need to introduce another repository, but this repository needs to use a different instance of the IUnitOfWork:

// Unit of work for the OrderDbContext
container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>(new PerResolveLifetimeManager(), new InjectionConstructor(new OrderDbContext()));

container.RegisterType<IOrderRepository, OrderRepository>();

How can I use Unity to explicity specify which IUnitOfWork gets injected into which repository?

EDIT:

Using Daniel J.G's answer, I have the following code:

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>(new PerResolveLifetimeManager(), new InjectionConstructor(new UserDbContext()));
container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>("OrderDbContext", new PerResolveLifetimeManager(), new InjectionConstructor(new OrderDbContext()));

container.RegisterType<IUserRepository, UserRepository>();
container.RegisterType<ITokenRepository, TokenRepository>();

container.RegisterType<IOrderRepository, OrderRepository>(
    new InjectionConstructor(
        new ResolvedParameter<IUnitOfWork>("OrderDbContext"),
        new ResolvedParameter<IEntityFactory<Order, int, OrderTbl>>()
    )
);

But the following exception is being thrown:

[ResolutionFailedException: Resolution of the dependency failed, type = "WebTest.Controllers.TestController", name = "(none)". Exception occurred while: while resolving. Exception is: InvalidOperationException - The type IUnitOfWork does not have an accessible constructor.

UserRepository concrete implementation:

public class UserRepository : EntityFrameworkRepository<User, UserTbl>, IUserRepository
{
    public UserRepository(IUnitOfWork unitOfWork, IEntityFactory<User, UserTbl> factory)
        : base(unitOfWork, factory)
    { }
}

I am also registering the entity factories. I.e:

container.RegisterType<IEntityFactory<User, int, UserTbl>, UserFactory>();

EntityFrameworkUnitOfWork constructor:

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    public EntityFrameworkUnitOfWork(DbContext context)
    {
        Context = context;
    }

    ...
}
like image 592
Dave New Avatar asked Jan 12 '23 08:01

Dave New


1 Answers

You can use named registrations so you can register and resolve different flavours of IUnitOfWork.

Unity alows a single unnamed registration (which is the default one) and as many named registrations as your need. So you could leave one of the IUnitOfWork registrations without name as the default registration, and add another named registration. For example:

container.RegisterType<IUnitOfWork, UnitOfWork>(new PerResolveLifetimeManager(), 
                                                new InjectionConstructor(new UserDbContext()));

container.RegisterType<IUnitOfWork, UnitOfWork>("OrderUow", 
                                                new PerResolveLifetimeManager(), 
                                                new InjectionConstructor(new OrderDbContext()));

With the code above, when you resolve an instance of IUnitOfWork without specifying any name as in container.Resolve<IUnitOfWork> you would get the instance that uses the UserDbContext. For getting any named instance you need to do something like container.Resolve<IUnitOfWork>("OrderUow") and it would give you the instance that uses OrderDbContext. (When you want to use a named registration, you are always forced to resolve the type passing the name)

The only thing left is to correctly wire the injection parameters for your repositories, so they get the proper IUnitOfWork instance. You can do that configuring a ResolvedParameter, it allows you to specify a named registration for any dependency. (See this SO question)

This way, the code for wiring your repositories would be as follows:

container.RegisterType<IUserRepository, UserRepository>();
container.RegisterType<ITokenRepository, TokenRepository>();
container.RegisterType<IOrderRepository, OrderRepository>(new InjectionConstructor(
                                       new ResolvedParameter<IUnitOfWork>("OrderUow"),
                                       //additional dependencies in the OrderRepository constructor, resolved using default Unity registrations
                                       new ResolvedParameter<IEntityFactory<Order,OrderTbl>>()));

Notice that only in the case of the OrderRepository we need to add extra information to the registration, so Unity knows that the IUnitOfWork dependency should be resolved using the specified named registration instead of the default one.

With those registrations in place, assuming you had a class with the following dependencies:

public FooClass(IOrderRepository orderRepository, IUserRepository userRepository, ITokenRepository tokenRepository)
{
    ...
}
  • Both userRepository and tokenRepository will get the same instance of IUnitOfWork, which will be using the UserDbContext context.

  • orderRepository will get a different IUnitOfWork instance, that uses the OrderDbContext context.

Edit

When using new InjectionConstructor you will need to take into account all the parameters of the constructor you want to use. In the case of your repositories you have IUnitOfWork and IEntityFactory<T1, T2> as dependencies in the constructor. I have updated the registration of IOrderRepository above with a dependency I am guessing as of type IEntityFactory<Order,OrderTbl> (In the case of UserRepository that would be of type IEntityFactory<User, UserTbl> ).

For adding the extra dependencies in the constructor add more ResolvedParameter<T> or just typeOf(T). Please note that the order of the parameters in the InjectionConstructor needs to match the order of the parameters in the constructor of the type you are registering.

  • In other words, if you have a constructor like

    public UserRepository(IUnitOfWork unitOfWork, IEntityFactory<User, UserTbl> factory)
        : base(unitOfWork, factory)
    { }
    
  • You would add an InjectionConstructor in one of these 2 ways:

    container.RegisterType<IUserRepository, UserRepository>(new InjectionConstructor(
                     //Let's assume the Uow was registered as a named registration                   
                     new ResolvedParameter<IUnitOfWork>("UserUow"),
                     //provide information about additional parameters in the constructor
                     new ResolvedParameter<IEntityFactory<User,UserTbl>>()));
    
    container.RegisterType<IUserRepository, UserRepository>(new InjectionConstructor(
                     //Let's assume the Uow was registered as a named registration                   
                     new ResolvedParameter<IUnitOfWork>("UserUow"),
                     //provide information about additional parameters in the constructor
                     typeof(<IEntityFactory<User,UserTbl>>)));
    

It's a bit cumbersome so I would leave one of the UnitOfWork registrations as the default one, saving you from using the InjectionConstructor on the repositories that use the default UnitOfWork.

Edit 2 - Sample code

Dummy interfaces and implementations:

public class DbContext
{
    public string Name { get; set; }
}

public interface IUnitOfWork
{
    DbContext DbContext { get; }
}    

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _dbContext;
    public UnitOfWork(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public DbContext DbContext { get { return _dbContext; } }
}

public interface IOrderRepository
{
    IUnitOfWork UnitOfWork { get;}
}

public class BaseRepository
{
    private readonly IUnitOfWork _uow;
    public BaseRepository(IUnitOfWork uow)
    {
        _uow = uow;
    }

    public IUnitOfWork UnitOfWork { get { return _uow; } }
}

public class OrderRepository : BaseRepository, IOrderRepository
{
    private IFooAdditionalDependency _foo;
    public OrderRepository(IUnitOfWork uow, IFooAdditionalDependency foo)
        : base(uow)
    {
        _foo = foo;
    }
}

public interface IFooAdditionalDependency { }
public class FooAdditionalDependency : IFooAdditionalDependency
{
}

public interface IUserRepository
{
    IUnitOfWork UnitOfWork { get; }
}

public class UserRepository : BaseRepository, IUserRepository
{
    public UserRepository(IUnitOfWork uow)
        : base(uow)
    {
    }
}



public interface ITokenRepository
{
    IUnitOfWork UnitOfWork { get; }
}

public class TokenRepository : BaseRepository, ITokenRepository
{
    public TokenRepository(IUnitOfWork uow)
        : base(uow)
    {
    }

}

Unity container Registration:

container.RegisterType<IUnitOfWork, UnitOfWork>(new PerResolveLifetimeManager(), 
                                    new InjectionConstructor(new DbContext{Name = "UserDB"}));
container.RegisterType<IUnitOfWork, UnitOfWork>("OrderDbContext", 
                                    new PerResolveLifetimeManager(),
                                    new InjectionConstructor(new DbContext { Name = "OrderDB" }));

container.RegisterType<IUserRepository, UserRepository>();
container.RegisterType<ITokenRepository, TokenRepository>();
container.RegisterType<IOrderRepository, OrderRepository>(new InjectionConstructor(
                                    new ResolvedParameter<IUnitOfWork>("OrderDbContext"),                                                                
                                    typeof(IFooAdditionalDependency)));

container.RegisterType<IFooAdditionalDependency, FooAdditionalDependency>();

Dummy controller requiring the repositories:

public class HomeController : Controller
{
    public HomeController(IOrderRepository orderRepository, IUserRepository userRepository, ITokenRepository tokenRepository)
    {
        Debug.WriteLine("Order repository context: {0}, User repository context:{1}", orderRepository.UnitOfWork.DbContext.Name, userRepository.UnitOfWork.DbContext.Name);
        Debug.WriteLine("Order repository context: {0}, User repository context:{1}", orderRepository.UnitOfWork.DbContext.GetType().Name, userRepository.UnitOfWork.DbContext.GetType().Name);
        Debug.Assert(orderRepository.UnitOfWork != userRepository.UnitOfWork);
        Debug.Assert(userRepository.UnitOfWork == tokenRepository.UnitOfWork);
    }

    public ActionResult Index()
    {
        return View();
    }
}
like image 97
Daniel J.G. Avatar answered Jan 13 '23 20:01

Daniel J.G.