EFCore.MultiTenant.Core
1.0.2
dotnet add package EFCore.MultiTenant.Core --version 1.0.2
NuGet\Install-Package EFCore.MultiTenant.Core -Version 1.0.2
<PackageReference Include="EFCore.MultiTenant.Core" Version="1.0.2" />
<PackageVersion Include="EFCore.MultiTenant.Core" Version="1.0.2" />
<PackageReference Include="EFCore.MultiTenant.Core" />
paket add EFCore.MultiTenant.Core --version 1.0.2
#r "nuget: EFCore.MultiTenant.Core, 1.0.2"
#:package EFCore.MultiTenant.Core@1.0.2
#addin nuget:?package=EFCore.MultiTenant.Core&version=1.0.2
#tool nuget:?package=EFCore.MultiTenant.Core&version=1.0.2
EFCore.MultiTenant
Production-grade schema-isolated multitenancy for Entity Framework Core inspired by django-tenants.
Overview
EFCore.MultiTenant provides a complete multitenancy framework for Entity Framework Core using schema-per-tenant isolation.
While EF Core supports schemas technically, it does not provide a production-ready multitenancy architecture comparable to what django-tenants offers for Django applications.
This library fills that gap by providing:
- Automatic tenant resolution
- Dynamic schema switching
- Tenant-aware migrations
- Shared and tenant DbContext separation
- Tenant provisioning
- Middleware integration
- Background job support
- Connection safety
- Tenant caching
- Cross-request isolation guarantees
Features
Core Features
- Schema-per-tenant architecture
- Automatic schema switching
- Per-tenant model caching
- Tenant-aware migrations
- Shared + isolated entity support
- Automatic schema creation
- Safe connection reuse
- Tenant lifecycle management
- Background service support
- Async tenant provisioning
ASP.NET Core Integration
- Middleware-based tenant resolution
- Header resolution
- Subdomain resolution
- Route/path resolution
- JWT claim resolution
- Composite resolver pipeline
Database Support
| Database | Support |
|---|---|
| PostgreSQL | ✅ Full Support |
| SQL Server | ✅ Full Support |
| MySQL | ⚠️ Limited |
| SQLite | ❌ Not Supported |
Why EFCore.MultiTenant?
EF Core exposes low-level schema APIs such as:
modelBuilder.HasDefaultSchema(schema);
entity.ToTable("Products", schema);
However, production multitenancy requires much more than schema mapping.
This library provides:
| Capability | EF Core | EFCore.MultiTenant |
|---|---|---|
| Dynamic schema switching | ❌ | ✅ |
| Tenant resolution | ❌ | ✅ |
| Schema-aware model caching | ❌ | ✅ |
| Tenant migrations | ❌ | ✅ |
| Provisioning APIs | ❌ | ✅ |
| Isolation middleware | ❌ | ✅ |
| Background tenant scopes | ❌ | ✅ |
| Shared/Tenant context separation | ❌ | ✅ |
Architecture
Architecture
EFCore.MultiTenant is designed around a simple principle:
Resolve the tenant once per scope, then transparently route all EF Core operations to the correct schema.
The library separates the application into two logical layers:
| Layer | Responsibility |
|---|---|
| Shared Layer | Global application data (public schema) |
| Tenant Layer | Isolated tenant data (tenant_schema) |
Request Lifecycle
Incoming Request
│
▼
Tenant Resolver
(Header / Subdomain / Path / Claim)
│
▼
Tenant Store
(Load tenant metadata)
│
▼
Tenant Context
(Current tenant stored per request scope)
│
▼
Tenant DbContext
(Applies tenant schema automatically)
│
▼
Database
(schema.table)
Design Goals
The architecture focuses on:
- Minimal boilerplate
- Transparent schema routing
- Strong tenant isolation
- High performance
- Database portability
- Production readiness
- Familiar EF Core patterns
Installation
Core Package
dotnet add package EFCore.MultiTenant.Core
ASP.NET Core Integration
dotnet add package EFCore.MultiTenant.AspNetCore
PostgreSQL Support
dotnet add package EFCore.MultiTenant.PostgreSQL
Quick Start
1. Create a Tenant Entity
public class AppTenant : TenantBase
{
public string? ContactEmail { get; set; }
public string Plan { get; set; } = "free";
}
2. Configure Shared DbContext
The shared context hosts global tables such as tenants, plans, and application configuration.
public class AppSharedDbContext : SharedDbContext
{
public AppSharedDbContext(
DbContextOptions<AppSharedDbContext> options,
MultiTenantOptions optionsAccessor)
: base(options, optionsAccessor)
{
}
public DbSet<AppTenant> Tenants => Set<AppTenant>();
protected override void ConfigureSharedModel(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AppTenant>()
.HasIndex(t => t.SchemaName)
.IsUnique();
}
}
3. Configure Tenant DbContext
All entities automatically resolve to the current tenant schema.
public class AppTenantDbContext : MultiTenantDbContext
{
public AppTenantDbContext(
DbContextOptions<AppTenantDbContext> options,
ITenantContext tenantContext)
: base(options, tenantContext)
{
}
public DbSet<Product> Products => Set<Product>();
public DbSet<Order> Orders => Set<Order>();
protected override void ConfigureTenantModel(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(x => x.Name)
.HasMaxLength(300);
}
}
4. Implement a Tenant Store
public class EfTenantStore : ITenantStore<AppTenant>
{
private readonly AppSharedDbContext _db;
public EfTenantStore(AppSharedDbContext db)
{
_db = db;
}
public async Task<AppTenant?> GetTenantAsync(
string identifier,
CancellationToken cancellationToken = default)
{
return await _db.Tenants
.FirstOrDefaultAsync(
x => x.SchemaName == identifier,
cancellationToken);
}
public async Task<IReadOnlyList<AppTenant>> GetAllTenantsAsync(
CancellationToken cancellationToken = default)
{
return await _db.Tenants
.Where(x => x.IsActive)
.ToListAsync(cancellationToken);
}
}
5. Register Services
builder.Services.AddDbContext<AppSharedDbContext>(options =>
{
options.UseNpgsql(connectionString);
});
builder.Services.AddDbContext<AppTenantDbContext>(options =>
{
options.UseNpgsql(connectionString);
});
builder.Services
.AddMultiTenancy<
AppTenant,
AppTenantDbContext,
AppSharedDbContext>(options =>
{
options.SharedSchema = "public";
options.AutoMigrateOnProvision = true;
options.Return404OnUnknownTenant = true;
})
.WithHeaderResolution("X-Tenant-ID")
.WithSubdomainResolution()
.WithTenantStore<EfTenantStore>()
.UsePostgres();
builder.Services.AddTenantCaching<AppTenant>();
6. Add Middleware
app.UseRouting();
app.UseAuthentication();
app.UseMultiTenancy<AppTenant>();
app.UseAuthorization();
app.MapControllers();
7. Use Tenant DbContext
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly AppTenantDbContext _db;
public ProductsController(AppTenantDbContext db)
{
_db = db;
}
[HttpGet]
public async Task<IActionResult> Get()
{
return Ok(await _db.Products.ToListAsync());
}
}
Queries automatically resolve to:
acme.products
globex.products
contoso.products
depending on the current tenant.
Tenant Provisioning
public class TenantProvisioningService
{
private readonly ITenantProvisioner<AppTenant> _provisioner;
private readonly AppSharedDbContext _db;
public TenantProvisioningService(
ITenantProvisioner<AppTenant> provisioner,
AppSharedDbContext db)
{
_provisioner = provisioner;
_db = db;
}
public async Task<ProvisionResult> CreateTenantAsync(
string name,
string schema)
{
var tenant = new AppTenant
{
Name = name,
SchemaName = schema
};
_db.Tenants.Add(tenant);
await _db.SaveChangesAsync();
return await _provisioner.ProvisionAsync(tenant);
}
}
Provisioning automatically:
- Creates the schema
- Applies tenant migrations
- Seeds default data
- Validates schema state
Migrations
The library supports separate migration pipelines.
Shared Schema Migrations
dotnet ef migrations add InitShared \
--context AppSharedDbContext \
--output-dir Migrations/Shared
Tenant Schema Migrations
dotnet ef migrations add InitTenant \
--context AppTenantDbContext \
--output-dir Migrations/Tenant
Apply Shared Migrations
dotnet ef database update \
--context AppSharedDbContext
Apply Tenant Migrations
await provisioner.MigrateAllAsync();
Tenant Resolution Strategies
| Strategy | Example |
|---|---|
| Subdomain | acme.myapp.com |
| Header | X-Tenant-ID: acme |
| Path Segment | /acme/api/products |
| JWT Claim | tenant_id |
| Composite | First successful match |
Background Jobs
Execute for a Single Tenant
await services.RunForTenantAsync(
tenant,
async scope =>
{
var db = scope.ServiceProvider
.GetRequiredService<AppTenantDbContext>();
await db.Products.ToListAsync();
});
Execute for All Tenants
await services.RunForAllTenantsAsync<AppTenant>(
async (scope, tenant) =>
{
var db = scope.ServiceProvider
.GetRequiredService<AppTenantDbContext>();
await db.Products.CountAsync();
},
maxParallelism: 4);
Package Structure
EFCore.MultiTenant/
│
├── src/
│ ├── EFCore.MultiTenant.Core/
│ ├── EFCore.MultiTenant.AspNetCore/
│ ├── EFCore.MultiTenant.PostgreSQL/
│ ├── EFCore.MultiTenant.SqlServer/
│ └── EFCore.MultiTenant.Abstractions/
│
├── samples/
│ ├── MinimalApiSample/
│ ├── MVCSample/
│ └── SaaSSample/
│
├── tests/
│ ├── IntegrationTests/
│ └── UnitTests/
│
└── docs/
Roadmap
v1.0
- Schema isolation
- Middleware integration
- Tenant-aware migrations
- Provisioning APIs
- Background job scopes
- PostgreSQL support
- SQL Server support
v1.1
- CLI tooling
- Distributed cache namespacing
- OpenTelemetry integration
- Hangfire integration
- Quartz integration
v2.0
- Row-level security mode
- Hybrid multitenancy mode
- Blazor admin dashboard
- Tenant plan enforcement
Performance
Designed for high-scale SaaS applications:
- Zero model rebuilds per request
- Cached tenant lookups
- Safe pooled connections
- Async provisioning pipeline
- Bounded parallel tenant migrations
Security
- Strong schema isolation
- No shared tenant tables
- Query-level tenant enforcement
- Middleware validation
- Tenant context immutability
- Injection-safe schema handling
Contributing
Contributions are welcome.
- Fork the repository
- Create a feature branch
- Submit a pull request
Please read the contributing guidelines before submitting changes.
License
MIT License
Copyright (c) 2026 EFCore.MultiTenant Contributors
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on EFCore.MultiTenant.Core:
| Package | Downloads |
|---|---|
|
EFCore.MultiTenant.AspNetCore
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.