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.
@ -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.
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
````csharp
using System;
using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
using Volo.Abp.MultiTenancy;
namespace MultiTenancyDemo.Products
namespace MtDemoApp
{
{
public class Product : AggregateRoot<Guid>, IMultiTenant //Implementing the interface
public class Product : AggregateRoot<Guid>, IMultiTenant //Implementing the interface
{
{
public Guid? TenantId { get; set; } //Defined by the IMultiTenant interface
public Guid? TenantId { get; set; } //Defined by the IMultiTenant interface
public string Name { get; set; }
public string Name { get; set; }
public float Price { 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
## 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:
* 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)
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)
// 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: