Update: see part two here
Multitenancy is currently a hot topic in web development. Azure, SharePoint, and many other frameworks are offering multitenant options, because it totally makes sense to make a better use of a server by hosting many different services.
When it comes to data, there are usually three different strategies:
- Separate databases: each tenant is stored in its own database; different connection strings must be provided, one for each tenant;
- Shared database, separate schemas: all tenant’s data live in the same database and even share table names, but in different schemas;
- Shared database, shared schema tenants share the same physical tables, but use a discriminator column for distinguishing between them.
Let’s explore how we can use each of these techniques in Entity Framework (Code First, of course – is there another?).
First, we need to have a way to obtain the current tenant, specifically, a tenant id or code. To simplify, let’s just assume a simple interface:
You are free to implement this in any way you want.
We’ll also have a data context:
We need to inject the different connection strings through the constructor:
This is a simple strategy that relies on having one connection string per tenant id, but others exist, of course.
Shared Database, Separate Schemas
Another option is to have each tenant in its own schema. For that, we need to leverage the OnModelCreating method for configuring the model:
Again, a simple example: each type gets mapped to an equally-named table and to a schema that is identical to the tenant id. Pay attention to this: SQL Server and other RDBMSs allows users to have a default schema, so in theory, if you use integrated security, you may leave out the explicit schema. However, Entity Framework will always include the schema with the database objects, so you have to explicitly configure it, as we have.
Shared Database, Shared Schema
The final option depends on a discriminator column that is not mapped, but contains a different value for each tenant. Again, we need to configure the model accordingly (warning: reflection ahead):
This code is required because we need to iterate through all mapped entities, and Entity Framework doesn’t expose everything as public APIs. Some parts could be cached for performance or slightly improved, but I leave that as an exercise to you. In the end, all queries to entities will take an additional restriction “WHERE Tenant = @tenant”, where @tenant will take a different value for each tenant and Tenant is a physical column that isn’t mapped, nor does it need to be.
We’ve seen several techniques for mapping multitenant Entity Framework contexts; ultimately, the one we choose will depend on a number of factors. It will mostly be about having more or less isolation of your data.