Hello,
When we try to create a database like this:
[UnitOfWork(false)]
public bool Create()
{
DatabaseUpgradeResult result;
try
{
EnsureDatabase.For.SqlDatabase(ConnectionString);//fails here with error below
var upgrader =
DeployChanges.To
.SqlDatabase(ConnectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.LogToConsole()
.Build();
result = upgrader.PerformUpgrade();
}
catch (Exception exception)
{
throw;
}
return result.Successful;
}
This throws the error: CREATE DATABASE statement not allowed within multi-statement transaction.
We see the post here [http://forum.aspnetboilerplate.com/viewtopic.php?f=5&t=4103&p=9404]) and the article here [http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work#DocUowNoTransaction]). We tried to follow these instructions by converting our our class library to a console application and setting Program.cs as the startup class:
namespace MyCompanyName.AbpZeroTemplate.LegacyDatabaseService
{
using Abp;
public class Program
{
public static void Main(string[] args)
{
using (var bootstrapper = AbpBootstrapper.Create<AbpZeroTemplateLegacyDatabaseServiceModule>())
{
bootstrapper.Initialize();
}
}
}
}
We then created AbpZeroTemplateLegacyDatabaseServiceModule.cs as a module to set the initializer to null like the migrator project :
namespace MyCompanyName.AbpZeroTemplate.LegacyDatabaseService
{
using System.Data.Entity;
using Abp.Modules;
using System.Reflection;
using EntityFramework;
[DependsOn(typeof(AbpZeroTemplateDataModule))]
public class AbpZeroTemplateLegacyDatabaseServiceModule: AbpModule
{
public override void PreInitialize()
{
Database.SetInitializer<AbpZeroTemplateTenantDbContext>(null);
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
}
We also tried to turn off transactions by using: [UnitOfWork(isTransactional: false)] but the error still occurs.
Is this the correct way to turn off transactions?
Or is there another possible solution for this error?
Thank you
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?
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());
}
}
}
Hello,
Have you found time to address the above post? Thanks.
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:
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.
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.
We are still approaching this issue with mocking out the repositories. Therefore, we will post back with a solution if this becomes a bigger challenge.
Thank you again for your help.
When we try to resolve ILocalizationManager in a test it fails. If we change our test class to inherit from AbpIntegratedTestBase then it runs, but the tests take many, many times longer to run. What is the best way to resolve ILocalizationManager in the test project without incurring the time penalty of the AppTestBase class?
We are using moq to work around the issue we had here 8888. The code we are trying to write looks like:
public class MigrationAppService_Tests
{
private readonly ILocalizationManager _localizationManager;
private readonly Mock<IRepository<Tenant>> _mockReposistory;
public MigrationAppService_Tests()
{
_mockReposistory = new Mock<IRepository<Tenant>>();
_mockReposistory.Setup(x => x.GetAsync(1))
.Returns(Task.FromResult((Tenant)new TenantBuilder()));
_localizationManager = IocManager.Instance.Resolve<ILocalizationManager>();
}
[Fact]
public async Task IfTenantIdParameterIsMissingSaveZipAsyncReturnsExpectedErrorMessage()
{
var expected = "Tenant id was not found.";
MigrationAppService sut = new MigrationAppServiceBuilder()
.WithLocalizationManager(_localizationManager)
.WithTenantRepository(_mockReposistory.Object);
var result = await sut.SaveZipAsync(-1, null);
result.ErrorMessage.ShouldBe(expected);
}
}
When run, we get this error:
Castle.MicroKernel.ComponentNotFoundException
No component for supporting the service Abp.Localization.ILocalizationManager was found
at Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, IDictionary arguments, IReleasePolicy policy)
at Castle.Windsor.WindsorContainer.Resolve[T]()
at MyCompanyName.AbpZeroTemplate.Tests.Migration.MigrationAppService_Tests..ctor() in C:\projects\port\src\Tests\MyCompanyName.AbpZeroTemplate.Tests\Migration\MigrationAppService_Tests.cs:line 36
When we try to mock around this issue with something like this:
[Fact]
public async Task IfTenantIdParameterIsMissingSaveZipAsyncReturnsExpectedErrorMessage()
{
const string Expected = "Tenant id was not found.";
var mockLocalizationSource = new Mock<ILocalizationSource>();
mockLocalizationSource.Setup(x => x.GetString(It.IsAny<string>()))
.Returns(Expected);
_mockLocalizationManager.Setup(x => x.GetSource(It.IsAny<string>()))
.Returns(mockLocalizationSource.Object);
MigrationAppService sut = new MigrationAppServiceBuilder()
.WithLocalizationManager(_mockLocalizationManager.Object)
.WithTenantRepository(_mockReposistory.Object);
var result = await sut.SaveZipAsync(-1, null);
_mockLocalizationManager.Verify(x => x.GetSource("Custom"), Times.Once);
mockLocalizationSource.Verify(x => x.GetString(MigrationError.TenantIdMissing.ToString()), Times.Once());
result.ErrorMessage.ShouldBe(Expected);
}
Then it seems like we are not writing good tests--we are just testing the mocks.
Thank you.
We have worked around this using mocking, but still wonder if we did something wrong. We need this for real integration tests and TDD. I will send you a PM on this board to ask for your email address. Thanks.