elsa-core/src/modules/Elsa.Persistence.EFCore.Common/PersistenceFeatureBase.cs

99 lines
3.5 KiB
C#
Raw Normal View History

using Elsa.Common.Entities;
using Elsa.Extensions;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
Fix Multitenancy Support and Normalize Tenant ID Handling (#7217) * Enable multitenancy support and normalize tenant ID handling. - Activate multitenancy in `Program.cs`. - Introduce `NormalizeTenantId` method for consistent tenant ID usage. - Update tenant-related classes and features to support normalization logic. * Add ADR for adopting empty string as the default tenant ID - Standardized the tenant ID for the default tenant to use an empty string (`""`) instead of `null`. - Documented the rationale and migration considerations in ADR 0007. - Updated ADR table of contents and graph for new entry. * Apply suggestion from @sfmskywalker * Update doc/adr/graph.dot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Normalize spacing and improve readability in `Program.cs`. Fix multitenancy condition formatting. * Fix ADR numbering and update TOC * Add ADRs for flowchart execution model, tenant deletion event, merge modes, and default tenant ID - Introduced ADR 0005: Token-centric flowchart execution model for improved loop and join handling. - Added ADR 0006: Tenant Deleted event for distinct handling of tenant removal. - Documented ADR 0007: Explicit merge modes for flowchart joins, improving reliability and configurability. - Included ADR 0008: Standardization of empty string as the default tenant ID for consistency and clarity. * Add unit tests for tenant ID normalization and multitenancy pipeline invoker - Added comprehensive unit tests for tenant ID normalization to ensure consistent handling of null, empty, and valid IDs. - Introduced tests for the multitenancy pipeline invoker covering various tenant resolution scenarios. - Updated solution to include new unit testing projects for `Elsa.Tenants` and `Elsa.Common`. * Update unit tests for `ActivityConstructionResult` - Refactor test parameterization to verify `HasExceptions` property more explicitly. - Simplify exception creation logic in helper methods. - Improve test assertions by combining act and assert phases where applicable. * Enable configuration-based multitenancy with tenant-specific settings - Introduced a configuration-based tenant provider to streamline tenant initialization and customization. - Added tenant ID handling filters to ensure tenant ID is applied and filtered automatically. - Deprecated the `CommonPersistenceFeature` in favor of modular persistence feature extension. * Update database indexes to include `TenantId` for multitenancy support - Added `TenantId` to unique constraints on `Triggers` table across all EFCore providers. - Adjusted index names to reflect the updated constraints. - Updated trigger configuration to ensure uniqueness includes `TenantId`. * Add tenant filtering to `DefaultWorkflowDefinitionStorePopulator` - Introduced `ITenantAccessor` to support tenant-specific filtering of workflow definitions. - Updated logic to skip workflows not matching the current tenant. * Update doc/adr/toc.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove `CommonPersistenceFeature` as it has been deprecated * Add tenant-specific filtering to workflow import logic in `DefaultWorkflowDefinitionStorePopulator` * Replace hardcoded tenant ID with `Tenant.DefaultTenantId` in integration tests * Update database indexes and migration logic to support `TenantId` for multitenancy - Added `TenantId` to unique constraints on the `Triggers` table and updated index names. - Included logic to drop outdated indexes without `TenantId` during migration. - Adjusted tests to account for `TenantId` in workflow identity and indexing scenarios. * Remove `TenantId` from workflow identity construction in concurrent trigger indexing tests * Introduce `SelectiveMockLockProvider` for precise lock mocking in tests - Added `SelectiveMockLockProvider` to allow targeted lock mocking without affecting unrelated background operations. - Updated test services to use `SelectiveMockLockProvider` in place of `TestDistributedLockProvider`. - Refactored `DistributedLockResilienceTests` to support selective mocking for deterministic and reliable assertions. * Update Elsa.sln Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Normalize tenant ID handling in `DefaultWorkflowDefinitionStorePopulator` for consistent filtering * Refactor `TenantResolverResult` to support explicit resolved/unresolved state handling - Updated `TenantResolverResult` to include an explicit `_isResolved` property. - Adjusted `ResolveTenantId()` and `IsResolved` logic for improved clarity and robustness. - Simplified tenant resolution invocation in `TenantResolverBase`. - Removed redundant normalization in `DefaultTenantResolverPipelineInvoker`. * Normalize tenant ID handling in `DefaultWorkflowDefinitionStorePopulator` and `ClrWorkflowsProvider`. * Refactor `DefaultWorkflowDefinitionStorePopulatorTests`: streamline object initializations and add tenant-specific test coverage for `PopulateStoreAsync`. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-30 18:54:13 +00:00
using Elsa.Persistence.EFCore.EntityHandlers;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
// ReSharper disable once CheckNamespace
namespace Elsa.Persistence.EFCore;
public abstract class PersistenceFeatureBase<TFeature, TDbContext>(IModule module) : FeatureBase(module)
where TDbContext : ElsaDbContextBase
{
/// <summary>
/// Gets or sets a value indicating whether to use context pooling.
/// </summary>
public virtual bool UseContextPooling { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to run migrations.
/// </summary>
public virtual bool RunMigrations { get; set; } = true;
/// <summary>
/// Gets or sets the lifetime of the <see cref="IDbContextFactory{TContext}"/>. Defaults to <see cref="ServiceLifetime.Singleton"/>.
/// </summary>
public ServiceLifetime DbContextFactoryLifetime { get; set; } = ServiceLifetime.Scoped;
/// <summary>
/// Gets or sets the callback used to configure the <see cref="DbContextOptionsBuilder"/>.
/// </summary>
public virtual Action<IServiceProvider, DbContextOptionsBuilder> DbContextOptionsBuilder { get; set; } = null!;
public override void ConfigureHostedServices()
{
ConfigureMigrations();
}
/// <inheritdoc />
public override void Apply()
{
if (DbContextOptionsBuilder == null)
throw new InvalidOperationException("The DbContextOptionsBuilder must be configured.");
Action<IServiceProvider, DbContextOptionsBuilder> setup = (sp, opts) =>
{
opts.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
DbContextOptionsBuilder(sp, opts);
};
if (UseContextPooling)
Services.AddPooledDbContextFactory<TDbContext>(setup);
else
Services.AddDbContextFactory<TDbContext>(setup, DbContextFactoryLifetime);
Services.Decorate<IDbContextFactory<TDbContext>, TenantAwareDbContextFactory<TDbContext>>();
Services.Configure<MigrationOptions>(options =>
{
options.RunMigrations[typeof(TDbContext)] = RunMigrations;
});
Fix Multitenancy Support and Normalize Tenant ID Handling (#7217) * Enable multitenancy support and normalize tenant ID handling. - Activate multitenancy in `Program.cs`. - Introduce `NormalizeTenantId` method for consistent tenant ID usage. - Update tenant-related classes and features to support normalization logic. * Add ADR for adopting empty string as the default tenant ID - Standardized the tenant ID for the default tenant to use an empty string (`""`) instead of `null`. - Documented the rationale and migration considerations in ADR 0007. - Updated ADR table of contents and graph for new entry. * Apply suggestion from @sfmskywalker * Update doc/adr/graph.dot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Normalize spacing and improve readability in `Program.cs`. Fix multitenancy condition formatting. * Fix ADR numbering and update TOC * Add ADRs for flowchart execution model, tenant deletion event, merge modes, and default tenant ID - Introduced ADR 0005: Token-centric flowchart execution model for improved loop and join handling. - Added ADR 0006: Tenant Deleted event for distinct handling of tenant removal. - Documented ADR 0007: Explicit merge modes for flowchart joins, improving reliability and configurability. - Included ADR 0008: Standardization of empty string as the default tenant ID for consistency and clarity. * Add unit tests for tenant ID normalization and multitenancy pipeline invoker - Added comprehensive unit tests for tenant ID normalization to ensure consistent handling of null, empty, and valid IDs. - Introduced tests for the multitenancy pipeline invoker covering various tenant resolution scenarios. - Updated solution to include new unit testing projects for `Elsa.Tenants` and `Elsa.Common`. * Update unit tests for `ActivityConstructionResult` - Refactor test parameterization to verify `HasExceptions` property more explicitly. - Simplify exception creation logic in helper methods. - Improve test assertions by combining act and assert phases where applicable. * Enable configuration-based multitenancy with tenant-specific settings - Introduced a configuration-based tenant provider to streamline tenant initialization and customization. - Added tenant ID handling filters to ensure tenant ID is applied and filtered automatically. - Deprecated the `CommonPersistenceFeature` in favor of modular persistence feature extension. * Update database indexes to include `TenantId` for multitenancy support - Added `TenantId` to unique constraints on `Triggers` table across all EFCore providers. - Adjusted index names to reflect the updated constraints. - Updated trigger configuration to ensure uniqueness includes `TenantId`. * Add tenant filtering to `DefaultWorkflowDefinitionStorePopulator` - Introduced `ITenantAccessor` to support tenant-specific filtering of workflow definitions. - Updated logic to skip workflows not matching the current tenant. * Update doc/adr/toc.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove `CommonPersistenceFeature` as it has been deprecated * Add tenant-specific filtering to workflow import logic in `DefaultWorkflowDefinitionStorePopulator` * Replace hardcoded tenant ID with `Tenant.DefaultTenantId` in integration tests * Update database indexes and migration logic to support `TenantId` for multitenancy - Added `TenantId` to unique constraints on the `Triggers` table and updated index names. - Included logic to drop outdated indexes without `TenantId` during migration. - Adjusted tests to account for `TenantId` in workflow identity and indexing scenarios. * Remove `TenantId` from workflow identity construction in concurrent trigger indexing tests * Introduce `SelectiveMockLockProvider` for precise lock mocking in tests - Added `SelectiveMockLockProvider` to allow targeted lock mocking without affecting unrelated background operations. - Updated test services to use `SelectiveMockLockProvider` in place of `TestDistributedLockProvider`. - Refactored `DistributedLockResilienceTests` to support selective mocking for deterministic and reliable assertions. * Update Elsa.sln Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Normalize tenant ID handling in `DefaultWorkflowDefinitionStorePopulator` for consistent filtering * Refactor `TenantResolverResult` to support explicit resolved/unresolved state handling - Updated `TenantResolverResult` to include an explicit `_isResolved` property. - Adjusted `ResolveTenantId()` and `IsResolved` logic for improved clarity and robustness. - Simplified tenant resolution invocation in `TenantResolverBase`. - Removed redundant normalization in `DefaultTenantResolverPipelineInvoker`. * Normalize tenant ID handling in `DefaultWorkflowDefinitionStorePopulator` and `ClrWorkflowsProvider`. * Refactor `DefaultWorkflowDefinitionStorePopulatorTests`: streamline object initializations and add tenant-specific test coverage for `PopulateStoreAsync`. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-30 18:54:13 +00:00
Services.AddScoped<IEntitySavingHandler, ApplyTenantId>();
Services.AddScoped<IEntityModelCreatingHandler, SetTenantIdFilter>();
}
protected virtual void ConfigureMigrations()
{
Services.AddStartupTask<RunMigrationsStartupTask<TDbContext>>();
}
/// <summary>
/// Adds a store to the service collection.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TStore">The type of the store.</typeparam>
protected void AddStore<TEntity, TStore>() where TEntity : class, new() where TStore : class
{
Services
.AddScoped<Store<TDbContext, TEntity>>()
.AddScoped<TStore>()
;
}
/// <summary>
/// Adds an entity store to the service collection.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TStore">The type of the store.</typeparam>
protected void AddEntityStore<TEntity, TStore>() where TEntity : Entity, new() where TStore : class
{
Services
.AddScoped<EntityStore<TDbContext, TEntity>>()
.AddScoped<TStore>()
;
}
}