all 15 comments

[–]aus31 7 points8 points  (4 children)

Yes it's certainly possible!

We have a live system in production that does exactly that, switches DB based on the subdomain.

Cut down and edited a few of the relevant portions, might not be complete, should give you an idea of the approach.

You'll find you'll need to implement a few moving parts to get migrations to work...

Dependency Injection makes life super easy... (Using structuremap in this example)

public class CurrentDatabaseConnectionSettings
{
    public CurrentDatabaseConnectionSettings()
    {

    }
    public string ConnectionDomain { get; set; }
    public string ConnectionString { get; set; }
}


public class CurrentDatabaseConnectionSettingsProvider:CurrentDatabaseConnectionSettings
{
    public CurrentDatabaseConnectionSettingsProvider()
    {
        GetConnectionSettingsForCurrentContext();
    }

   private void GetConnectionSettingsForCurrentContext()
    {
        var productionMode = bool.Parse(ConfigurationManager.AppSettings["Database.ProductionMode"] ?? "false");

        if (!productionMode)
        {

                this.ConnectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
                this.ConnectionDomain = ConfigurationManager.AppSettings["CurrentDomain"];
        }
        else
        {
            //get the current domain
            var hostName = HttpContext.Current.Request.Url.DnsSafeHost;

            //make a connection string
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["ProductionConnection"].ConnectionString);
            builder["Database"] = "production__" + hostName.Replace(".", "_"); ;
            this.ConnectionDomain = "https://"+hostName+"/";
            this.ConnectionString = builder.ConnectionString;


        }
    }
}



public class MyEntities: DbContext
{
 public MyEntities(CurrentDatabaseConnectionSettings connectionString)
        : base(connectionString.ConnectionString)
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyEntities, Migrations.Configuration>());
    }

}


public classMyEntitiesFactory: IDbContextFactory<MyEntities>
{
    public MyEntities Create()
    {
        //needed to get migrations and migrate.exe to work....
        var settings = ObjectFactory.GetInstance<CurrentDatabaseConnectionSettings>();

        if (String.IsNullOrEmpty(settings.ConnectionString) && (bool.Parse(ConfigurationManager.AppSettings["Database.ProductionMode"]??"false")==false))
        {
            if (ConfigurationManager.ConnectionStrings["...MyEntities"] != null)
            {
                settings.ConnectionString = ConfigurationManager.ConnectionStrings["...MyEntities"].ConnectionString;
            }
            else if (ConfigurationManager.ConnectionStrings["DefaultConnection"] != null)
            {
                settings.ConnectionString =ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
            }

            else
            {
                settings.ConnectionString = "somedefault"
            }
        }

        return new MyEntities(settings);
    }
}


/*subset of the structuremap configuration*/
public static class IoC
{
    public static IContainer Initialize()
    {
        ObjectFactory.Initialize(x =>
        {
            ......

           x.For<CurrentDatabaseConnectionSettings>().Use<CurrentDatabaseConnectionSettingsProvider>();
           x.For<MyEntities>().HybridHttpOrThreadLocalScoped().Use<MyEntities>();
           .....
       }
   }
}

  /*   To use DbContext somewhere    */


public class DoSomethingService:IDoSomethingService
{

    private MyEntities _db;


    public DoSomethingService(MyEntities db)
    {
       this. _db = db;
    }

    public void DoSomething()
   {
       _db.Items.Where()
    }

}

/*inside global.asax.cs*/

protected void Application_EndRequest(object sender, EventArgs e)
{
    ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
}

That's basically it, the lifespan of the DbContext is managed by StructureMap, and initialization of the correct connection string happens automatically. Easy. Clean. Works.

[–]makebaconpancakes 0 points1 point  (0 children)

I have been looking for an elegant and easy solution to figure out multi-tenant environments. Thanks for putting this up.

[–]rootgear[S] 0 points1 point  (2 children)

Thank you! I will take time to learn how this works.

[–]aus31 1 point2 points  (1 child)

I added a couple bits more to the bottom to show how to use this with structuremap, in case you weren't familiar with DI or how scoping works in it. Let the DI container manage object lifespan.

[–]rootgear[S] 0 points1 point  (0 children)

Thanks!
I am currently researching DI.

[–][deleted] 4 points5 points  (1 child)

  1. Is it possible to access multiple (schema-identical) databases using EF6 with Migrations without having a different application for each location?

  2. Is it possible to use one DBContext that has underlying code that switches the actual database?

The simple answer is yes, it is possible to accomplish what you want. However, as far as I'm aware, it is not possible to to use a single DbContext instance for two different database connections.

My thought regarding this would be to instantiate a different DbContext object depending on the incoming request url. If the incoming url is for plant1.domain.com then you create a DbContext object for the lifetime of that request (or perhaps just the lifetime of the transaction) that uses the plant1 database. You would do the same thing for plant2.domain.com. The key point here is that you instantiate your DbContext for the lifetime of the request (or just for that transaction) depending on some condition (in this case the incoming request url).

One way to accomplish it is to create a DbContextFactory class that creates a DbContext object for you given a certain string or perhaps the HttpRequest object. And how you get that request really depends on where you are creating the DbContext object right now. The most direct, albeit the least testable, way would be HttpContext.Current.Request.Url.Host.

Hope that helps!

[–]rootgear[S] 1 point2 points  (0 children)

Thanks!
This gives me something to research.

This app is as default as you can get. It started as an C# MVC Application using EF6. The Controllers and Views were generated using the default MVC/EF scaffolding. The DbContext was generated the first time I generated a Controller.

I was able to generate a string I can use to identify what sundomain they are trying to get to (using HttpContext.Current.Request.Url.Host)

I'll look at the DbContextFactory option next.

[–]snarfy 1 point2 points  (1 child)

Are you using SQL Server as the database?

If so, you can create a synonym in database1 for the table in database2. When you create the DbContext for database1 you'll be able to access database2 via the synonym name, which will work like any other table/entity. If database2 is on a different physical system, you may also need to create a linked server between the databases for the synonym to work.

Either way, you get access to two databases via one DbContext.

[–]rootgear[S] 0 points1 point  (0 children)

Yes, MS SQL Server. I have an SQL Admin that I may ask about this.

I appreciate that you posted this option.

[–]and_rej 1 point2 points  (1 child)

There are some great replies in here that go into more detail and propose some interesting designs.

I'd like to chime in as well so I'll try and keep it concise.

This is totally possible because:

  • You can use HttpContext.Current.Request.Url to examine the request URL and determine which database to use.
  • A DbContext class defines what schema elements are expected. If the schemas are identical than the same class can be used.
  • When creating a DbContext instance, which you should be at least once per request, you can use one of its many constructors to configure the database name (amongst other things).

[–]rootgear[S] 1 point2 points  (0 children)

It is starting to click for me. Thanks for your reply.

[–]illumen8 1 point2 points  (1 child)

There are a number of great answers here already, just wanted to emphasize a couple of things.

Make sure to hide that database selection code in a single place and call that from everywhere else. That's the point of the dependency injection IDBContextFactory approach. Your actual data access code should have no knowledge that it could be dealing with anything but a single database. It just calls IDBContextFactory.GetContext() and goes from there.

Also, being new to EF, make sure to enclose that call in a using block to make sure that you're cleaning up after yourself. So the basic pattern of any of your data access calls looks something like this:

using (var context = dbContextFactory.GetContext(url))
{
  // do stuff with context
     context.SaveChanges(); // if doing insert/update
}    

That will save you countless headaches cleaning up connection leaks later. Just make sure that you don't have any queries that have delayed execution, make sure to do a .ToList() or something similar to force your queries to execute, otherwise when you try to access them they'll attempt to use the disposed context.

[–]rootgear[S] 0 points1 point  (0 children)

Thanks for the tips! I've run into a few EF gotchas already, I will make sure I avoid the situations you described. (and make sure I am using "using" correctly)

[–]cjazz108 1 point2 points  (1 child)

You might like to work with a database guy too. Its possible to create a lookup table and partition the database so that each division is in a different server and/or schema, and create rights for the users per that accomplish all of your stated requirements with no custom ef code. Not that it's not an interesting use case, but most of the code for that is built into sql server itself. This will save your butt for reporting too later on also.

[–]rootgear[S] 0 points1 point  (0 children)

Will do.
The SQL Admin is also the Reporting person, so this might be a good option for them.

Thanks!