Hello,
We are trying to migrate a database using this code:
namespace MyCompanyName.AbpZeroTemplate.Web.Controllers.Api
{
using System;
using System.Web.Http;
using Abp.Domain.Repositories;
using Abp.MultiTenancy;
using AbpZeroTemplate.MultiTenancy;
public class MigrationController : ApiController
{
private readonly IAbpZeroDbMigrator _abpZeroDbMigrator;
private readonly IRepository<Tenant> _tenantRepository;
public MigrationController(IAbpZeroDbMigrator abpZeroDbMigrator,
IRepository<Tenant> tenantRepository)
{
_abpZeroDbMigrator = abpZeroDbMigrator;
_tenantRepository = tenantRepository;
}
[HttpPost]
public IHttpActionResult Schema(int id)
{
var tenant = _tenantRepository.Get(id);
if (tenant == null)
return NotFound();
try
{
_abpZeroDbMigrator.CreateOrMigrateForTenant(tenant);
}
catch (Exception exception)
{
return InternalServerError(exception);
}
return Ok(tenant.TenancyName);
}
}
}
The error we get is:
An error has occurred.","exceptionMessage":"Unable to update database to match the current model because there are pending changes and automatic migration is disabled. Either write the pending model changes to a code-based migration or enable automatic migration. Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true to enable automatic migration.","exceptionType":"System.Data.Entity.Migrations.Infrastructure.AutomaticMigrationsDisabledException","stackTrace":" at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)\r\n at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)\r\n at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)\r\n at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)\r\n at System.Data.Entity.MigrateDatabaseToLatestVersion`2.InitializeDatabase(TContext context)\r\n at Abp.Zero.EntityFramework.AbpZeroDbMigrator`2.CreateOrMigrate(AbpTenantBase tenant) in D:\\Halil\\GitHub\\module-zero\\src\\Abp.Zero.EntityFramework\\Zero\\EntityFramework\\AbpZeroDbMigrator.cs:line 72\r\n at MyCompanyName.AbpZeroTemplate.Web.Controllers.Api.MigrationController.Schema(Int32 id) in C:\\projects\\port\\src\\MyCompanyName.AbpZeroTemplate.Web\\Controllers\\Api\\MigrationController.cs:line 30
When we open the database, it appears to have added some tables, but not the correct tables.
If we open the project and run "add-migration" then we get a message that no migrations are found.
If we open the project and run "update-database -verbose -projectname MyCompanyName.AbpZeroTemplate.EntityFramework -configuration MyCompanyName.AbpZeroTemplate.AbpZeroTemplateTenant.Configuration" then it works.
We pulled down the latest abp as of the morning of 11/29.
Since this will work in the package manager console but not in the code, maybe our code is not correct. Is there some method we should use to specify which dbcontext or something like that? We can't figure out what is causing this error. Any insight or thoughts would be appreciated.
Thank you for your time.
6 Answer(s)
-
0
Hi,
If a tenant has a seperate database, you can use CreateOrMigrateForTenant but if you store tenant's data in host database then you should use CreateOrMigrateForHost.
You can check MultiTenantMigrateExecuter class in the Migrator tool.
Basically first run CreateOrMigrateForHost once. Then run CreateOrMigrateForTenant for each tenant who has ConnectionString set.
-
0
Dear @ismcagdas thank you for your reply.
We did consult the MultiTenantMigrateExecuter class in the Migrator project. Please allow me to better explain our use case.
Currently, we have few hundred members (aka tenants) that self-host their own Microsoft SQL Database. We will be gradually onboarding each member separately. Our goal would be to manually migrate our host database, as needed. However, we would like to independently migrate each tenant automatically through custom tooling.
The tooling has the following steps:
- Member uploads a compressed file
- Decompress file to temporary server
- SMO Restore uncompressed .bak to temporary server
- Run tenant EF migrations
- Manually run SQL to convert legacy settings for tenant to new AbpSettings table
- SMO Create new backup .bak file
- Compress .bak file to new zip file
- Transfer to permanent server via FTP
- Decompress file
- Restore .bak to permanent SQL server
We have written the steps for 1-3 and 6-10.
When I just add
try { _abpZeroDbMigrator.CreateOrMigrateForHost(); // Migrate host per suggestion _abpZeroDbMigrator.CreateOrMigrateForTenant(tenant); } catch (Exception exception) { //var friendlyException = new Exception("Something went wrong with the migration."); return InternalServerError(exception); }
I get the following exception returned because the host database is already at the latest migration.
{ "message": "An error has occurred.", "exceptionMessage": "There is already an object named 'AbpPermissions' in the database.", "exceptionType": "System.Data.SqlClient.SqlException", "stackTrace": " at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)\r\n at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)\r\n at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)\r\n at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)\r\n at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)\r\n at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()\r\n at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)\r\n at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.NonQuery(DbCommand command, DbCommandInterceptionContext interceptionContext)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(MigrationStatement migrationStatement, DbConnection connection, DbTransaction transaction, DbInterceptionContext interceptionContext)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteStatementsInternal(IEnumerable`1 migrationStatements, DbConnection connection, DbTransaction transaction, DbInterceptionContext interceptionContext)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteStatementsWithinTransaction(IEnumerable`1 migrationStatements, DbTransaction transaction, DbInterceptionContext interceptionContext)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteStatementsWithinNewTransaction(IEnumerable`1 migrationStatements, DbConnection connection, DbInterceptionContext interceptionContext)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteStatementsInternal(IEnumerable`1 migrationStatements, DbConnection connection, DbInterceptionContext interceptionContext)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteStatementsInternal(IEnumerable`1 migrationStatements, DbConnection connection)\r\n at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<>c__DisplayClass1.<Execute>b__0()\r\n at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements, DbTransaction existingTransaction)\r\n at System.Data.Entity.Migrations.DbMigrator.ExecuteOperations(String migrationId, VersionedModel targetModel, IEnumerable`1 operations, IEnumerable`1 systemOperations, Boolean downgrading, Boolean auto)\r\n at System.Data.Entity.Migrations.DbMigrator.ApplyMigration(DbMigration migration, DbMigration lastMigration)\r\n at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)\r\n at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)\r\n at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)\r\n at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)\r\n at System.Data.Entity.MigrateDatabaseToLatestVersion`2.InitializeDatabase(TContext context)\r\n at Abp.Zero.EntityFramework.AbpZeroDbMigrator`2.CreateOrMigrate(AbpTenantBase tenant) in D:\\Halil\\GitHub\\module-zero\\src\\Abp.Zero.EntityFramework\\Zero\\EntityFramework\\AbpZeroDbMigrator.cs:line 72\r\n at MyCompanyName.AbpZeroTemplate.Web.Controllers.Api.MigrationController.Schema(Int32 id) in C:\\projects\\port\\src\\MyCompanyName.AbpZeroTemplate.Web\\Controllers\\Api\\MigrationController.cs:line 35" }
When we run the host migration the package management console in Visual Studio
add-migration test -projectname MyCompanyName.AbpZeroTemplate.EntityFramework -configuration MyCompanyName.AbpZeroTemplate.AbpZeroTemplateHost.Configuration
The following test migration class is created
namespace MyCompanyName.AbpZeroTemplate.AbpZeroTemplateHost { using System; using System.Data.Entity.Migrations; public partial class test : DbMigration { public override void Up() { } public override void Down() { } } }
We hope this explains our scenario better for a clearer picture of what we are trying to accomplish.
We are not sure exactly why we would need to run the Host migration per your prior suggestion.
Thank you for the time and an outstanding product.
-
0
Hello,
Have you found time to address the above post? Thanks.
-
0
We split our Host (AbpZeroHostDbContext) and Tenant (AbpZeroTenantDbContext) into two database context classes. We noticed the original context is of type AbpZeroDbContext.
Does migrator support this scenario?
When we run the tool directly we get the errors we posted earlier.
Host Context:
namespace MyCompanyName.AbpZeroTemplate.EntityFramework { using Abp.EntityFramework; using Abp.Zero.EntityFramework; using Authorization.Roles; using Authorization.Users; using Chat; using Friendships; using MultiTenancy; using Storage; using System.Data.Common; using System.Data.Entity; [DefaultDbContext] public class AbpZeroTemplateDbContext : AbpZeroHostDbContext<Tenant, Role, User> { /* Define an IDbSet for each entity of the application */ public virtual IDbSet<BinaryObject> BinaryObjects { get; set; } public virtual IDbSet<Friendship> Friendships { get; set; } public virtual IDbSet<ChatMessage> ChatMessages { get; set; } /* Setting "Default" to base class helps us when working migration commands on Package Manager Console. * But it may cause problems when working Migrate.exe of EF. ABP works either way. * */ public AbpZeroTemplateDbContext() : base("Host") { } /* This constructor is used by ABP to pass connection string defined in AmsDataModule.PreInitialize. * Notice that, actually you will not directly create an instance of AmsDbContext since ABP automatically handles it. */ public AbpZeroTemplateDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } /* This constructor is used in tests to pass a fake/mock connection. */ public AbpZeroTemplateDbContext(DbConnection dbConnection) : base(dbConnection, true) { } } }
Tenant Context:
namespace MyCompanyName.AbpZeroTemplate.EntityFramework { using Abp.Zero.EntityFramework; using Authorization.Roles; using Authorization.Users; using Configuration; using Domain; using System.Data.Common; using System.Data.Entity; public class AbpZeroTemplateTenantDbContext : AbpZeroTenantDbContext<Role, User> { public virtual IDbSet<Department> Departments { get; set; } public AbpZeroTemplateTenantDbContext() : base("Tenant") { } public AbpZeroTemplateTenantDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } /* This constructor is used in tests to pass a fake/mock connection. */ public AbpZeroTemplateTenantDbContext(DbConnection dbConnection) : base(dbConnection, true) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Configurations.Add(new DepartmentConfiguration()); } } }
-
0
Replaced contents of AbpZeroDbMigrator with TenantDbContext version.
namespace MyCompanyName.AbpZeroTemplate.EntityFramework { using Abp.Dependency; using Abp.Domain.Uow; using Abp.MultiTenancy; using Abp.Zero.EntityFramework; public class AbpZeroDbMigrator : AbpZeroDbMigrator<AbpZeroTemplateTenantDbContext, AbpZeroTemplateTenant.Configuration> { public AbpZeroDbMigrator( IUnitOfWorkManager unitOfWorkManager, IDbPerTenantConnectionStringResolver connectionStringResolver, IIocResolver iocResolver) : base( unitOfWorkManager, connectionStringResolver, iocResolver) { } } }
Is there a way to override the IOC container registration to use our custom class AbpZeroTenantDbMigrator, instead of the replacing the contents of the AbpZeroDbMigrator?
-
0
Hi,
You can replace it liek this in your module's PreInitialize method.
Configuration.ReplaceService<IAbpZeroDbMigrator, AbpZeroTenantDbMigrator>();
You can also check below forum topic and github issue for seperate DbContext usages. <a class="postlink" href="https://github.com/aspnetzero/aspnet-zero/issues/247">https://github.com/aspnetzero/aspnet-zero/issues/247</a> #1316@38f4c851-f549-4837-9916-2b8a9d396f9a