Fail-closed tenant isolation for your existing ASP.NET Core and EF Core apps
One call on the DbContext you already have isolates its data, with no base class, and Tenantry reads tenants from where your application keeps them. With no tenant, queries return nothing, and a forged tenant key changes no rows.
builder.Services.AddTenantry<Guid>(tenant => tenant
.ResolveFromHeader("X-Tenant-Id") // names the tenant
.ValidateTenantAccessByClaim("tenant_id") // the user may use it
.UseInMemoryStore(tenants)); // lists the tenants
builder.Services.AddDbContext<AppDbContext>(options => options
.UseSqlServer(connectionString)
.UseTenantry()); // isolates the data
// Tenant-owned: stamped on insert, filtered in every query.
public class Order : ITenantEntity<Guid>
{
public int Id { get; set; }
public Guid TenantId { get; private set; }
}Your DbContext stays yours
One call, options.UseTenantry(), isolates any DbContext: no base class, no interface, no SaveChanges override. Pooled contexts too, with a database per tenant.
Fails closed
With no tenant, queries match nothing. Writes to another tenant’s rows are rejected before saving, and the stored tenant is part of every UPDATE and DELETE, so a forged key changes nothing.
Your keys, your registry
A Guid, int, string or any parsable tenant key. Resolve it from a header, subdomain, host name, route value, claim or a resolver of your own, and find a tenant by its slug or custom domain.
HTTP and beyond
ASP.NET Core middleware and access validation, and the same isolation in workers, console and desktop apps. .NET 10, and .NET 8 and 9 until 10 November 2027; Native AOT for every Core package except Tenantry.EfCore.
Open-source Core. Pro for running tenants.
Core is free and isolates tenant data, in a shared database or a database per tenant. Pro adds the operations around it: with a shared database, the tenant in Hangfire, Quartz.NET, MassTransit and Rebus work, audit logging, and offboarding that deletes a tenant’s rows; with a database or schema per tenant, also provisioning, migrations and health checks.
Tenantry Core
Tenant resolution and EF Core isolation for an existing application.
- Tenant resolution, your tenant store and access validation
- Suspended tenants refused in requests and in RunInScopeAsync (ValidateTenantActivity)
- Worker scopes for background work as a tenant
- Options per tenant, authentication settings included (Tenantry.Options)
- HybridCache and IDistributedCache entries (Tenantry.Caching) and cached responses kept per tenant
- ITenantInvalidator, which clears what Tenantry keeps for a tenant when the tenant changes
- The current tenant sent with HttpClient and gRPC calls, and read back only from callers you trust (Tenantry.Http, Tenantry.AspNetCore)
- Log event ids to alert on, a tenant.id tag on traces, and request metrics per tenant
- A model check that refuses mappings it cannot isolate, and a list of unisolated entity types for your tests
Tenantry Pro
The tenant in your jobs and messages, audit logging, and tenant onboarding, offboarding and migrations, with a shared database or a database or schema per tenant.
- Hangfire, Quartz.NET, MassTransit and Rebus work run as the tenant it was created for, and refused for a suspended or deleted tenant
- Recurring jobs and background services that run once for each tenant
- Audit logging of each tenant’s inserts, updates and deletes
- Onboarding and offboarding in one call, including deleting a tenant’s rows from a shared database
- Tenant databases created, migrated and seeded, on SQL Server, PostgreSQL and MySQL
- Migrations across every tenant database or schema, as a deployment step
- A schema per tenant (SQL Server, PostgreSQL), and mixed mode
- Tenant health checks and connection-string caching
- Private NuGet package feed
- Email support, with a reply within 48 hours
Bring your own tenant registry
Tenantry reads tenants from where you already keep them: a table, configuration or another service. A store finds a tenant by id and lists them all. Creating and changing tenants stays in your application.
Tenant storespublic sealed class TenantStore(AppDbContext db) : ITenantStore<string>
{
public async ValueTask<ITenantDescriptor<string>?> GetTenantAsync(
string tenantId, CancellationToken ct = default) =>
await db.Tenants.AsNoTracking()
.FirstOrDefaultAsync(t => t.TenantId == tenantId, ct);
public async ValueTask<IReadOnlyList<ITenantDescriptor<string>>> GetAllTenantsAsync(
CancellationToken ct = default) =>
await db.Tenants.AsNoTracking()
.ToListAsync<ITenantDescriptor<string>>(ct);
}
builder.Services.AddTenantry<string>(tenant => tenant
.ResolveFromSubdomain(o => o.BaseDomains.Add("example.com"))
.UseStore<TenantStore>()
.CacheTenants());How Tenantry compares
Tenantry beside your own query filters, Finbuckle.MultiTenant and ABP. The full comparison also lists what Tenantry does not do.
| Your own query filters | Finbuckle.MultiTenant | ABP | Tenantry | |
|---|---|---|---|---|
| What your DbContext needs | A filter on each entity, and a SaveChanges override | Derive from MultiTenantDbContext, or implement IMultiTenantDbContext, configure entities in OnModelCreating and call EnforceMultiTenant() in SaveChanges | Derive from AbpDbContext and register it with AddAbpDbContext, in an ABP application | One call, options.UseTenantry() |
| Saving an entity with another tenant’s TenantId | Whatever your SaveChanges override checks | Throws by default; TenantMismatchMode can ignore or overwrite it instead | Saved with that TenantId. ABP sets TenantId from the current tenant when the entity object is created, and does not check it on save; its docs say changing it moves the entity to that tenant, and advise setting it only in the entity’s constructor | Throws, and the stored TenantId is in the WHERE clause of every UPDATE and DELETE that SaveChanges sends, so a forged key matches no row |
| Create and migrate tenant databases | You write it | No | ABP provides a base class for a handler you write that migrates a tenant’s database when the tenant is created; managing tenants’ connection strings from a UI needs the commercial SaaS module | Yes, in Pro |
| Per-tenant options and authentication | You write it | Yes, both | Per-tenant settings and features | Yes, both |
| Licence | Yours | Apache-2.0. Its README says that from 10 November, use of its official releases in revenue-generating work falls under an Open Source Maintenance Fee. | LGPL-3.0; the SaaS module needs a commercial ABP licence (Team or higher) | Core Apache-2.0; Pro a subscription |
Sources for Finbuckle.MultiTenant: EF Core, Stores, Strategies, Options, Authentication, TenantInfo, README, Issue #375, NuGet.
Sources for ABP: Multi-tenancy, EF Core, SaaS module, Tenant Management module, Settings, Features.
Pricing
Core is free. Pro is one subscription for your whole company, billed monthly or yearly. Both are in beta until 1.0.