diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md index 322fd9e085..d99d9392e3 100644 --- a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md +++ b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md @@ -24,21 +24,21 @@ In the shared database model, all the application data stored in a single physic This is the default behavior when you [create a new ABP application](https://abp.io/docs/latest/get-started), because it is simple to begin with and proper for must applications. -In this model, a single database table may contain data of multiple tenants. Each row in these tables have a `TenantId` field which is used to distinguish the tenant data and isolate a tenant's data from other tenant users. To make your entities multi-tenant aware, all you have to do is to implement the `IMultiTenant` interface provided by the ABP Framework: +In this model, a single database table may contain data of multiple tenants. Each row in these tables have a `TenantId` field which is used to distinguish the tenant data and isolate a tenant's data from other tenant users. To make your entities multi-tenant aware, all you have to do is to implement the `IMultiTenant` interface provided by the ABP Framework. + +Here, is an example `Product` entity that should support multi-tenancy: ````csharp using System; using Volo.Abp.Domain.Entities; using Volo.Abp.MultiTenancy; -namespace MultiTenancyDemo.Products +namespace MtDemoApp { public class Product : AggregateRoot, IMultiTenant //Implementing the interface { public Guid? TenantId { get; set; } //Defined by the IMultiTenant interface - public string Name { get; set; } - public float Price { get; set; } } } @@ -83,9 +83,160 @@ While you can manually convert your applications so they support separate databa ## Creating a new Application -Follow the *[Get Started tutorial](https://abp.io/docs/latest/get-started/layered-web-application)* to create a new ABP application. Remember to select the "Use separate tenant schema" option since I want to demonstrate it in this article. +Follow the *[Get Started tutorial](https://abp.io/docs/latest/get-started/layered-web-application)* to create a new ABP application. Remember to select the "*Use separate tenant schema*" option since I want to demonstrate it in this article. + +## Understanding the DbContext Structure + +When you open the solution in your IDE, you will see the following structure under the `.EntityFrameworkCore` project: + +![multi-tenancy-dbcontext-structure](multi-tenancy-dbcontext-structure.png) + +There are 3 DbContext-related classes here (MtDemoApp is your application name): + +* `MtDemoAppDbContext` class is used to map entities for the main (host + shared) database. +* `MtDemoAppTenantDbContext` class is used to map entities for tenant that have separate physical databases. +* `MtDemoAppDbContextBase` is an abstract base class for the classes explained above. In this way, you can configure common mapping logic here. + +Let's see these classes a bit closer... + +### The Main `DbContext` Class + +Here the main `DbContext` class: + +````csharp +public class MtDemoAppDbContext : MtDemoAppDbContextBase +{ + public MtDemoAppDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.SetMultiTenancySide(MultiTenancySides.Both); + + base.OnModelCreating(builder); + } +} +```` + +* It inherits from the `MtDemoAppDbContextBase` as I mentioned before. So, any configuration made in the base class is also valid here. +* `OnModelCreating` overrides the base method and sets the multi-tenancy side as `MultiTenancySides.Both`. `Both` means this database can store host data as well as tenant data. This is needed because we store data in this database for the tenants who don't have a separate database. + +### The Tenant `DbContext` class + +Here is the tenant-specific `DbContext` class: + +````csharp +public class MtDemoAppTenantDbContext : MtDemoAppDbContextBase +{ + public MtDemoAppTenantDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.SetMultiTenancySide(MultiTenancySides.Tenant); + + base.OnModelCreating(builder); + } +} +```` + +The only difference is that we used `MultiTenancySides.Tenant` as the multi-tenancy side here, since this `DbContext` will only have entities/tables for tenants that have separate databases. + +### The Base `DbContext` Class + +Here is the base `DbContext` class: + +````csharp +public abstract class MtDemoAppDbContextBase : AbpDbContext + where TDbContext : DbContext +{ + + public MtDemoAppDbContextBase(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureIdentityPro(); + builder.ConfigureOpenIddictPro(); + builder.ConfigureFeatureManagement(); + builder.ConfigureLanguageManagement(); + builder.ConfigureSaas(); + builder.ConfigureTextTemplateManagement(); + builder.ConfigureBlobStoring(); + builder.ConfigureGdpr(); + + /* Configure your own tables/entities inside here */ + + //builder.Entity(b => + //{ + // b.ToTable(MtDemoAppConsts.DbTablePrefix + "YourEntities", MtDemoAppConsts.DbSchema); + // b.ConfigureByConvention(); //auto configure for the base class props + // //... + //}); + + //if (builder.IsHostDatabase()) + //{ + // /* Tip: Configure mappings like that for the entities only + * available in the host side, + // * but should not be in the tenant databases. */ + //} + } +} +```` + +This `DbContext` class configures database mappings for all the [application modules](https://abp.io/docs/latest/modules) used by this application by calling their extension methods, like `builder.ConfigureBackgroundJobs()`. Each of these extension methods are defined as multi-tenancy aware and care about what you've set for the multi-tenancy side. + +### Where to Configure Your Entities? + +You can configure your entity mappings in the `OnModelCreating` method in any of the `DbContext` classes that was explained: + +* If you configure in the main `DbContext` class, these configuration will be valid only for the main database. So, don't configure tenant-related configuration here, otherwise, it won't be applied for the tenants who have separate databases. +* If you configure in the tenant `DbContext` class, it will be valid only for the tenants with separate databases. You rarely need to do that. You typically want to make same configuration in the base `DbContext` to support hybrid scenarios (some tenants use the main (shared) database and some tenants have separate databases). +* If you configure in the base `DbContext` class, it will be valid for the main database and tenant databases. You typically define tenant-related configuration here. That means, if you have a multi-tenant `Product` entity, then you should define its EF Core database mapping configuration here, so the Products table is created in the main database as well as in the tenant databases. -## Understanding DbContext Structure +The recommended approach is to configure all the mapping in the base class, but add controls like `builder.IsHostDatabase()` and `builder.IsTenantDatabase()` to conditionally configure the mappings: + +![builder-check-tenant-side](builder-check-tenant-side.png) + +## Adding Database Migrations + +In this section, I will show how to configure your entity mappings, generate database migrations and apply to the database. + +### Defining an Entity + +Let's define a `Product` entity in the `.Domain` layer of your application: + +````csharp +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace MtDemoApp +{ + public class Product : AggregateRoot, IMultiTenant + { + public Guid? TenantId { get; set; } + public string Name { get; set; } + public float Price { get; set; } + } +} +```` -TODO +### Add a New Database Migration +TODO \ No newline at end of file diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png new file mode 100644 index 0000000000..bfc89e9dda Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png new file mode 100644 index 0000000000..0bd76b7d59 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png differ