The Multi-tenancy Problem
Suppose you have completed a project for a customer X, who was the first to contact you for his requirements.
The customer is satisfied, and demand has arisen from a few more customers of his same segment - and you have a chance of hitting a fortune!
Such a project can often be sold to multiple customers without altering even a single line of code - except the few cosmetic changes in logo, company name, etc., and the database.
You have some options here. First is to sell by installing the project on per machine basis at the premises of each customer. Experience shows that this often ends up as a foolish idea when it comes to serving maintenance. Providing new security updates becomes a problem - each customer has to be contacted and visited - which raises costs, and also affects customer satisfaction.
The second option is to host the project on an internet server, and provide the services through Web API, etc.,. This is an excellent idea, maintenance wise. You have a single codebase running from a server under your own control, and you can update the code 100 times a day - at your own convenience, and without the need of contacting each subscriber (tenant) individually. This strategy turns out to be very cost effective, and also leads to a higher customer satisfaction.
BUT WE HAVE A BIGGER PROBLEM TO SOLVE!
How safe is it for your business to put all data of all customers (and their customers) in a single database, running into lakhs and lakhs of records, and still growing each year? Would you, as a CEO, be able to sleep peacefully? I am sure NO! Anything can go wrong with a database. I have seen database files get corrupted for no obvious reasons.
Some will suggest about reliable database backups. That's fine theoretically, but it's difficult to bet on it.
My suggestion, as always, is to stay as defensive as possible - do not take a complex path, avoid it, if possible.
The correct strategy is to maintain a separate database for each tenant. This avoids data conflicts, data mixing, data corruptions due to badly handled locks and concurrencies. It also protects against a COMPLETE HALT of your website. It could also speed up the CRUD queries of each tenant.
The above is the problem of multi-tenancy, and these are the problems to be addressed.
- how to switch-in the correct connection string, so that EF Core commands reach the correct database?
- how to enable automatic migrations, without the need of any human intervention?
- how to enable automated backups, and automated database tuning on a scheduled basis?
The Official, but too complex, Solution
The Microsoft official solutions are given on this page: Multi-tenancy - EF Core
I feel that it is un-necessarily complicated by adherence to the SOLID principles. They have used interfaces like ITenantService for providing a general solution.
Another solution has been given by Michael McKenna on his blog post: Multi-tenancy in ASP.NET Core 8 - Tenant Resolution
The above approach also gets complicated.
Source Code on GitHub
The repository is uploaded on GitHub. Please follow this link: GitHub Repo - multi-tenancy-aspnetcore
Video Explanation of the Source Code
Please watch the following youtube video, completely till the end.
The video is available in English, French, German, Hindi, Indonesian, Italian, Japanese, Polish, Portuguese and Spanish dubbings.
My Solution - Simpler and Practical
The simpler you keep, the easier is it to run and maintain.
Connection String should be switched at the time a user authenticates into your web app. At that time Identity is available, and it is easier to extract the unique id associated with a user account. The unique-id can then be used to set the connection string specific to a user's database.
This is also the time to check for any pending migrations, and also to check for backup and cleanup schedules, and perform those tasks as well.
Following is an extract of the code I implemented once.
// see the GitHub link given above using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using MultiTenancy.Models; namespace MultiTenancy.Areas.Members.Pages; public class IndexModel(PatientContext _patientCtx, ILogger<IndexModel> _logger) : PageModel { public async Task OnGet() { await EnsureDatabaseMigrationAndExistense(); } public async Task EnsureDatabaseMigrationAndExistense() { string patientDatabaseFolder = Path.Combine( DoctorContext.DBASE_DIRECTORY, User.Identity!.Name!); if (Directory.Exists(patientDatabaseFolder)) { if (true == _patientCtx.Database.GetPendingMigrations().Any()) { await _patientCtx.Database.MigrateAsync(); _logger.LogInformation("Migrations applied to patients database!"); } } else { Directory.CreateDirectory(patientDatabaseFolder); await _patientCtx.Database.MigrateAsync(); await _patientCtx.Database.EnsureCreatedAsync(); _logger.LogInformation("Created patients database!"); } // get system time and check backup schedules, and perform them now! } }
Source Code on GitHub
The repository is uploaded on GitHub. Please follow this link: GitHub Repo - multi-tenancy-aspnetcore
This Blog Post/Article "The Problem of Multi-tenancy in ASP.NET Core and a Suggested Solution" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.