Finbuckle / Finbuckle.MultiTenant

Finbuckle.MultiTenant is an open-source multitenancy middleware library for .NET. It enables tenant resolution, per-tenant app behavior, and per-tenant data isolation.
https://www.finbuckle.com/multitenant
Apache License 2.0
1.32k stars 267 forks source link

Batch migration database #152

Closed stuyun closed 5 years ago

stuyun commented 5 years ago

efcore How do you migrate independent databases if you have many libraries Abp database migration is recommended https://github.com/aspnetboilerplate/aspnetboilerplate

AndrewTriesToCode commented 5 years ago

Hi @stuyun . Can you elaborate a little more on the question? Are you moving away from Abp? I don't use Abp myself and this library stays pretty close to standard ASP.NET Core and EF Core conventions.

Using Finbuckle.MultiTenant with existing database should be as simple as using EF Core with an existing database except that you alter the created context to inherit from MultiTenantDbContext rather than DbContext. You would then create a new migration and apply it so that the TenantID column is created in the database (if it doesn't already exist).

reverse engineering / scaffolding info migration info

stuyun commented 5 years ago

I know microsoft migration info but Feature update too many tenants too many databases can not batch migration problem abp There is a batch migration feature

https://aspnetboilerplate.com/Templates abp create demo
public class MultiTenantMigrateExecuter : ITransientDependency { private readonly Log _log; private readonly AbpZeroDbMigrator _migrator; private readonly IRepository _tenantRepository; private readonly IDbPerTenantConnectionStringResolver _connectionStringResolver;

    public MultiTenantMigrateExecuter(
        AbpZeroDbMigrator migrator,
        IRepository<Tenant> tenantRepository,
        Log log,
        IDbPerTenantConnectionStringResolver connectionStringResolver)
    {
        _log = log;

        _migrator = migrator;
        _tenantRepository = tenantRepository;
        _connectionStringResolver = connectionStringResolver;
    }

    public bool Run(bool skipConnVerification)
    {
        var hostConnStr = CensorConnectionString(_connectionStringResolver.GetNameOrConnectionString(new ConnectionStringResolveArgs(MultiTenancySides.Host)));
        if (hostConnStr.IsNullOrWhiteSpace())
        {
            _log.Write("Configuration file should contain a connection string named 'Default'");
            return false;
        }

        _log.Write("Host database: " + ConnectionStringHelper.GetConnectionString(hostConnStr));
        if (!skipConnVerification)
        {
            _log.Write("Continue to migration for this host database and all tenants..? (Y/N): ");
            var command = Console.ReadLine();
            if (!command.IsIn("Y", "y"))
            {
                _log.Write("Migration canceled.");
                return false;
            }
        }

        _log.Write("HOST database migration started...");

        try
        {
            _migrator.CreateOrMigrateForHost(SeedHelper.SeedHostDb);
        }
        catch (Exception ex)
        {
            _log.Write("An error occured during migration of host database:");
            _log.Write(ex.ToString());
            _log.Write("Canceled migrations.");
            return false;
        }

        _log.Write("HOST database migration completed.");
        _log.Write("--------------------------------------------------------");

        var migratedDatabases = new HashSet<string>();
        var tenants = _tenantRepository.GetAllList(t => t.ConnectionString != null && t.ConnectionString != "");
        for (var i = 0; i < tenants.Count; i++)
        {
            var tenant = tenants[i];
            _log.Write(string.Format("Tenant database migration started... ({0} / {1})", (i + 1), tenants.Count));
            _log.Write("Name              : " + tenant.Name);
            _log.Write("TenancyName       : " + tenant.TenancyName);
            _log.Write("Tenant Id         : " + tenant.Id);
            _log.Write("Connection string : " + SimpleStringCipher.Instance.Decrypt(tenant.ConnectionString));

            if (!migratedDatabases.Contains(tenant.ConnectionString))
            {
                try
                {
                    _migrator.CreateOrMigrateForTenant(tenant);
                }
                catch (Exception ex)
                {
                    _log.Write("An error occured during migration of tenant database:");
                    _log.Write(ex.ToString());
                    _log.Write("Skipped this tenant and will continue for others...");
                }

                migratedDatabases.Add(tenant.ConnectionString);
            }
            else
            {
                _log.Write("This database has already migrated before (you have more than one tenant in same database). Skipping it....");
            }

            _log.Write(string.Format("Tenant database migration completed. ({0} / {1})", (i + 1), tenants.Count));
            _log.Write("--------------------------------------------------------");
        }

        _log.Write("All databases have been migrated.");

        return true;
    }

    private static string CensorConnectionString(string connectionString)
    {
        var builder = new DbConnectionStringBuilder { ConnectionString = connectionString };
        var keysToMask = new[] { "password", "pwd", "user id", "uid" };

        foreach (var key in keysToMask)
        {
            if (builder.ContainsKey(key))
            {
                builder[key] = "*****";
            }
        }

        return builder.ToString();
    }
}

}

stuyun commented 5 years ago

var tenants = _tenantRepository.GetAllList(t => t.ConnectionString != null && t.ConnectionString != ""); for (var i = 0; i < tenants.Count; i++) { var tenant = tenants[i]; _log.Write(string.Format("Tenant database migration started... ({0} / {1})", (i + 1), tenants.Count)); _log.Write("Name : " + tenant.Name); _log.Write("TenancyName : " + tenant.TenancyName); _log.Write("Tenant Id : " + tenant.Id); _log.Write("Connection string : " + SimpleStringCipher.Instance.Decrypt(tenant.ConnectionString));

        if (!migratedDatabases.Contains(tenant.ConnectionString))
        {
            try
            {
                _migrator.CreateOrMigrateForTenant(tenant);
            }
            catch (Exception ex)
            {
                _log.Write("An error occured during migration of tenant database:");
                _log.Write(ex.ToString());
                _log.Write("Skipped this tenant and will continue for others...");
            }

            migratedDatabases.Add(tenant.ConnectionString);
        }
        else
        {
            _log.Write("This database has already migrated before (you have more than one tenant in same database). Skipping it....");
        }

        _log.Write(string.Format("Tenant database migration completed. ({0} / {1})", (i + 1), tenants.Count));
        _log.Write("--------------------------------------------------------");
    }

    _log.Write("All databases have been migrated.");

    return true;
stuyun commented 5 years ago

It supports simultaneous migration of databases for all tenants

AndrewTriesToCode commented 5 years ago

@stuyun I apologize for not responding sooner. Unfortunately I don't have a good answer. Finbuckle.MultiTenant doesn't have batch migration functionality as it is more on the library side of things than a full framework like abp. Were you able to make any progress?

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.