CleanArchitecture.Extensions.Multitenancy.EFCore 0.2.9

dotnet add package CleanArchitecture.Extensions.Multitenancy.EFCore --version 0.2.9
                    
NuGet\Install-Package CleanArchitecture.Extensions.Multitenancy.EFCore -Version 0.2.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CleanArchitecture.Extensions.Multitenancy.EFCore" Version="0.2.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CleanArchitecture.Extensions.Multitenancy.EFCore" Version="0.2.9" />
                    
Directory.Packages.props
<PackageReference Include="CleanArchitecture.Extensions.Multitenancy.EFCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CleanArchitecture.Extensions.Multitenancy.EFCore --version 0.2.9
                    
#r "nuget: CleanArchitecture.Extensions.Multitenancy.EFCore, 0.2.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CleanArchitecture.Extensions.Multitenancy.EFCore@0.2.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CleanArchitecture.Extensions.Multitenancy.EFCore&version=0.2.9
                    
Install as a Cake Addin
#tool nuget:?package=CleanArchitecture.Extensions.Multitenancy.EFCore&version=0.2.9
                    
Install as a Cake Tool

CleanArchitecture.Extensions.Multitenancy.EFCore

EF Core adapters for tenant-aware data isolation in the JaysonTaylorCleanArchitectureBlank template. This package adds model filters, SaveChanges enforcement, schema helpers, and tenant-aware DbContext primitives.

Step 1 - Install the package

Install in the Infrastructure project:

dotnet add src/Infrastructure/Infrastructure.csproj package CleanArchitecture.Extensions.Multitenancy.EFCore

Step 2 - Register EF Core multitenancy services

File: src/Infrastructure/DependencyInjection.cs

using CleanArchitecture.Extensions.Multitenancy.EFCore;
using CleanArchitecture.Extensions.Multitenancy.EFCore.Interceptors;
using CleanArchitecture.Extensions.Multitenancy.EFCore.Options;
using Microsoft.EntityFrameworkCore;

builder.Services.AddCleanArchitectureMultitenancyEfCore(options =>
{
    options.Mode = TenantIsolationMode.SharedDatabase;
    options.TenantIdPropertyName = "TenantId";
    options.UseShadowTenantId = true;
});

builder.Services.AddDbContext<ApplicationDbContext>((sp, options) =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("JaysonTaylorCleanArchitectureBlankDb"));
    options.AddInterceptors(sp.GetRequiredService<TenantSaveChangesInterceptor>());
    options.UseTenantModelCacheKeyFactory(sp);
});

Call options.UseTenantModelCacheKeyFactory(sp) when configuring your DbContext to enable tenant-aware model cache keys. If you need a custom IModelCacheKeyFactory, call ReplaceService after this line.

If you already register ISaveChangesInterceptor instances (as the template does), you can omit the explicit TenantSaveChangesInterceptor line and rely on GetServices<ISaveChangesInterceptor>().

Row-level filtering/enforcement defaults to shared database mode. For schema/database-per-tenant setups, set UseShadowTenantId, EnableQueryFilters, and EnableSaveChangesEnforcement to true if you want defense-in-depth at the row level.

Step 3 - Wire tenant context into your DbContext

Option A: use the tenant-aware base class (non-Identity DbContext)

using CleanArchitecture.Extensions.Multitenancy.Abstractions;
using CleanArchitecture.Extensions.Multitenancy.EFCore.Abstractions;
using CleanArchitecture.Extensions.Multitenancy.EFCore;

public sealed class ApplicationDbContext : TenantDbContext
{
    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options,
        ICurrentTenant currentTenant,
        IOptions<EfCoreMultitenancyOptions> optionsAccessor,
        ITenantModelCustomizer modelCustomizer)
        : base(options, currentTenant, optionsAccessor, modelCustomizer)
    {
    }
}

Option B: keep IdentityDbContext (template default)

The Jason Taylor template uses IdentityDbContext, so you can implement ITenantDbContext directly and apply the model customizer after your configurations.

using System.Reflection;
using CleanArchitecture.Extensions.Multitenancy.Abstractions;
using CleanArchitecture.Extensions.Multitenancy.EFCore.Abstractions;
using CleanArchitecture.Extensions.Multitenancy.EFCore.Options;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, ITenantDbContext
{
    private readonly ICurrentTenant _currentTenant;
    private readonly EfCoreMultitenancyOptions _multitenancyOptions;
    private readonly ITenantModelCustomizer _tenantModelCustomizer;

    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options,
        ICurrentTenant currentTenant,
        IOptions<EfCoreMultitenancyOptions> optionsAccessor,
        ITenantModelCustomizer tenantModelCustomizer)
        : base(options)
    {
        _currentTenant = currentTenant;
        _multitenancyOptions = optionsAccessor.Value;
        _tenantModelCustomizer = tenantModelCustomizer;
    }

    public string? CurrentTenantId => _currentTenant.TenantId;

    public ITenantInfo? CurrentTenantInfo => _currentTenant.TenantInfo;

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
        _tenantModelCustomizer.Customize(builder, this, _multitenancyOptions);
    }
}

Note: if you add your own HasQueryFilter calls, apply tenant filtering last by calling ApplyTenantModel(builder) (from TenantDbContext) or _tenantModelCustomizer.Customize(...) after your configurations.

Step 4 - Mark tenant entities

Use explicit tenant identifiers or let the extension add a shadow property:

using CleanArchitecture.Extensions.Multitenancy.EFCore.Abstractions;

public sealed class Customer : ITenantEntity
{
    public int Id { get; set; }
    public string TenantId { get; set; } = string.Empty;
}

If you already use BaseAuditableEntity, you can either:

  • Keep it as-is and rely on the shadow TenantId property.
  • Implement ITenantEntity on your base entity to make the tenant column explicit.

To exclude global entities from filtering:

using CleanArchitecture.Extensions.Multitenancy.EFCore.Abstractions;

[GlobalEntity]
public sealed class FeatureFlag
{
    public int Id { get; set; }
}

Identity entities

By default, ASP.NET Core Identity entities are treated as global to keep the template working without extra changes. Set TreatIdentityEntitiesAsGlobal = false if you want tenant-scoped identity and add a tenant identifier to your Identity entities or custom stores.

Schema-per-tenant setup

builder.Services.AddCleanArchitectureMultitenancyEfCore(options =>
{
    options.Mode = TenantIsolationMode.SchemaPerTenant;
    options.SchemaNameFormat = "tenant_{0}";
});

Database-per-tenant setup

builder.Services.AddCleanArchitectureMultitenancyEfCore(options =>
{
    options.Mode = TenantIsolationMode.DatabasePerTenant;
    options.ConnectionStringFormat = "Server=.;Database=Tenant_{0};Trusted_Connection=True;TrustServerCertificate=True;";
});

builder.Services.AddDbContextFactory<ApplicationDbContext>((sp, options) =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("JaysonTaylorCleanArchitectureBlankDb"));
});

builder.Services.AddTenantDbContextFactory<ApplicationDbContext>();

For request-scoped DbContext registration, resolve the tenant connection inside AddDbContext:

builder.Services.AddDbContext<ApplicationDbContext>((sp, options) =>
{
    var currentTenant = sp.GetRequiredService<ICurrentTenant>();
    var resolver = sp.GetRequiredService<ITenantConnectionResolver>();
    var connectionString = resolver.ResolveConnectionString(currentTenant.TenantInfo);

    if (string.IsNullOrWhiteSpace(connectionString))
    {
        throw new InvalidOperationException("Tenant connection string was not resolved.");
    }

    options.UseSqlServer(connectionString);
});

ITenantDbContextFactory<TContext> switches the connection string per tenant for background tasks and migrations. Register a custom resolver if you want to pull connection strings from a vault or catalog.

What to expect

  • Shared database mode automatically adds a tenant filter (TenantId) to all tenant-scoped entities.
  • SaveChanges enforces tenant ownership and rejects cross-tenant updates.
  • Schema-per-tenant mode applies the tenant schema and isolates EF Core model caching when UseTenantModelCacheKeyFactory(sp) is enabled.
  • Global entities are excluded when they implement IGlobalEntity or use [GlobalEntity].
Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.9 96 1/13/2026
0.2.8 90 1/13/2026
0.2.7 96 1/13/2026
0.2.6 93 1/12/2026
0.2.5 99 1/8/2026
0.2.4 98 1/3/2026
0.2.3 95 1/1/2026
0.2.2 100 1/1/2026