Base solution for your next web application

Activities of "carelearning"

Answer

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());
        }
    }
}
Answer

Hello,

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

Answer

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.

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.

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.

Hello,

Thank you for your response. We do not use the ChatMessage and Friendship tables. They are not referenced anywhere in our code. We do not get this error until we include the javascript files in our layout. When we drop the tables and try to add a migration, it does not find find these tables to add. To use signalR in abpZero template, are these tables required? Is there some sort of dependenct injection requirement? We can manually create a migration instead of adding the table manually through SQL. Is that what you mean?

For more background, our Tenent DB Context File looks like this:

namespace MyCompanyName.AbpZeroTemplate.EntityFramework
{
//    using statments;

    public class AbpZeroTemplateTenantDbContext : AbpZeroTenantDbContext<Role, User>
    {
        public virtual IDbSet<Department> Departments { get; set; }
        public virtual IDbSet<Enrollment> Enrollments { 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());
            modelBuilder.Configurations.Add(new EnrollmentConfiguration());
        }
    }
}

and the error we are getting is attached.

Thank you again for your help.

After scouring the Git repository's comment messages, I determined we had some out-of-date NuGet packages. I eventually updated all the packages except for EntityFramework.DynamicFilters. Next, I added the missing Changed_Code_MaxLength_Of_OrganizationUnit migration. Finally, I built and ran and the exception went away. I push the changes the broken branch of the example repo.

I replicated the changes in our real project and after fixing several changes like; Clock.SupportsMultipleTimezone from a method to a property, renaming IdentityFrameworkClaimsAbpSession to ClaimsAbpSession and updating the CustomDtoMapper's CreateMappingsInternal method. I built and ran and the exception is gone.

-Chris

@ismcagdas

Sorry for the tardy reply and the long explanatory post to follow.

To illustrate the problem I created a File->New Project->ASP.NET Web Application (.NET Framework)

  • Authentication: No Authentication
  • MVC: include
  • Web API: include

Working Branch git clone <a class="postlink" href="http://git.carelearning.com/dxabp.git">http://git.carelearning.com/dxabp.git</a> example -b working

When I build and run the Example project from the working branch; I select click "Upload", a DevExpress FileUploader widget, and navigate to an included sample file located at the [cloned-project-dir]\assets\departments.csv. This in return executes the controller's Upload method shown below.

[HttpPost]
        public ActionResult Upload()
        {
            var file = Request.Files[0];
            if (file == null ||
                file.ContentLength == 0)
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

            var content = _departmentAppService.Upload(file.InputStream);
            return Content(content);
        }

Here is the DepartmentAppService Upload method which uses Gembox.Spreadsheet and Json.Net.

public string Upload(Stream stream)
        {
            SpreadsheetInfo.SetLicense("FREE-LIMITED-KEY");

            var workbook = ExcelFile.Load(stream, LoadOptions.CsvDefault);
            var worksheet = workbook.Worksheets.ActiveWorksheet;
           
           // TODO optimize
            var dataTable = worksheet.CreateDataTable(new CreateDataTableOptions()
            {
                ColumnHeaders = true
            });

            return JsonConvert.SerializeObject(dataTable, new DataSetConverter());
        }

When I breakpoint and debug I see the expected Json content string.

Broken Branch git clone <a class="postlink" href="http://git.carelearning.com/dxabp.git">http://git.carelearning.com/dxabp.git</a> example -b broken

To prevent licensing issues when sharing this example code; yesterday I created a brand new Asp.Net Boilerplate project. I created a Custom controller with single Index view. I then transplanted the code from the Working project, slightly altering the code to use the idomatic Abp code and the suggested inheritance hierarchy. I also had to disable the CSRF/XSS protection according to this post.

After building and setting Web as the Startup Project I run update-database -verbose -projectname EntityFramework to bootstrap the required database. The project uses (localdb) for the data source in the connection string.

When running and selecting the same test file at[cloned-project-dir]\assets\departments.csv. The Upload method now throws the aforementioned ReadTimeout not supported exception (see attached screenshot, ReadTimeout_Exception.png).

When searching the forums another user experienced this issue with Kendo Upload Widget. We tried the presented solution of adding MethodInvocationValidator.IgnoredTypesForRecursiveValidation.AddIfNotContains(typeof(HttpPostedFileWrapper)); in the Global.aspx code-behind in our real AbpZero project and it did not work. I also tried adding this code to this example project, however it would not compile. I left it in but commented out.

Thank you for your time and an outstanding library.

-Chris

Thank you for your reply. Here are the steps we used to resolve the issue we had:

  • We changed AbpDbContext to AbpZeroTenantDbContext<Role, User>
  • Added DefaultDbContext attribute to AbpZeroHostDbContext<Tenant, Role, User>
  • Seeded AbpLanguages with 1 record for English
  • Stopped seeding AbpUsers in the host context and instead created AbpUser for each TenantDbContext
  • MSDTC service was configured on DB Server and local dev machine and firewall rules had to be set

Thank you again for your help and effort.

Thank you. The project is intentionally only Asp.Net Boilerplate so as not to make anything public. I am sorry for not specifying that earlier. Thanks again for all your hard work.

Showing 51 to 60 of 65 entries