Tenantry.EfCore
0.2.3-alpha
Prefix Reserved
dotnet add package Tenantry.EfCore --version 0.2.3-alpha
NuGet\Install-Package Tenantry.EfCore -Version 0.2.3-alpha
<PackageReference Include="Tenantry.EfCore" Version="0.2.3-alpha" />
<PackageVersion Include="Tenantry.EfCore" Version="0.2.3-alpha" />
<PackageReference Include="Tenantry.EfCore" />
paket add Tenantry.EfCore --version 0.2.3-alpha
#r "nuget: Tenantry.EfCore, 0.2.3-alpha"
#:package Tenantry.EfCore@0.2.3-alpha
#addin nuget:?package=Tenantry.EfCore&version=0.2.3-alpha&prerelease
#tool nuget:?package=Tenantry.EfCore&version=0.2.3-alpha&prerelease
Tenantry
A flexible, modern, and unopinionated multi-tenancy library for .NET.
Tenantry isolates each tenant's data in a shared database using row-level isolation: a
TenantId column on the entities you choose to make tenant-scoped. It does this without forcing
a base class on your entities, without a custom DbContext, and without taking over your request
pipeline. You pick the tenant key type, how tenants are resolved, and where they are stored — and
Tenantry wires the isolation in.
builder.Services.AddTenantry<Guid>(tenant =>
{
tenant.ResolveFromHeader("X-Tenant-Id"); // where the tenant comes from
tenant.UseInMemoryStore(tenants); // where tenants are defined
tenant.AddEfCoreIsolation(options => options.DetectSpoofedWrites = true); // how data is isolated
});
Why Tenantry?
- Unopinionated. Your tenant key can be a
Guid,int,string, or any type that isIEquatable<T>andIParsable<T>. Resolve tenants from a header, subdomain, route, claim, query string, or your own resolver. Store them in memory, a database, or anywhere behind an interface. - Interceptor-first isolation. Tenant stamping and cross-tenant write protection work on any
DbContextvia an EF CoreSaveChangesinterceptor — no base class required. An optionalMultiTenantDbContext<TKey>base class is provided for greenfield convenience. - Fails closed. When no tenant is resolved, query filters match nothing rather than leaking every
tenant's rows. Cross-tenant writes are rejected before anything is persisted, and a configurable
OnMissingTenantpolicy (warn, allow, or reject) governs writes that run without a tenant context. - HTTP and beyond.
AddTenantrycovers ASP.NET Core (resolution middleware, access validation, endpoint metadata).AddTenantryCorebrings the same isolation to console apps, worker services, and desktop UIs with no web stack. - Modern .NET. Targets .NET 8, 9, and 10. The core and ASP.NET Core packages are trim- and Native-AOT-compatible (see AOT & trimming).
Packages
Tenantry.EfCore and Tenantry.AspNetCore both depend on Tenantry.Core. Reference whichever
combination matches your host:
# ASP.NET Core app with EF Core isolation (most common)
dotnet add package Tenantry.AspNetCore
dotnet add package Tenantry.EfCore
# Console / worker / desktop app with EF Core isolation
dotnet add package Tenantry.Core
dotnet add package Tenantry.EfCore
Quick start (ASP.NET Core)
using Tenantry.AspNetCore.Extensions;
using Tenantry.Core;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTenantry<Guid>(tenant =>
{
// 1. How is the tenant identified on each request? (resolvers are tried in order)
tenant.ResolveFromHeader("X-Tenant-Id");
// 2. Which tenants exist? (swap for a DB/cache-backed store in production)
tenant.UseInMemoryStore(
[
new TenantDescriptor<Guid> { TenantId = Guid.Parse("…0001"), Name = "Acme" },
new TenantDescriptor<Guid> { TenantId = Guid.Parse("…0002"), Name = "Globex" },
]);
});
var app = builder.Build();
// Resolves the tenant and populates ITenantContext<Guid> for the rest of the request.
app.UseTenantry();
app.MapGet("/me", (ITenantContext<Guid> ctx) =>
ctx.HasTenant ? Results.Ok(ctx.CurrentTenant!.Name) : Results.NotFound())
.RequireTenant();
app.Run();
Add EF Core isolation by registering tenant.AddEfCoreIsolation() in the lambda above and calling
options.AddTenantInterceptors(sp) in your AddDbContext callback. See the
EF Core integration guide for the full picture.
Quick start (console / worker — no ASP.NET Core)
There is no request to resolve a tenant from, so you open and close the tenant scope yourself:
using Tenantry.Core;
using Tenantry.Core.Extensions;
builder.Services.AddTenantryCore<Guid>(tenant =>
{
tenant.AddEfCoreIsolation(options => options.DetectSpoofedWrites = true);
});
// …later, around a unit of work:
var scope = sp.GetRequiredService<ITenantScope<Guid>>();
using (scope.BeginScope(new TenantDescriptor<Guid> { TenantId = tenantId, Name = "Acme" }))
{
// EF Core reads are filtered to this tenant and writes are stamped with it.
await db.SaveChangesAsync();
}
See the runnable Tenantry.Samples.EfCoreConsole project
and the non-HTTP hosts guide.
AOT & trimming
Tenantry is built with the trim and AOT analyzers enabled and ships annotated for both. Support differs by package because EF Core's query-filter mechanism requires runtime code generation:
| Package | Trimming | Native AOT |
|---|---|---|
Tenantry.Core |
✅ Fully compatible | ✅ Fully compatible (IsAotCompatible) |
Tenantry.AspNetCore |
✅ Fully compatible | ✅ Fully compatible (IsAotCompatible) — see the Aot sample |
Tenantry.EfCore |
✅ Trim-compatible | ⚠️ Not AOT-compatible — query filters require dynamic code (see below) |
Tenantry.CoreandTenantry.AspNetCoreare markedIsAotCompatibleandIsTrimmableand carry no trim/AOT warnings. TheTenantry.Samples.Aotproject publishes withPublishAot=trueagainst a slim host and source-generated JSON.Tenantry.EfCoreisIsTrimmablebut not AOT-compatible. The read-side query filters (ApplyTenantFiltersand theMultiTenantDbContext<TKey>base class) build LINQ expression trees by reflecting over the EF Core model, so they are annotated[RequiresDynamicCode]and[RequiresUnreferencedCode]. This mirrors EF Core itself, which does not support Native AOT. The write-side interceptor does not generate code, but the integration as a whole should be treated as non-AOT.
Full details and guidance are in AOT & trimming.
Documentation
| Guide | What it covers |
|---|---|
| Getting started | Install, your first tenant-aware app, end to end |
| Core concepts | Tenant key, descriptor, context vs. scope, the AsyncLocal model |
| Tenant stores | In-memory and custom stores, service lifetimes |
| ASP.NET Core integration | Registration, middleware, pipeline ordering, status codes |
| Tenant resolution | Header, subdomain, route, claim, query-string, and custom resolvers |
| Access control | Requiring tenants, access validators, claim-based validation |
| EF Core integration | Query filters, the interceptor, isolation policy, migrations, admin queries |
| Non-HTTP hosts | AddTenantryCore in console apps, workers, and background jobs |
| AOT & trimming | What is supported, per package, and why |
| Troubleshooting | Common pitfalls and how to diagnose them |
Samples
| Sample | Demonstrates |
|---|---|
Quickstart |
Minimal ASP.NET Core setup, resolvers, access validators, endpoint metadata |
EfCoreWeb |
Realistic EF Core app: migrations, DB-backed store, mixed tenanted/global entities, admin queries |
EfCoreConsole |
EF Core isolation with no ASP.NET Core, using AddTenantryCore and manual scopes |
Aot |
Native-AOT-published ASP.NET Core app |
License
Licensed under the Apache License 2.0.
| 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 is compatible. 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 is compatible. 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. |
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.0 && < 11.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0 && < 11.0.0)
- Tenantry.Core (>= 0.2.3-alpha)
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.10 && < 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2 && < 9.0.0)
- Tenantry.Core (>= 0.2.3-alpha)
-
net9.0
- Microsoft.EntityFrameworkCore (>= 9.0.0 && < 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0 && < 10.0.0)
- Tenantry.Core (>= 0.2.3-alpha)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.3-alpha | 98 | 6/27/2026 |
| 0.1.0-alpha | 83 | 6/21/2026 |