Base solution for your next web application
Open Closed

Separate existing ANZ tables into two databases #11633


User avatar
0
dschnitt created

Good day, ANZ admins,

We're using AspNetZero version 10.2.0 (2021-01-29). We're also using EntityFramework Code-First Migration.

We want to use some of the ANZ functionalities, but some functionalities we won't use. We also want to use two databases. One for the Business Logic Database and one for the Web Interface Database. We've already configured ANZ to use two databases by creating a second db context. The existing first db context is for the Business Logic Database. The second db context that we've created is for the Web Interface Database. There are 42 existing ANZ tables, but we only need 19 of them. We know that we can't delete 23 other tables because behind the scene they're all being used, and we may also use them in the future. So instead of deleting them, we want those 23 tables to be placed in the Web Interface Database. That's our requirement. I only managed to put 3 tables (app_binary_object, app_chat_message, app_friendship) that we don't need in the Web Interface Database because their entities are present in the FirstDbContext.cs. For the other 20 tables, I can't find their definition. I also saw their entities are inside the DLL package.

Here's the list of 19 ANZ tables that we need and are now placed in the Business Logic Database:

  1. abp_audit_log
  2. abp_edition
  3. abp_feature
  4. abp_language
  5. abp_language_text
  6. abp_setting
  7. abp_tenant
  8. abp_tenant_notification
  9. abp_user
  10. abp_user_account
  11. abp_user_claim
  12. abp_user_login
  13. abp_user_login_attempt
  14. abp_user_notification
  15. abp_user_token
  16. app_invoice
  17. app_subscription_payment
  18. app_subscription_payments_extension_datum
  19. app_user_delegation

Here's the list of 20 ANZ tables that we don't need and are currently in the Business Logic Database and want to be placed in the Web Interface Database:

  1. abp_dynamic_entity_property
  2. abp_dynamic_entity_property_value
  3. abp_dynamic_property
  4. abp_dynamic_property_value
  5. abp_entity_change
  6. abp_entity_change_set
  7. abp_entity_property_change
  8. abp_background_job
  9. abp_notification
  10. abp_notification_subscription
  11. abp_organization_unit
  12. abp_organization_unit_role
  13. abp_permission
  14. abp_role
  15. abp_role_claim
  16. abp_user_organization_unit
  17. abp_user_role
  18. abp_webhook_event
  19. abp_webhook_send_attempt
  20. abp_webhook_subscription

Here are the 3 ANZ tables that I managed to put in the Web Interface Database:

  1. app_binary_object
  2. app_chat_message
  3. app_friendship

Please help us with this requirement. Thank you in advance.


3 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    I'm not sure if this scenario is fully supported or not but those tables are defined in https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbContext.cs and some of them are defined in https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCommonDbContext.cs

    So, you can try removing base class from your DbContext and manually add entities to your DbContext.

  • User Avatar
    0
    dschnitt created

    Hi,

    I'm not sure if this scenario is fully supported or not but those tables are defined in https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbContext.cs and some of them are defined in https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCommonDbContext.cs

    So, you can try removing base class from your DbContext and manually add entities to your DbContext.

    Hi ismcagdas,

    Thank you for your immediate response. Your advice really helped. However, I can't use the below definition, it says 'TUser' could not be found. I know I should not use the 'TUser', but I'm not sure what to use instead. Same for the TRoles and TTenants

    public virtual DbSet<TUser> Users { get; set; }

    public virtual DbSet<TRole> Roles { get; set; }

    public virtual DbSet<TTenant> Tenants { get; set; }

    Please help us again. Thank you.

  • User Avatar
    0
    sedulen created

    Hi @dschnitt,

    While I'm on an older version of ANZ and not running 10.2, I had looked into doing something like this a year ago for the AbpAuditLogs table, as well as another table that I have defined.

    While I never ended up taking this to production, I built a proof-of-concept that used AmbientDataContext

    I had to do a couple of things:

    1. for the tables that I wanted to move, I had to create concrete repository classes for
    2. I had to define my own ConnectionStringResolver

    Here is my concrete repository class

    using Abp.Auditing;
    using Abp.EntityFrameworkCore;
    using Abp.Runtime;
    using Abp.Domain.Repositories;
    using Microsoft.Extensions.Logging;
    using Brian.EntityFrameworkCore.Repositories;
    using Brian.EntityFrameworkCore;
    
    namespace Brian.MultiTenancy.Auditing
    {
        public class AbpAuditLogsRepository : BrianRepositoryBase<AuditLog, long>, IRepository<AuditLog, long>
        {
            private readonly ILogger<AbpAuditLogsRepository> _logger;
            public AbpAuditLogsRepository(IDbContextProvider<BrianDbContext> dbContextProvider, IAmbientDataContext ambientDataContext, ILogger<AbpAuditLogsRepository> logger)
                : base(dbContextProvider)
            {
                _logger = logger;
    
                _logger.LogDebug("[AbpAuditLogsRepository] : setting AmbientDataContext 'DBCONTEXT' to 'AuditLog'");
                ambientDataContext.SetData("DBCONTEXT", "AuditLog");
            }
        }
    }
    
    

    here is my ConnectionStringResolver

    using System;
    using Abp.Configuration.Startup;
    using Abp.Domain.Uow;
    using Microsoft.Extensions.Configuration;
    using Brian.Configuration;
    using Abp.Reflection.Extensions;
    using Abp.Zero.EntityFrameworkCore;
    using Abp.MultiTenancy;
    using Abp.Runtime;
    using Microsoft.Extensions.Logging;
    
    namespace Brian.EntityFrameworkCore
    {
        public class BrianDbConnectionStringResolver : DbPerTenantConnectionStringResolver
        {
            private readonly IConfigurationRoot _appConfiguration;
            private readonly ILogger<BrianDbConnectionStringResolver> _logger;
            private readonly IAmbientDataContext _ambientDataContext;
    
            public BrianDbConnectionStringResolver(IAbpStartupConfiguration configuration,
                ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
                ITenantCache tenantCache,
                IAppConfigurationAccessor configurationAccessor,
                IAmbientDataContext ambientDataContext,
                ILogger<BrianDbConnectionStringResolver> logger)
                : base(configuration, currentUnitOfWorkProvider, tenantCache)
            {
                _ambientDataContext = ambientDataContext;
                _appConfiguration = configurationAccessor.Configuration;
                _logger = logger;
            }
    
            public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
            {
                var s = base.GetNameOrConnectionString(args);
                object dbContext = null;
                try
                {
                    dbContext = _ambientDataContext.GetData("DBCONTEXT");
                    if(dbContext != null && dbContext.GetType().Equals(typeof(string)))
                    {
                        var context = (string)dbContext;
                        _logger.LogDebug($"[BrianDbConnectionStringResolver.GetNameOrConnectionString] : Found 'DBCONTEXT' of '{context}'");
                        //how do we ensure that there _is_ a connectionString defined for the given Context
                        var connectionString = _appConfiguration.GetConnectionString(context);
                        if(!string.IsNullOrEmpty(connectionString))
                        {
                            _logger.LogDebug($"[BrianDbConnectionStringResolver.GetNameOrConnectionString] : Found connectionString defined for 'DBCONTEXT' of '{context}'");
                            s = connectionString;
                        }
                    }
                }
                catch (Exception ex)
                {
                    //can we log this
                    _logger.LogError(ex, "[BrianDbConnectionStringResolver.GetNameOrConnectionString] : An unexpected error has occurred.");
                }
                return s;
            }
        }
    }
    

    Then in my WebCoreModule, in the PreInitialize method:

        public override void PreInitialize()
        {
            Configuration.ReplaceService&lt;IConnectionStringResolver, BrianDbConnectionStringResolver&gt;();
            Configuration.ReplaceService&lt;IRepository&lt;AuditLog, long&gt;, AbpAuditLogsRepository>();
            ...
    

    Disclaimer: this code was written against a much older version of ABP & ANZ, and written in just a few hours as part of a rapid proof-of-concept just to see if this was even feasible. It's definitely not my cleanest work, and it was never code reviewed or tested for production readiness. Additionally, I never looked into how this would be handled in for data migrations within EntityFrameworkCore, so it's possible that doing this could cause issues running the .Migrator project or running your EF migrations either code-first or via SQL scripts.

    I don't know if ABP / ANZ still supports or recommends using DbPerTenantConnectionStringResolver or IAmbientDataContext.

    I hope this helps. Good luck! -Brian