7

I am exposing my repository operations through web api. Repository has been implemented with Entity framework and Unit Of Work Pattern. I have many instances of the same database. Each one represent the data of a different Client. Now the issue is how can I set the connection string dynamically through each webapi call? Should I get connection string parameter with each call ? Or I should host web Api per client ?

3
  • What is the intended lifetime of the DbContext object? I would expect it to be a new instance for every request, but don't want to assume. Commented Jun 23, 2015 at 16:33
  • Yes a new instance for each request as requests will come randomly from different clients Commented Jun 23, 2015 at 16:35
  • Did you find any solution for this ? I have also exactly same requirement so need your help. Commented Apr 11, 2017 at 14:33

2 Answers 2

5
+50

Based on the information provided, I would use the same controller and look up the connection string rather than rather than hosting separate Web API instances for each client. There would be more complexity in hosting multiple instances and given the only difference indicated is the connection string, I do not think the complexity would be justified.

The first thing we will need to do is determine which client is calling in order to get the appropriate connection string. This could be done with tokens, headers, request data, or routing. Routing is simplest and most generally accessible to clients, so I will demonstrate using it; however, carefully consider your requirements in deciding how you will make the determination.

[Route( "{clientId}" )]
public Foo Get( string clientId ) { /* ... */ }

Next we need to get the right DbContext for the client. We want to keep using DI but that is complicated in that we do not know until after the Controller is created what connection string is needed to construct the object. Therefore, we need to inject some form of factory rather than the object itself. In this case we will represent this as a Func<string, IUnitOfWork> with the understanding it takes the 'clientId' as a string and returns an appropriately instantiated IUnitOfWork. We could alternatively use a named interface for this.

[RoutePrefix("foo")]
public class FooController : ApiController
{  
    private Func<string, IUnitOfWork> unitOfWorkFactory;

    public FooController( Func<string, IUnitOfWork> unitOfWorkFactory )
    {
        this.unitOfWorkFactory = unitOfWorkFactory;
    }

    [Route( "{clientId}" )]
    public Foo Get( string clientId )
    {
        var unitOfWork = unitOfWorkFactory(clientId);
        // ...
    }
}

All that remains is configuring our dependency injection container to provide us that Func<string, IUnitOfWork>. This could vary significantly between implementation. The following is one possible way to do it in Autofac.

protected override void Load( ContainerBuilder builder )
{
    // It is expected `MyDbContext` has a constructor that takes the connection string as a parameter
    // This registration may need to be tweaked depending on what other constructors you have.
    builder.Register<MyDbContext>().ForType<DbContext>().InstancePerRequest();

    // It is expected `UnitOfWork`'s constructor takes a `DbContext` as a parameter
    builder.RegisterType<UnitOfWork>().ForType<IUnitOfWork>().InstancePerRequest();

    builder.Register<Func<string, Bar>>(
        c =>
            {
                var dbContextFactory = c.Resolve<Func<string, DbContext>>();
                var unitOfWorkFactory = c.Resolve<Func<DbContext, IUnitOfWork>>();

                return clientId =>
                    {
                        // You may have injected another type to help with this
                        var connectionString = GetConnectionStringForClient(clientId);
                        return unitOfWorkFactory(dbContextFactory(connectionString));
                    };
            });
 }

Autofac is used since comments indicates Autofac is currently being used, though similar results would be possible with other containers.

With that the controller should be able to be instantiated and the appropriate connection string will be used for each request.


Example registration based on linked project:

builder.Register<Func<string, IEmployeeService>>(
    c =>
        {
            var dbContextFactory = c.Resolve<Func<string, IMainContext>>();
            var unitOfWorkFactory = c.Resolve<Func<IMainContext, IUnitOfWork>>();
            var repositoryFactory = c.Resolve<Func<IMainContext, IEmployeeRepository>>();
            var serviceFactory = c.Resolve<Func<IUnitOfWork, IEmployeeService>>();

            return clientId =>
                {
                    // You may have injected another type to help with this
                    var connectionString = GetConnectionStringForClient(clientId);

                    IMainContext dbContext = dbContextFactory(connectionString);
                    IUnitOfWork unitOfWork = unitOfWorkFactory(dbContext);
                    IEmployeeRepository employeeRepository = repositoryFactory(dbContext);
                    unitOfWork.employeeRepositoty = employeeRepository;

                    return serviceFactory(unitOfWork);
                };
        });

If you find the registration grows too cumbersome because of needing to do a little wiring manually, you probably need to look at updating (or creating a new) container after you have determined the client so that you can rely more on the container.

Sign up to request clarification or add additional context in comments.

7 Comments

Many thanks , here is the link to a sample project I have created to demonstrate the architecture I am using dropbox.com/sh/f2leb6i8kmggef2/AAC8rGfkeD73e19HX9Vf_qZYa?dl=0
@InTheWorldOfCodingApplications It looks like the above would apply there. However, you are using MVC rather than WebAPI as the question indicates (and this answer shows) though you should still be able to use attribute routing if you decide on that route. It does not appear you have yet decided how you will determine the client.
You have one more class between the database and the controller (the Service) and that can be handled in registration in exactly the same way as shown above.
Actual project is a web Api project. This is just a sample to show how Repository and Unit of work are interacting with ServiceFacade layer
So we will have a service Factory instead of UnitOfWork Factory ? Context is being injected into both Repository and UnitOfWork how that can be handled with removing DI?
|
0

You can change the connectionstring per DbContext instance

Example:

public class AwesomeContext : DbContext
{
    public AwesomeContext (string connectionString)
        : base(connectionString)
    {
    }
    public DbSet<AwesomePeople> AwesomePeoples { get; set; }
}

And then use your DbContext like this:

   using(AwesomeContext context = new AwesomeContext("newConnectionString"))
   {
     return context.AwesomePeoples.ToList();
   }

Depending on how many ConnectionStrings there are you can make a DB table for the client / constring mapping or save it in the solution (array for example).

If you can't/don't want to change the constructor you can do it later as well

Add this to your DbContext override:

    public void SetConnectionString(string connectionString)
    {
        this.Database.Connection.ConnectionString = connectionString;
    }

And call the method before you do any DB operations:

   using(AwesomeContext context = new AwesomeContext())
   {

    context.SetConnectionString(ConfigurationManager.ConnectionStrings["newConnectionString"].ConnectionString)
    return context.AwesomePeoples.ToList();

   }

14 Comments

I am injecting my context through interface and autofac. It's like Public Class AwesomeContext: DbContext, IAwesomeContext
I updated the answer so you can change the connectionstring even if you are injecting the context
I am injecting IAwesomeContext not DbContext
I can't look in your code (you need to provide examples), but you can change my example to your implementation. All you need to do is add the SetConnectionString method to the interface and implement it in your specific context class. Then call the method on the injected variable
Code example is here stackoverflow.com/q/30404108/1711287 . I have eloborated it further
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.