From 2ed9ccf032da0b2c356bfc3ad96645e7b7623e8a Mon Sep 17 00:00:00 2001 From: Ralf Date: Fri, 6 Feb 2026 09:53:00 +0100 Subject: [PATCH 1/6] Assign Tenant.AgnosticTenantId in populator --- .../Services/DefaultWorkflowDefinitionStorePopulator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs index 7650fea41d..9e9527411f 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs @@ -69,7 +69,7 @@ public async Task> PopulateStoreAsync(bool index foreach (var result in results) { // Normalize tenant IDs for comparison (null becomes empty string) - var definitionTenantId = result.Workflow.Identity.TenantId.NormalizeTenantId(); + var definitionTenantId = result.Workflow.Identity.TenantId ?? Tenant.AgnosticTenantId; // Only import workflows belonging to the current tenant or tenant-agnostic workflows (TenantId = "*"). if (definitionTenantId != currentTenantId && definitionTenantId != Tenant.AgnosticTenantId) @@ -192,7 +192,7 @@ private async Task AddOrUpdateCoreAsync(MaterializedWorkflow // Determine the tenant ID for the workflow definition // If the workflow has no tenant ID, use the current tenant (normalized to handle null -> "") - var workflowTenantId = workflow.Identity.TenantId ?? (_tenantAccessor.Tenant?.Id).NormalizeTenantId(); + var workflowTenantId = workflow.Identity.TenantId ?? _tenantAccessor.Tenant?.Id ?? Tenant.AgnosticTenantId; var workflowDefinition = existingDefinitionVersion ?? new WorkflowDefinition { From 5969dfe8a5a2ad5173eed7778cdce4de98216188 Mon Sep 17 00:00:00 2001 From: Ralf Date: Fri, 6 Feb 2026 09:59:15 +0100 Subject: [PATCH 2/6] Corrected comment --- .../Services/DefaultWorkflowDefinitionStorePopulator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs index 9e9527411f..12b8bdd188 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs @@ -191,7 +191,7 @@ private async Task AddOrUpdateCoreAsync(MaterializedWorkflow await UpdateIsPublished(); // Determine the tenant ID for the workflow definition - // If the workflow has no tenant ID, use the current tenant (normalized to handle null -> "") + // If the workflow has no tenant ID, use the current tenant (normalized to handle null -> "*") var workflowTenantId = workflow.Identity.TenantId ?? _tenantAccessor.Tenant?.Id ?? Tenant.AgnosticTenantId; var workflowDefinition = existingDefinitionVersion ?? new WorkflowDefinition From c70c73c3c351fdcb0b9e99f8f8a1bb92b2849544 Mon Sep 17 00:00:00 2001 From: Ralf Date: Fri, 6 Feb 2026 13:43:33 +0100 Subject: [PATCH 3/6] Fix entities persisted with NULL TenantId instead of default tenant Use normalized TenantId property (which returns empty string for default tenant) instead of Tenant?.Id (which returns null when no tenant is set). When Tenant?.Id returned null: TenantAwareDbContextFactory set dbContext.TenantId to null, ApplyTenantId skipped assigning TenantId, entities were saved with NULL TenantId, and query filters using SQL equality against NULL never matched these entities. This affected all EF Core stores including BookmarkStore and any store using bulk upsert operations. Co-authored-by: Cursor --- src/modules/Elsa.Persistence.EFCore.Common/Store.cs | 2 +- .../TenantAwareDbContextFactory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Persistence.EFCore.Common/Store.cs b/src/modules/Elsa.Persistence.EFCore.Common/Store.cs index 680e2ca7a8..c8ddbdd43f 100644 --- a/src/modules/Elsa.Persistence.EFCore.Common/Store.cs +++ b/src/modules/Elsa.Persistence.EFCore.Common/Store.cs @@ -182,7 +182,7 @@ public async Task SaveManyAsync( } // When doing a custom SQL query (Bulk Upsert), none of the installed query filters will be applied. Hence, we are assigning the current tenant ID explicitly. - var tenantId = serviceProvider.GetRequiredService().Tenant?.Id; + var tenantId = serviceProvider.GetRequiredService().TenantId; foreach (var entity in entityList) { if (entity is Entity entityWithTenant) diff --git a/src/modules/Elsa.Persistence.EFCore.Common/TenantAwareDbContextFactory.cs b/src/modules/Elsa.Persistence.EFCore.Common/TenantAwareDbContextFactory.cs index 8128553ce3..bcf4fd2272 100644 --- a/src/modules/Elsa.Persistence.EFCore.Common/TenantAwareDbContextFactory.cs +++ b/src/modules/Elsa.Persistence.EFCore.Common/TenantAwareDbContextFactory.cs @@ -32,6 +32,6 @@ public async Task CreateDbContextAsync(CancellationToken cancellatio private void SetTenantId(TDbContext context) { if (context is ElsaDbContextBase elsaContext) - elsaContext.TenantId = tenantAccessor.Tenant?.Id; + elsaContext.TenantId = tenantAccessor.TenantId; } } \ No newline at end of file From a0919bf17d24f0ccc1349732ecfc48aae7ea79fa Mon Sep 17 00:00:00 2001 From: RalfvandenBurg Date: Mon, 9 Feb 2026 09:40:11 +0100 Subject: [PATCH 4/6] Update src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../Services/DefaultWorkflowDefinitionStorePopulator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs index 12b8bdd188..b6bf1612c4 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs @@ -192,7 +192,7 @@ private async Task AddOrUpdateCoreAsync(MaterializedWorkflow // Determine the tenant ID for the workflow definition // If the workflow has no tenant ID, use the current tenant (normalized to handle null -> "*") - var workflowTenantId = workflow.Identity.TenantId ?? _tenantAccessor.Tenant?.Id ?? Tenant.AgnosticTenantId; + var workflowTenantId = workflow.Identity.TenantId ?? _tenantAccessor.TenantId; var workflowDefinition = existingDefinitionVersion ?? new WorkflowDefinition { From 8b30fe445b287c77cdd4fe1a02e09eab158d7430 Mon Sep 17 00:00:00 2001 From: Ralf Date: Mon, 9 Feb 2026 09:52:56 +0100 Subject: [PATCH 5/6] PR comments --- .../Services/DefaultWorkflowDefinitionStorePopulator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs index b6bf1612c4..b49f637857 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs @@ -60,7 +60,7 @@ public async Task> PopulateStoreAsync(bool index { var providers = _workflowDefinitionProviders(); var workflowDefinitions = new List(); - var currentTenantId = (_tenantAccessor.Tenant?.Id).NormalizeTenantId(); + var currentTenantId = _tenantAccessor.TenantId; foreach (var provider in providers) { @@ -69,7 +69,7 @@ public async Task> PopulateStoreAsync(bool index foreach (var result in results) { // Normalize tenant IDs for comparison (null becomes empty string) - var definitionTenantId = result.Workflow.Identity.TenantId ?? Tenant.AgnosticTenantId; + var definitionTenantId = result.Workflow.Identity.TenantId ?? _tenantAccessor.TenantId; // Only import workflows belonging to the current tenant or tenant-agnostic workflows (TenantId = "*"). if (definitionTenantId != currentTenantId && definitionTenantId != Tenant.AgnosticTenantId) From faab34c639f48a39814967aea10e4b7b6732cf27 Mon Sep 17 00:00:00 2001 From: Ralf Date: Mon, 9 Feb 2026 10:09:42 +0100 Subject: [PATCH 6/6] Set Tenant.AgnosticTenantId when no tenantId is specified --- .../Elsa.Workflows.Runtime/Providers/ClrWorkflowsProvider.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowsProvider.cs b/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowsProvider.cs index debe17fb28..94aa271a2e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowsProvider.cs +++ b/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowsProvider.cs @@ -1,3 +1,4 @@ +using Elsa.Common.Multitenancy; using Elsa.Workflows.Management.Materializers; using Elsa.Workflows.Runtime.Features; using Elsa.Workflows.Runtime.Options; @@ -10,7 +11,7 @@ namespace Elsa.Workflows.Runtime.Providers; /// Provides workflows to the system that are registered with /// [UsedImplicitly] -public class ClrWorkflowsProvider( +public class ( IOptions options, IWorkflowBuilderFactory workflowBuilderFactory, IServiceProvider serviceProvider) : IWorkflowsProvider @@ -41,7 +42,7 @@ private async Task BuildWorkflowAsync(Func