EntityFramework.Docs icon indicating copy to clipboard operation
EntityFramework.Docs copied to clipboard

Multi-tenant applications (model building and migrations)

Open rowanmiller opened this issue 10 years ago • 8 comments

See question in https://github.com/aspnet/EntityFramework/issues/4004 as an example of what we should cover

rowanmiller avatar Dec 17 '15 23:12 rowanmiller

See https://github.com/aspnet/EntityFramework/issues/4583

rowanmiller avatar Feb 22 '16 23:02 rowanmiller

Any update on this? I understand that multi-tenancy has not been accepted as within the scope of EF Core, but nonetheless people are constantly asking questions on here about this.

Specifically on a database-per-tenant approach: After looking through the ef tool I don't see why it wouldn't be possible for a user to make a "mass update" command to call Update() on each database given a context and a list of tenant connection strings because of the --connection option now in 5.0 (either it could literally call "database update --connection ..." for each, or somehow specify the connection string when instantiating through Activator). I would say the only issue is that migrations require the constructor and OnConfiguring() to be run, which is often where the tenant connection string is injected via some route data, cookie, or token. However, I think IDesignTimeDbContextFactory solves this issue by providing a static dummy database to create them from.

We appreciate you working with those of us who are implementing our own solutions, it may be best to explicitly state that multi-tenancy is not supported but is available via libraries or on help-sites.

Perustaja avatar Jan 12 '21 00:01 Perustaja

@Perustaja No update, but the issue is not closed, which means we still plan to have guidance at some point.

ajcvickers avatar Jan 12 '21 16:01 ajcvickers

I've read countless articles over the last month trying to find a way to achieve this before stumbling onto this post and wish I'd seen this first and saved the time! I assumed there was already a way to accomplish migrations with multiple connection strings. There seems to be various methods for using multi tenant databases which is was surprised me that I couldn't use migrations. Seeing as this is now over 5 years old and still no information, I'll look into alternate providers / methods.

Marren85 avatar Mar 21 '21 12:03 Marren85

I found a good and simple solution for this problem. (I like the title of https://github.com/dotnet/efcore/issues/4004 better but I see the discussion continues here)

The scenario: You want to use the tenant code / identifier as a schema in your DB

Steps:

  1. Extend your MigrationsSqlGenerator for your provider. In my example, I'll be using Postgresql
    public class SchemaAwareMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
    {
        private readonly string _schema;
        public SchemaAwareMigrationsSqlGenerator(
            MigrationsSqlGeneratorDependencies dependencies, 
            INpgsqlOptions npgsqlOptions
        ) : base(dependencies, npgsqlOptions) 
        {
             var dbContext = dependencies?.CurrentContext?.Context;
            _schema = getTenantCodeFromDbContext(dbContext);
        }

NB: The code in the constructor will be the same regardless of your DB provider. This is because the constructor will always include the depenencies parameter. NB: The implementation for getTenantCodeFromDbContext will depend on how you represent the tenant code in your dbcontext.

  1. Add the following override to the SchemaAwareMigrationsSqlGenerator class you just made.
        protected override void Generate(MigrationOperation operation, IModel model, MigrationCommandListBuilder builder)
        {
            var schemaProp = operation.GetType().GetProperty("Schema");
            if (schemaProp != null && schemaProp.CanWrite)
            {
                schemaProp.SetValue(operation, _schema);
            }
            
            // Updating 'NewSchema' is optional but you may likely need it
            schemaProp = operation.GetType().GetProperty("NewSchema");
            if (schemaProp != null && schemaProp.CanWrite)
            {
                schemaProp.SetValue(operation, _schema);
            }

            base.Generate(operation, model, builder);
        }
  1. In your dbcontextbuilder (somewhere in ConfigureServices or wherever you kept it, add the following:
var options = new DbContextOptionsBuilder<YourSchemaAwareDbContext>()
// ...
.ReplaceService<IMigrationsSqlGenerator, SchemaAwareMigrationsSqlGenerator>();

Addendum: You may also want to customize the dbcontext's IModelCacheKeyFactory to be schema-aware. See https://stackoverflow.com/a/41985226 for an example of how to do it.

smbadiwe avatar Nov 19 '21 19:11 smbadiwe

After a long week reading nearly everything I could find about tenant by schema in EF Core I found this post and it helped me go 5 steps further.

One thing to add frmo the last posts.

For the foreign keys the SchemaAwareMigrationsSqlGenerator needs also the schemaProp = operation.GetType().GetProperty("PrincipalSchema"); if (schemaProp != null && schemaProp.CanWrite) { schemaProp.SetValue(operation, tenantProvider.TenantSchemaName); }

  1. the insert data (e.g. for enum seeding) causes problems, the solution is to manuelly add the column types in the migration file

ReneKochITTitans avatar Jul 19 '24 17:07 ReneKochITTitans

Hello, do we have any updates on this?

arielmoraes avatar Apr 21 '25 21:04 arielmoraes

This issue is in the Backlog milestone. This means that it is not planned for the next release (EF Core 10.0). We will re-assess the backlog following the this release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to vote (👍) for this issue if it is important to you.

roji avatar Apr 22 '25 08:04 roji