elsa-workflows / elsa-workflows/elsa-core

Programmatic Workflow updates with version increment are not persisted to the database

Open
#7,287 3 comments 0 reactions 0 assignees View on GitHub
triaged
Dominant language
C#
Stars
7.9k
Forks
1.5k
Avg merge
15h 22m
Merged PRs (30d)
114

Description

## Description
When updating a programmatic workflow by increasing its version number in the code, the new version definition is not persisted to the database.

While the `DefaultWorkflowDefinitionStorePopulator` correctly updates a workflow if the version number remains the same, it fails to insert a new record when the version is incremented. This is due to a logic check in `AddOrUpdateCoreAsync` that prevents saving if the Workflow Definition ID exists in the `workflowDefinitionsToSave` list, regardless of the version mismatch.

As a result, the application may run the new logic in memory (as the method returns the new definition), but the database retains the old version and the old JSON definition.

## Steps to Reproduce
To help us identify the issue more quickly, please follow these guidelines:

1. **Detailed Steps**:
1. Create a programmatic workflow (inheriting from `WorkflowBase`) and set the version to `1`.
2. Run the application to allow the `DefaultWorkflowDefinitionStorePopulator` to register the workflow.
3. Verify in the database (`WorkflowDefinitions` table) that Version 1 exists.
4. Stop the application.
5. Modify the programmatic workflow: change the logic (e.g., add a generic activity) and increment the version to `2`.
6. Restart the application.
7. Check the database (`WorkflowDefinitions` table).

2. **Code Snippets**:
The issue is located in `src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs`.

The code attempts to find an exact version match for the *new* version:
```csharp
// Check if there's already a workflow definition stored with this definition ID and version.
var specificVersionFilter = new WorkflowDefinitionFilter
{
DefinitionId = definitionId,
VersionOptions = VersionOptions.SpecificVersion(workflow.Identity.Version)
};

var existingDefinitionVersion = await _workflowDefinitionStore.FindAsync(specificVersionFilter, cancellationToken);

// Set up a list to collect all workflow definitions to be persisted.
var workflowDefinitionsToSave = new HashSet();

if (existingDefinitionVersion != null)
{
workflowDefinitionsToSave.Add(existingDefinitionVersion);
// ...
```

If the version is new (e.g., Version 2), `existingDefinitionVersion` is null. However, the code then checks if the Definition ID exists in `workflowDefinitionsToSave`. In this scenario, `workflowDefinitionsToSave` contains the definition for the *previous* version:

```csharp
if (existingDefinitionVersion is null && workflowDefinitionsToSave.Any(w => w.Id == workflowDefinition.Id))
{
_logger.LogInformation("Workflow with ID {WorkflowId} already exists", workflowDefinition.Id);
return workflowDefinition;
}
```

Because `w.Id` refers to the Definition ID (which is constant across versions), this condition evaluates to `true` even though the versions differ. This causes the method to return early without saving the new version to the database.

3. **Attachments**:
- **Sample Project**: N/A (Standard programmatic workflow setup).

4. **Reproduction Rate**: Every time a programmatic workflow version is incremented.

5. **Video/Screenshots**: N/A

6. **Additional Configuration**:
- Standard Elsa Server setup with Programmatic Workflows enabled.

## Expected Behavior
When a programmatic workflow version is incremented:
1. The system should detect that this specific version does not exist in the database.
2. The system should persist the new version (e.g., Version 2) to the database.
3. The database should reflect the updated JSON definition.

## Actual Behavior
The system detects that the **Definition ID** already exists in the `workflowDefinitionsToSave` list (associated with the previous version) and skips the database update.
- The log outputs: "Workflow with ID {WorkflowId} already exists".
- The database remains on the old version (e.g., Version 1).
- The database JSON content remains outdated.

## Screenshots
N/A

## Environment
- **Elsa Package Version**: 3.5.3
- **Operating System**: N/A (Logic issue, OS independent)
- **Browser and Version**: N/A

## Log Output
```text
Workflow with ID {YourWorkflowDefinitionId} already exists
```

## Troubleshooting Attempts
I have analyzed the source code in `DefaultWorkflowDefinitionStorePopulator.cs`.
If I keep the version number the same, the `existingDefinitionVersion` is found, and the update proceeds correctly. The issue is isolated to when the version number changes, triggering the fallback `if` block that checks `workflowDefinitionsToSave`.

## Additional Context
The logic seems to assume that if the ID exists in `workflowDefinitionsToSave`, the workflow is already handled. However, this check ignores the version.

If the intention of `workflowDefinitionsToSave.Any(w => w.Id == workflowDefinition.Id)` is to prevent duplicate inserts, it should likely check the Version as well, or rely on the fact that `existingDefinitionVersion` was already null.

Reference file: [DefaultWorkflowDefinitionStorePopulator.cs](https://github.com/elsa-workflows/elsa-core/blob/3.5.3/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs)

## Related Issues
N/A

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.