Browse Source

Expand article with DbContext structure and images

Enhanced the multi-tenancy article by adding detailed explanations of the DbContext structure for separate tenant schemas, including code samples and best practices. Added two illustrative images to clarify the DbContext setup and conditional configuration for tenant/host databases.
pull/23161/merge
Halil İbrahim Kalkan 1 year ago
parent
commit
18a26ef74e
  1. 165
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md
  2. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png
  3. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png

165
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<Guid>, 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<MtDemoAppDbContext>
{
public MtDemoAppDbContext(DbContextOptions<MtDemoAppDbContext> 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<MtDemoAppTenantDbContext>
{
public MtDemoAppTenantDbContext(DbContextOptions<MtDemoAppTenantDbContext> 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<TDbContext> : AbpDbContext<TDbContext>
where TDbContext : DbContext
{
public MtDemoAppDbContextBase(DbContextOptions<TDbContext> 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<YourEntity>(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<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public string Name { get; set; }
public float Price { get; set; }
}
}
````
TODO
### Add a New Database Migration
TODO

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Loading…
Cancel
Save