views:

75

answers:

4

We are trying to figure out how to setup Dependency Injection for situations where service classes can have different dependencies based on how they are used. In our specific case, we have a web app where 95% of the time the connection string is the same for the entire Request (this is a web application), but sometimes it can change.

For example, we might have 2 classes with the following dependencies (simplified version - service actually has 4 dependencies):

public LoginService (IUserRepository userRep)
{
}

public UserRepository (IContext dbContext)
{
}

In our IoC container, most of our dependencies are auto-wired except the Context for which I have something like this (not actual code, it's from memory ... this is StructureMap): x.ForRequestedType().Use() .WithCtorArg("connectionString").EqualTo(Session["ConnString"]);

For 95% of our web application, this works perfectly. However, we have some admin-type functions that must operate across thousands of databases (one per client). Basically, we'd want to do this:

public CreateUserList(IList<string> connStrings)
{
   foreach (connString in connStrings)
   {
       //first create dependency graph using new connection string 
       ????       
       //then call service method on new database
       _loginService.GetReportDataForAllUsers();
   }
}

My question is: How do we create that new dependency graph for each time through the loop, while maintaining something that can easily be tested?

+2  A: 

To defer the creation of an object until runtime, you can use a factory:

public interface ILoginServiceFactory
{
    ILoginService CreateLoginService(string connectionString);
}

Usage:

public void CreateUserList(IList<string> connStrings)
{
    foreach(connString in connStrings)
    {
        var loginService = _loginServiceFactory.CreateLoginService(connString);

        loginService.GetReportDataForAllUsers();
    }
}
Bryan Watts
Any thoughts on how we would mix this in with an IoC container? In our case, we have a pretty large dependency graph (the example I gave above was very simplified), and managing many dependencies manually does not sound fun.
Jess
+1 A good alternative if all relevant methods return no data is to create a Composite that takes care of iterating through all the connection strings.
Mark Seemann
Mark - can you say more? Our struggle is getting the existing service/repository methods working with a database context that changes.
Jess
A: 

Within the loop, do:

container.With("connectionString").EqualTo(connString).GetInstance<ILoginService>()

where "connectionString" is the name of a string constructor parameter on the concrete implementation of ILoginService.

Joshua Flanagan
We need a single context to be shared across all services/repositories, not just used within a single one. Given that, would it be best to rebuild the whole graph?
Jess
A: 

So most UserRepository methods use a single connection string obtained from session, but several methods need to operate against a list of connection strings?

You can solve this problem by promoting the connection string dependency from IContext to the repository and adding two additional dependencies - a context factory and a list of all the possible connections strings the repository might need to do its work:

public UserRepository(IContextFactory contextFactory, 
                      string          defaultConnectionString, 
                      List<string>    allConnectionStrings)

Then each of its methods can build as many IContext instances as they need:

// In UserRepository

public CreateUserList() {
    foreach (string connString in allConnectionStrings) {
        IContext context = contextFactory.CreateInstance(connString);
        // Build the rest of the dependency graph, etc. 
        _loginService.GetReportDataForAllUsers();
    }
}

public LoginUser() {
    IContext context = contextFactory.CreateInstance(defaultConnectionString);
    // Build the rest of the dependency graph, etc.
}
Jeff Sternal
A: 

We ended up just creating a concrete context and injecting that, then changing creating a wrapper class that changed the context's connection string. Seemed to work fine.

Jess