Docs
Tenantry Core

Tenantry documentation

Tenantry is a flexible, modern, and unopinionated multi-tenancy library for .NET. It isolates each tenant's data in a shared database using a TenantId column, wiring the isolation in through an EF Core interceptor and global query filters, or gives each tenant its own database through per-tenant connection strings (database per tenant) — without forcing a base class on your entities or taking over your request pipeline. Schema-per-tenant, provisioning and migrations across tenant databases are in Tenantry.Pro.

If you are new, start with Getting started and Core concepts.

Guides

  1. Getting started — install the packages and build a tenant-aware app end to end.
  2. Core concepts — the tenant key, ITenantDescriptor, ITenantContext vs. ITenantScope, and the AsyncLocal model that ties them together.
  3. Tenant stores — the in-memory store, writing a custom ITenantStore, and service lifetimes.
  4. ASP.NET Core integration — AddTenantry, the resolution middleware, pipeline ordering, and HTTP status codes.
  5. Tenant resolution — header, subdomain, route, claim, and query-string resolvers, resolver ordering, and custom resolvers.
  6. Access control — requiring tenants per-endpoint or globally, access validators, and claim-based validation.
  7. EF Core integration — query filters, the SaveChanges interceptor, the isolation policy, the optional base context, pooling, a database per tenant, migrations, and admin/cross-tenant queries.
  8. Non-HTTP hosts — AddTenantryCore for console apps, worker services, and background jobs.
  9. AOT & trimming — exactly what is supported, per package, and why EF Core differs.
  10. Compatibility — supported .NET and EF Core versions, databases, and dependency ranges.
  11. Troubleshooting — common pitfalls and how to diagnose them.
  12. API reference — every public type and member, generated from the XML documentation comments.

How the pieces fit together

Tenantry has three responsibilities, each configured in the AddTenantry/AddTenantryCore lambda:

ResponsibilityQuestion it answersConfigured with
ResolutionWho is the tenant for this request/operation?ResolveFromHeader(...), ResolveFromClaim(...), … (ASP.NET Core), or a manual BeginScope(...) (non-HTTP)
StorageWhich tenants exist, and what are their details?UseInMemoryStore(...), UseStore<T>()
IsolationHow is each tenant's data kept separate?AddEfCoreIsolation(...), plus UseConnectionStrings(...) for a database per tenant

The flow on an ASP.NET Core request:

HTTP request
   │
   ▼
UseTenantry()  ──►  resolver(s) extract a raw id  ──►  parse to TKey  ──►  ITenantStore looks it up
   │                                                                              │
   │                                          (optional) access validators run    │
   ▼                                                                              ▼
ITenantScope.BeginScope(tenant)  sets the AsyncLocal tenant for the rest of the request
   │
   ▼
Your endpoint + EF Core
   ├─ reads  ──► global query filter restricts rows to ITenantContext.CurrentTenantId
   └─ writes ──► SaveChanges interceptor stamps/validates TenantId

In a console or worker app there is no request, so you open the scope yourself with ITenantScopeFactory (or BeginScope); everything below that line behaves identically.

On this page