Docs
Tenantry ProAPI reference

TenantryProEfCoreBuilderExtensions class

Namespace: Microsoft.Extensions.DependencyInjection · Package: Tenantry.Pro.EfCore · API reference

Registers Tenantry.Pro's EF Core features on IProBuilder<TKey>.

public static class TenantryProEfCoreBuilderExtensions

Methods

AddAuditLogging<TKey>(IProBuilder<TKey>, Action<AuditOptions>?)

Audit logging: records the changes every context that uses UseTenantry() saves, as AuditEntry values with the current tenant, and passes them to IAuditStore: by default once they are committed (AuditOptions.Timing). The default store writes each entry to the log; register your own IAuditStore, with any lifetime, to replace it.

[RequiresUnreferencedCode("Audit logging reads EF Core's change tracker, which uses reflection and is not trim-safe.")]
[RequiresDynamicCode("Audit logging uses EF Core, which generates code at run time and is not Native AOT-compatible.")]
public static IProBuilder<TKey> AddAuditLogging<TKey>(this IProBuilder<TKey> pro, Action<AuditOptions>? configure = null) where TKey : IEquatable<TKey>, IParsable<TKey>

Type parameters:

  • TKey: The tenant identifier type.

Parameters:

  • pro IProBuilder<TKey>: The Pro builder.
  • configure Action<AuditOptions>: Optionally sets AuditOptions: entity types and properties to exclude, when entries are written, and what a store failure does.

Returns: IProBuilder<TKey>: The same pro for chaining.

Nothing is added to the contexts: UseTenantry() adds the audit interceptor, after Tenantry's own, so a new tenant-owned entity is recorded with its tenant. A context that does not use UseTenantry() is not audited, nor are the saves the store makes.

By default (AuditTiming.AfterCommit) the entries of changes saved in a transaction are written when it commits, a Database.BeginTransaction transaction (shared with other contexts through UseTransaction or not), a TransactionScope or an enlisted one, and discarded if it rolls back. A store failure is then logged, as the changes are committed, or thrown as an AuditStoreException (AuditOptions.OnStoreFailure). With AuditTiming.InTransaction the store is called inside the transaction, so it can write through the same connection and transaction, and its failure is thrown from SaveChanges.

Each entry says who made the change, and its correlation id, as IAuditContextProvider gives them: by default no one, and the current trace's id. Register your own to name the user.

Options that are not valid (an undefined AuditOptions.Timing or AuditOptions.OnStoreFailure) stop the application from starting.

tenant.UsePro(pro => pro.AddAuditLogging());
builder.Services.AddScoped<IAuditStore, MyDatabaseAuditStore>();

AddDatabaseProvisioning<TContext>(IProBuilder, Action<DatabaseProvisioningOptions<TContext>>?)

Adds creating each tenant's database to tenant provisioning (ITenantProvisioner<TKey>), as the CreateDatabase step, which runs first. It creates the database TContext connects to for the tenant, through EF Core's database creator, unless it exists. In mixed mode it applies only to TenantIsolation.Database tenants.

public static IProBuilder AddDatabaseProvisioning<TContext>(this IProBuilder pro, Action<DatabaseProvisioningOptions<TContext>>? configure = null) where TContext : DbContext

Type parameters:

  • TContext: The context whose database is created, with any relational EF Core provider.

Parameters:

Returns: IProBuilder: The same builder, without its key type: in a chain, call it after methods that need the key type.

The step uses TContext from the tenant's scope, so with Tenantry Core's AddDbContextPerTenantDatabase it creates the tenant's own database. Its credentials must be allowed to create databases, or set DatabaseProvisioningOptions<TContext>.CreateContext. Two runs for one tenant at once both succeed: the one whose create fails waits, up to 30 seconds, for the database to come online, and logs the failure as a warning, in case no other run created it.

AddMigrations<TContext>(IProBuilder, Action<TenantMigrationOptions<TContext>>?)

Applies TContext's EF Core migrations for every tenant: registers ITenantMigrationRunner<TKey>, adds the Migrations step to tenant provisioning (ITenantProvisioner<TKey>), after the database or schema is created, and optionally applies them when the application starts (TenantMigrationOptions<TContext>.OnStartup).

[RequiresUnreferencedCode("EF Core migrations use reflection and are not trim-safe. Use a migration bundle for trimmed applications.")]
[RequiresDynamicCode("EF Core migrations use dynamic code generation and are not Native AOT-compatible. Use a migration bundle for AOT applications.")]
public static IProBuilder AddMigrations<TContext>(this IProBuilder pro, Action<TenantMigrationOptions<TContext>>? configure = null) where TContext : DbContext

Type parameters:

  • TContext: The context whose migrations are applied.

Parameters:

  • pro IProBuilder: The Pro builder.
  • configure Action<TenantMigrationOptions<TContext>>: Optionally sets when migrations run at startup, and how the context is created.

Returns: IProBuilder: The same builder, without its key type: in a chain, call it after methods that need the key type.

The context comes from the application's registration, in each tenant's scope, so it connects to the tenant's database (Tenantry Core's AddDbContextPerTenantDatabase) and, with schema per tenant, uses the tenant's schema. Tenants whose context connects to the same database and schema are migrated once: in mixed mode, the shared database. Call it once for each context to migrate.

With schema per tenant (UseSchemaPerTenant), the migrations are generated without a schema (the design-time model has none) and applied to each tenant's schema, with the migration history table in that schema. SQL a migration runs with migrationBuilder.Sql is applied as written.

In mixed mode the provisioning step applies to TenantIsolation.Database and TenantIsolation.Schema tenants; the shared database is migrated with every tenant, by the runner.

tenant.UsePro(pro => pro.AddMigrations<AppDbContext>());

AddSchemaProvisioning<TContext>(IProBuilder, Action<SchemaProvisioningOptions<TContext>>?)

Adds creating each tenant's schema to tenant provisioning (ITenantProvisioner<TKey>), as the CreateSchema step, which runs first. It creates the tenant's schema (UseSchemaPerTenant), unless it exists, in the database TContext connects to for the tenant. In mixed mode it applies only to TenantIsolation.Schema tenants.

public static IProBuilder AddSchemaProvisioning<TContext>(this IProBuilder pro, Action<SchemaProvisioningOptions<TContext>>? configure = null) where TContext : DbContext

Type parameters:

  • TContext: The context, on SQL Server or PostgreSQL, whose database the schema is created in.

Parameters:

Returns: IProBuilder: The same builder, without its key type: in a chain, call it after methods that need the key type.

It needs pro.UseSchemaPerTenant(...): without it, the application does not start. The step creates the schema with EF Core's migrations SQL, so only SQL Server and PostgreSQL are supported (MySQL has no schemas apart from databases); another provider fails the step with NotSupportedException. A schema name too long for the database, or with control characters, fails it with InvalidOperationException. Its credentials must be allowed to create schemas, or set SchemaProvisioningOptions<TContext>.CreateContext.

UseSchemaPerTenant<TKey>(IProBuilder<TKey>, Action<SchemaPerTenantOptions<TKey>>)

Schema per tenant: each tenant's tables in a schema of its own. Every context that uses UseTenantry() gets the current tenant's schema as its default schema, with a compiled model per schema; nothing in the context or its registration names the schema.

public static IProBuilder<TKey> UseSchemaPerTenant<TKey>(this IProBuilder<TKey> pro, Action<SchemaPerTenantOptions<TKey>> configure) where TKey : IEquatable<TKey>, IParsable<TKey>

Type parameters:

  • TKey: The tenant identifier type.

Parameters:

Returns: IProBuilder<TKey>: The same pro for chaining.

The schema is the model's default schema, set after OnModelCreating, so it applies to every table without a schema of its own. Without a current tenant (design-time tools such as dotnet ef, say) the model has no default schema, so migrations are generated without one. In mixed mode only TenantIsolation.Schema tenants get a schema; the others keep the database's default.

The contexts cannot be pooled: a pooled context keeps the model of the first tenant it served. Creating one with AddDbContextPool, AddPooledDbContextFactory or Tenantry Core's AddDbContextPerTenantDatabase with pooled: true throws InvalidOperationException. Each context type also gets a model cache sized for SchemaPerTenantOptions<TKey>.MaxCachedSchemas schemas, in place of EF Core's, in an EF Core internal service provider of its own that the applications in the process share; EF Core throws once a process has built more than 20 of them. UseMemoryCache, or ReplaceService of IModelCacheKeyFactory or IMemoryCache, on their options throws InvalidOperationException.

Options that are not valid (no GetSchemaName, a cache size out of range) stop the application from starting.

tenant.UsePro(pro => pro.UseSchemaPerTenant(o => o.GetSchemaName = t => $"tenant_{t.TenantId}"));

On this page