MultiTenancy.EntityFrameworkCore
1.6.1
dotnet add package MultiTenancy.EntityFrameworkCore --version 1.6.1
NuGet\Install-Package MultiTenancy.EntityFrameworkCore -Version 1.6.1
<PackageReference Include="MultiTenancy.EntityFrameworkCore" Version="1.6.1" />
<PackageVersion Include="MultiTenancy.EntityFrameworkCore" Version="1.6.1" />
<PackageReference Include="MultiTenancy.EntityFrameworkCore" />
paket add MultiTenancy.EntityFrameworkCore --version 1.6.1
#r "nuget: MultiTenancy.EntityFrameworkCore, 1.6.1"
#:package MultiTenancy.EntityFrameworkCore@1.6.1
#addin nuget:?package=MultiTenancy.EntityFrameworkCore&version=1.6.1
#tool nuget:?package=MultiTenancy.EntityFrameworkCore&version=1.6.1
MultiTenancy.EntityFrameworkCore
Multi-tenant data isolation support for Entity Framework Core applications
Installation
dotnet add package MultiTenancy.EntityFrameworkCore
Description
Multi-tenant data isolation support for Entity Framework Core applications.
Provides the ambient tenant context (ICurrentTenantService) and, since 1.5.0, the EF Core half:
a global tenant query filter and a write-side stamping guard.
Usage
Have your DbContext implement ITenantScopedDbContext, then call the two helpers:
using MultiTenancy.Abstractions;
using MultiTenancy.EntityFrameworkCore;
public class AppDbContext : DbContext, ITenantScopedDbContext
{
private readonly ICurrentTenantService _currentTenantService;
public ICurrentTenantService CurrentTenantService => _currentTenantService;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// ... entity configuration ...
modelBuilder.ApplyTenantFilters(this, TenantFilterMode.FailOpen);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ChangeTracker.StampTenancy(_currentTenantService);
return await base.SaveChangesAsync(cancellationToken);
}
}
ApplyTenantFilters(context, mode)
Adds a global query filter to every entity deriving from BaseTenantEntity.
mode is required and has no default — the two polarities differ by one character at the call
site and by "every tenant's rows" at runtime:
| Mode | With NO ambient tenant | Use when |
|---|---|---|
TenantFilterMode.FailOpen |
matches every row (pass-through) | migrations, background jobs, MassTransit consumers and unauthenticated webhook/reset paths must still read. Those paths get isolation from an explicit TenantId predicate, not from this filter. |
TenantFilterMode.FailClosed |
matches no rows | a forgotten tenant argument should fail loudly ("not found") rather than leak silently. Requires IgnoreQueryFilters() + e.TenantId == tenantId repository methods for anonymous paths. |
⚠️ Pass
thisascontext. The filter is deliberately rooted at the DbContext instance so EF Core substitutes the executing context on every query.ICurrentTenantServiceis registered Scoped, and EF caches the model once per process — so a helper that captured the service instance would pin every future request to whichever request happened to build the model. That is a permanent cross-tenant leak, and it is why this method takes a context rather than a service.
ChangeTracker.StampTenancy(currentTenantService)
Stamps TenantId/UserId onto newly added tenant-scoped rows that still hold Guid.Empty, from
the ambient tenant. Purely additive: it never overwrites a value that is already set, never stamps
non-Added rows, and is a no-op when there is no ambient tenant.
It exists because a row saved with TenantId = Guid.Empty raises no error and violates no
constraint — it is simply invisible to the tenant that owns it, forever. Child rows created inside an
aggregate are the usual way that happens.
Documentation
See the NuGet package page for full documentation.
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Related Packages
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
How to Publish
cd C:\desktopContents\projects\SaaS\NuGetPackages\MultiTenancy.EntityFrameworkCore
.\publish.ps1 -ApiKey XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -Bump patch # Bug fixes
.\publish.ps1 -ApiKey XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -Bump minor # New features
.\publish.ps1 -ApiKey XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -Bump major # Breaking changes
| 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 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
- DomainCore (>= 1.0.1)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.EntityFrameworkCore (>= 9.0.6)
- Microsoft.Extensions.Http (>= 8.0.1)
- Security.Claims (>= 1.0.1)
-
net8.0
- DomainCore (>= 1.0.1)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.EntityFrameworkCore (>= 8.0.11)
- Microsoft.Extensions.Http (>= 8.0.1)
- Security.Claims (>= 1.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.