Open Closed

Error when migrating #2033


0
carelearning created

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
    ismcagdas created
    Support Team

    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
    carelearning created

    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.&lt;&gt;c__DisplayClass1.&lt;Execute&gt;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
    carelearning created

    Hello,

    Have you found time to address the above post? Thanks.

  • 0
    carelearning created

    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
    carelearning created

    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
    ismcagdas created
    Support Team

    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