Fix Race Condition Causing Duplicate Trigger Registration in Multi-Engine Environments + Add Concurrency Tests to Prevent Regression (#7131)

* Add component tests for concurrent trigger indexing to prevent duplicate trigger registration in multi-engine workflows (#7130)

* Pin `dotnet-ef` version in `.config/dotnet-tools.json` to address migration bug, update EF Core script modules, and enhance `DesignTimeDbContextFactoryBase` with improved option description.

* Add migrations for EF Core SQLite and MySQL to include unique indexing on triggers.

* Update ConcurrentTriggerIndexing test to verify unique constraint enforcement and throw `DbUpdateException` for duplicate triggers

* Update src/modules/Elsa.Persistence.EFCore/Modules/Runtime/TriggerStore.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/Elsa.Persistence.EFCore/Modules/Runtime/TriggerStore.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update EF Core script to include `Management` module and restore `Sqlite` provider in migration process

* Rename `SimpleHttpApiWorkflow.cs` to `HttpWorkflow.cs` in ConcurrentTriggerIndexing test.

* Remove 'locks' folder from project

Removed the 'locks' folder from the project structure.

* Clean up migration script by removing redundant column type changes

* Update EF Core Oracle migrations to change NVARCHAR2 fields to NCLOB for larger data storage

* Update EF Core Oracle migrations to use NCLOB for larger data storage

* Remove retry logic and logger dependency from EFCoreTriggerStore in TriggerStore implementation

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Sipke Schoorstra 2025-12-05 09:17:14 +01:00 committed by GitHub
parent 63061aa657
commit fdf3e385b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 3278 additions and 15 deletions

13
.config/dotnet-tools.json Normal file
View file

@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.11",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}

View file

@ -14,7 +14,10 @@ public abstract class DesignTimeDbContextFactoryBase<TDbContext> : IDesignTimeDb
public TDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<TDbContext>();
var connectionStringOption = new Option<string>("--connectionString", "Specifies the connection string.");
var connectionStringOption = new Option<string>("--connectionString")
{
Description = "Specifies the connection string."
};
var command = new RootCommand
{
connectionStringOption

View file

@ -0,0 +1,510 @@
// <auto-generated />
using System;
using Elsa.Persistence.EFCore.Modules.Runtime;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.Persistence.EFCore.MySql.Migrations.Runtime
{
[DbContext(typeof(RuntimeElsaDbContext))]
[Migration("20251204150235_V3_6")]
partial class V3_6
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("SerializedValue")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex(new[] { "TenantId" }, "IX_SerializedKeyValuePair_TenantId");
b.ToTable("KeyValuePairs", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.ActivityExecutionRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ActivityName")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("int");
b.Property<int>("AggregateFaultCount")
.HasColumnType("int");
b.Property<DateTimeOffset?>("CompletedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("HasBookmarks")
.HasColumnType("tinyint(1)");
b.Property<string>("SerializedActivityState")
.HasColumnType("longtext");
b.Property<string>("SerializedActivityStateCompressionAlgorithm")
.HasColumnType("longtext");
b.Property<string>("SerializedException")
.HasColumnType("longtext");
b.Property<string>("SerializedMetadata")
.HasColumnType("longtext");
b.Property<string>("SerializedOutputs")
.HasColumnType("longtext");
b.Property<string>("SerializedPayload")
.HasColumnType("longtext");
b.Property<string>("SerializedProperties")
.HasColumnType("longtext");
b.Property<DateTimeOffset>("StartedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityTypeVersion");
b.HasIndex("CompletedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_CompletedAt");
b.HasIndex("HasBookmarks")
.HasDatabaseName("IX_ActivityExecutionRecord_HasBookmarks");
b.HasIndex("StartedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_StartedAt");
b.HasIndex("Status")
.HasDatabaseName("IX_ActivityExecutionRecord_Status");
b.HasIndex("TenantId")
.HasDatabaseName("IX_ActivityExecutionRecord_TenantId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_ActivityExecutionRecord_WorkflowInstanceId");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType_ActivityTypeVersion");
b.ToTable("ActivityExecutionRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.BookmarkQueueItem", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityTypeName")
.HasColumnType("varchar(255)");
b.Property<string>("BookmarkId")
.HasColumnType("varchar(255)");
b.Property<string>("CorrelationId")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("SerializedOptions")
.HasColumnType("longtext");
b.Property<string>("StimulusHash")
.HasColumnType("varchar(255)");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_BookmarkQueueItem_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_BookmarkQueueItem_ActivityTypeName");
b.HasIndex(new[] { "BookmarkId" }, "IX_BookmarkQueueItem_BookmarkId");
b.HasIndex(new[] { "CorrelationId" }, "IX_BookmarkQueueItem_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_BookmarkQueueItem_CreatedAt");
b.HasIndex(new[] { "StimulusHash" }, "IX_BookmarkQueueItem_StimulusHash");
b.HasIndex(new[] { "TenantId" }, "IX_BookmarkQueueItem_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_BookmarkQueueItem_WorkflowInstanceId");
b.ToTable("BookmarkQueueItems", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredBookmark", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("CorrelationId")
.HasColumnType("longtext");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("SerializedMetadata")
.HasColumnType("longtext");
b.Property<string>("SerializedPayload")
.HasColumnType("longtext");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_StoredBookmark_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_StoredBookmark_ActivityTypeName");
b.HasIndex(new[] { "ActivityTypeName", "Hash" }, "IX_StoredBookmark_ActivityTypeName_Hash");
b.HasIndex(new[] { "ActivityTypeName", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_ActivityTypeName_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "CreatedAt" }, "IX_StoredBookmark_CreatedAt");
b.HasIndex(new[] { "Hash" }, "IX_StoredBookmark_Hash");
b.HasIndex(new[] { "Name" }, "IX_StoredBookmark_Name");
b.HasIndex(new[] { "Name", "Hash" }, "IX_StoredBookmark_Name_Hash");
b.HasIndex(new[] { "Name", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_Name_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "TenantId" }, "IX_StoredBookmark_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_StoredBookmark_WorkflowInstanceId");
b.ToTable("Bookmarks", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredTrigger", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("Hash")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("SerializedPayload")
.HasColumnType("longtext");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("Hash")
.HasDatabaseName("IX_StoredTrigger_Hash");
b.HasIndex("Name")
.HasDatabaseName("IX_StoredTrigger_Name");
b.HasIndex("TenantId")
.HasDatabaseName("IX_StoredTrigger_TenantId");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId");
b.ToTable("Triggers", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowExecutionLogRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ActivityInstanceId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ActivityName")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("int");
b.Property<string>("EventName")
.HasColumnType("varchar(255)");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<string>("ParentActivityInstanceId")
.HasColumnType("varchar(255)");
b.Property<long>("Sequence")
.HasColumnType("bigint");
b.Property<string>("SerializedActivityState")
.HasColumnType("longtext");
b.Property<string>("SerializedPayload")
.HasColumnType("longtext");
b.Property<string>("Source")
.HasColumnType("longtext");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetime(6)");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("WorkflowVersion")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityId");
b.HasIndex("ActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityInstanceId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityTypeVersion");
b.HasIndex("EventName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_EventName");
b.HasIndex("ParentActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ParentActivityInstanceId");
b.HasIndex("Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Sequence");
b.HasIndex("TenantId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_TenantId");
b.HasIndex("Timestamp")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowInstanceId");
b.HasIndex("WorkflowVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowVersion");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType_ActivityTypeVersion");
b.HasIndex("Timestamp", "Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp_Sequence");
b.ToTable("WorkflowExecutionLogRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowInboxMessage", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("varchar(255)");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("CorrelationId")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("SerializedBookmarkPayload")
.HasColumnType("longtext");
b.Property<string>("SerializedInput")
.HasColumnType("longtext");
b.Property<string>("TenantId")
.HasColumnType("longtext");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_WorkflowInboxMessage_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_WorkflowInboxMessage_ActivityTypeName");
b.HasIndex(new[] { "CorrelationId" }, "IX_WorkflowInboxMessage_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_WorkflowInboxMessage_CreatedAt");
b.HasIndex(new[] { "ExpiresAt" }, "IX_WorkflowInboxMessage_ExpiresAt");
b.HasIndex(new[] { "Hash" }, "IX_WorkflowInboxMessage_Hash");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_WorkflowInboxMessage_WorkflowInstanceId");
b.ToTable("WorkflowInboxMessages", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,60 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.Persistence.EFCore.MySql.Migrations.Runtime
{
/// <inheritdoc />
public partial class V3_6 : Migration
{
private readonly Elsa.Persistence.EFCore.IElsaDbContextSchema _schema;
/// <inheritdoc />
public V3_6(Elsa.Persistence.EFCore.IElsaDbContextSchema schema)
{
_schema = schema;
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ActivityId",
schema: _schema.Schema,
table: "Triggers",
type: "varchar(255)",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers",
columns: new[] { "WorkflowDefinitionId", "Hash", "ActivityId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers");
migrationBuilder.AlterColumn<string>(
name: "ActivityId",
schema: _schema.Schema,
table: "Triggers",
type: "longtext",
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(255)")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}

View file

@ -18,7 +18,7 @@ namespace Elsa.Persistence.EFCore.MySql.Migrations.Runtime
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
@ -275,7 +275,7 @@ namespace Elsa.Persistence.EFCore.MySql.Migrations.Runtime
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("longtext");
.HasColumnType("varchar(255)");
b.Property<string>("Hash")
.HasColumnType("varchar(255)");
@ -314,6 +314,10 @@ namespace Elsa.Persistence.EFCore.MySql.Migrations.Runtime
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId");
b.ToTable("Triggers", "Elsa");
});

View file

@ -0,0 +1,511 @@
// <auto-generated />
using System;
using Elsa.Persistence.EFCore.Modules.Runtime;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Oracle.EntityFrameworkCore.Metadata;
#nullable disable
namespace Elsa.Persistence.EFCore.Oracle.Migrations.Runtime
{
[DbContext(typeof(RuntimeElsaDbContext))]
[Migration("20251204150355_V3_6")]
partial class V3_6
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("SerializedValue")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex(new[] { "TenantId" }, "IX_SerializedKeyValuePair_TenantId");
b.ToTable("KeyValuePairs", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.ActivityExecutionRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityName")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("NUMBER(10)");
b.Property<int>("AggregateFaultCount")
.HasColumnType("NUMBER(10)");
b.Property<DateTimeOffset?>("CompletedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<bool>("HasBookmarks")
.HasColumnType("BOOLEAN");
b.Property<string>("SerializedActivityState")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedActivityStateCompressionAlgorithm")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedException")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedMetadata")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedOutputs")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedPayload")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedProperties")
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset>("StartedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityTypeVersion");
b.HasIndex("CompletedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_CompletedAt");
b.HasIndex("HasBookmarks")
.HasDatabaseName("IX_ActivityExecutionRecord_HasBookmarks");
b.HasIndex("StartedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_StartedAt");
b.HasIndex("Status")
.HasDatabaseName("IX_ActivityExecutionRecord_Status");
b.HasIndex("TenantId")
.HasDatabaseName("IX_ActivityExecutionRecord_TenantId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_ActivityExecutionRecord_WorkflowInstanceId");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType_ActivityTypeVersion");
b.ToTable("ActivityExecutionRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.BookmarkQueueItem", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityTypeName")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("BookmarkId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("CorrelationId")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("SerializedOptions")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("StimulusHash")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_BookmarkQueueItem_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_BookmarkQueueItem_ActivityTypeName");
b.HasIndex(new[] { "BookmarkId" }, "IX_BookmarkQueueItem_BookmarkId");
b.HasIndex(new[] { "CorrelationId" }, "IX_BookmarkQueueItem_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_BookmarkQueueItem_CreatedAt");
b.HasIndex(new[] { "StimulusHash" }, "IX_BookmarkQueueItem_StimulusHash");
b.HasIndex(new[] { "TenantId" }, "IX_BookmarkQueueItem_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_BookmarkQueueItem_WorkflowInstanceId");
b.ToTable("BookmarkQueueItems", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredBookmark", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("CorrelationId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Name")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("SerializedMetadata")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedPayload")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_StoredBookmark_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_StoredBookmark_ActivityTypeName");
b.HasIndex(new[] { "ActivityTypeName", "Hash" }, "IX_StoredBookmark_ActivityTypeName_Hash");
b.HasIndex(new[] { "ActivityTypeName", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_ActivityTypeName_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "CreatedAt" }, "IX_StoredBookmark_CreatedAt");
b.HasIndex(new[] { "Hash" }, "IX_StoredBookmark_Hash");
b.HasIndex(new[] { "Name" }, "IX_StoredBookmark_Name");
b.HasIndex(new[] { "Name", "Hash" }, "IX_StoredBookmark_Name_Hash");
b.HasIndex(new[] { "Name", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_Name_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "TenantId" }, "IX_StoredBookmark_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_StoredBookmark_WorkflowInstanceId");
b.ToTable("Bookmarks", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredTrigger", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Hash")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Name")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("SerializedPayload")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex("Hash")
.HasDatabaseName("IX_StoredTrigger_Hash");
b.HasIndex("Name")
.HasDatabaseName("IX_StoredTrigger_Name");
b.HasIndex("TenantId")
.HasDatabaseName("IX_StoredTrigger_TenantId");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId")
.HasFilter("\"Hash\" IS NOT NULL");
b.ToTable("Triggers", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowExecutionLogRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityInstanceId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityName")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("NUMBER(10)");
b.Property<string>("EventName")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Message")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("ParentActivityInstanceId")
.HasColumnType("NVARCHAR2(450)");
b.Property<long>("Sequence")
.HasColumnType("NUMBER(19)");
b.Property<string>("SerializedActivityState")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedPayload")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Source")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<int>("WorkflowVersion")
.HasColumnType("NUMBER(10)");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityId");
b.HasIndex("ActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityInstanceId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityTypeVersion");
b.HasIndex("EventName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_EventName");
b.HasIndex("ParentActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ParentActivityInstanceId");
b.HasIndex("Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Sequence");
b.HasIndex("TenantId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_TenantId");
b.HasIndex("Timestamp")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowInstanceId");
b.HasIndex("WorkflowVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowVersion");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType_ActivityTypeVersion");
b.HasIndex("Timestamp", "Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp_Sequence");
b.ToTable("WorkflowExecutionLogRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowInboxMessage", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("CorrelationId")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("SerializedBookmarkPayload")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("SerializedInput")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_WorkflowInboxMessage_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_WorkflowInboxMessage_ActivityTypeName");
b.HasIndex(new[] { "CorrelationId" }, "IX_WorkflowInboxMessage_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_WorkflowInboxMessage_CreatedAt");
b.HasIndex(new[] { "ExpiresAt" }, "IX_WorkflowInboxMessage_ExpiresAt");
b.HasIndex(new[] { "Hash" }, "IX_WorkflowInboxMessage_Hash");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_WorkflowInboxMessage_WorkflowInstanceId");
b.ToTable("WorkflowInboxMessages", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.Persistence.EFCore.Oracle.Migrations.Runtime
{
/// <inheritdoc />
public partial class V3_6 : Migration
{
private readonly Elsa.Persistence.EFCore.IElsaDbContextSchema _schema;
/// <inheritdoc />
public V3_6(Elsa.Persistence.EFCore.IElsaDbContextSchema schema)
{
_schema = schema;
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers",
columns: new[] { "WorkflowDefinitionId", "Hash", "ActivityId" },
unique: true,
filter: "\"Hash\" IS NOT NULL");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers");
}
}
}

View file

@ -18,7 +18,7 @@ namespace Elsa.Persistence.EFCore.Oracle.Migrations.Runtime
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@ -84,7 +84,7 @@ namespace Elsa.Persistence.EFCore.Oracle.Migrations.Runtime
.HasColumnType("NCLOB");
b.Property<string>("SerializedMetadata")
.HasColumnType("NVARCHAR2(2000)");
.HasColumnType("NCLOB");
b.Property<string>("SerializedOutputs")
.HasColumnType("NCLOB");
@ -275,7 +275,7 @@ namespace Elsa.Persistence.EFCore.Oracle.Migrations.Runtime
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Hash")
.HasColumnType("NVARCHAR2(450)");
@ -314,6 +314,11 @@ namespace Elsa.Persistence.EFCore.Oracle.Migrations.Runtime
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId")
.HasFilter("\"Hash\" IS NOT NULL");
b.ToTable("Triggers", "Elsa");
});
@ -348,7 +353,7 @@ namespace Elsa.Persistence.EFCore.Oracle.Migrations.Runtime
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Message")
.HasColumnType("NVARCHAR2(2000)");
.HasColumnType("NCLOB");
b.Property<string>("ParentActivityInstanceId")
.HasColumnType("NVARCHAR2(450)");

View file

@ -0,0 +1,510 @@
// <auto-generated />
using System;
using Elsa.Persistence.EFCore.Modules.Runtime;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Elsa.Persistence.EFCore.PostgreSql.Migrations.Runtime
{
[DbContext(typeof(RuntimeElsaDbContext))]
[Migration("20251204150341_V3_6")]
partial class V3_6
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("SerializedValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex(new[] { "TenantId" }, "IX_SerializedKeyValuePair_TenantId");
b.ToTable("KeyValuePairs", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.ActivityExecutionRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ActivityName")
.HasColumnType("text");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("text");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("integer");
b.Property<int>("AggregateFaultCount")
.HasColumnType("integer");
b.Property<DateTimeOffset?>("CompletedAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("HasBookmarks")
.HasColumnType("boolean");
b.Property<string>("SerializedActivityState")
.HasColumnType("text");
b.Property<string>("SerializedActivityStateCompressionAlgorithm")
.HasColumnType("text");
b.Property<string>("SerializedException")
.HasColumnType("text");
b.Property<string>("SerializedMetadata")
.HasColumnType("text");
b.Property<string>("SerializedOutputs")
.HasColumnType("text");
b.Property<string>("SerializedPayload")
.HasColumnType("text");
b.Property<string>("SerializedProperties")
.HasColumnType("text");
b.Property<DateTimeOffset>("StartedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityTypeVersion");
b.HasIndex("CompletedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_CompletedAt");
b.HasIndex("HasBookmarks")
.HasDatabaseName("IX_ActivityExecutionRecord_HasBookmarks");
b.HasIndex("StartedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_StartedAt");
b.HasIndex("Status")
.HasDatabaseName("IX_ActivityExecutionRecord_Status");
b.HasIndex("TenantId")
.HasDatabaseName("IX_ActivityExecutionRecord_TenantId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_ActivityExecutionRecord_WorkflowInstanceId");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType_ActivityTypeVersion");
b.ToTable("ActivityExecutionRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.BookmarkQueueItem", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActivityInstanceId")
.HasColumnType("text");
b.Property<string>("ActivityTypeName")
.HasColumnType("text");
b.Property<string>("BookmarkId")
.HasColumnType("text");
b.Property<string>("CorrelationId")
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("SerializedOptions")
.HasColumnType("text");
b.Property<string>("StimulusHash")
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_BookmarkQueueItem_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_BookmarkQueueItem_ActivityTypeName");
b.HasIndex(new[] { "BookmarkId" }, "IX_BookmarkQueueItem_BookmarkId");
b.HasIndex(new[] { "CorrelationId" }, "IX_BookmarkQueueItem_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_BookmarkQueueItem_CreatedAt");
b.HasIndex(new[] { "StimulusHash" }, "IX_BookmarkQueueItem_StimulusHash");
b.HasIndex(new[] { "TenantId" }, "IX_BookmarkQueueItem_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_BookmarkQueueItem_WorkflowInstanceId");
b.ToTable("BookmarkQueueItems", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredBookmark", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActivityInstanceId")
.HasColumnType("text");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("CorrelationId")
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("SerializedMetadata")
.HasColumnType("text");
b.Property<string>("SerializedPayload")
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_StoredBookmark_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_StoredBookmark_ActivityTypeName");
b.HasIndex(new[] { "ActivityTypeName", "Hash" }, "IX_StoredBookmark_ActivityTypeName_Hash");
b.HasIndex(new[] { "ActivityTypeName", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_ActivityTypeName_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "CreatedAt" }, "IX_StoredBookmark_CreatedAt");
b.HasIndex(new[] { "Hash" }, "IX_StoredBookmark_Hash");
b.HasIndex(new[] { "Name" }, "IX_StoredBookmark_Name");
b.HasIndex(new[] { "Name", "Hash" }, "IX_StoredBookmark_Name_Hash");
b.HasIndex(new[] { "Name", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_Name_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "TenantId" }, "IX_StoredBookmark_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_StoredBookmark_WorkflowInstanceId");
b.ToTable("Bookmarks", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredTrigger", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Hash")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("SerializedPayload")
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Hash")
.HasDatabaseName("IX_StoredTrigger_Hash");
b.HasIndex("Name")
.HasDatabaseName("IX_StoredTrigger_Name");
b.HasIndex("TenantId")
.HasDatabaseName("IX_StoredTrigger_TenantId");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId");
b.ToTable("Triggers", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowExecutionLogRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ActivityInstanceId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ActivityName")
.HasColumnType("text");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("text");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("integer");
b.Property<string>("EventName")
.HasColumnType("text");
b.Property<string>("Message")
.HasColumnType("text");
b.Property<string>("ParentActivityInstanceId")
.HasColumnType("text");
b.Property<long>("Sequence")
.HasColumnType("bigint");
b.Property<string>("SerializedActivityState")
.HasColumnType("text");
b.Property<string>("SerializedPayload")
.HasColumnType("text");
b.Property<string>("Source")
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("text");
b.Property<int>("WorkflowVersion")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityId");
b.HasIndex("ActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityInstanceId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityTypeVersion");
b.HasIndex("EventName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_EventName");
b.HasIndex("ParentActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ParentActivityInstanceId");
b.HasIndex("Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Sequence");
b.HasIndex("TenantId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_TenantId");
b.HasIndex("Timestamp")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowInstanceId");
b.HasIndex("WorkflowVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowVersion");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType_ActivityTypeVersion");
b.HasIndex("Timestamp", "Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp_Sequence");
b.ToTable("WorkflowExecutionLogRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowInboxMessage", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActivityInstanceId")
.HasColumnType("text");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("CorrelationId")
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SerializedBookmarkPayload")
.HasColumnType("text");
b.Property<string>("SerializedInput")
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_WorkflowInboxMessage_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_WorkflowInboxMessage_ActivityTypeName");
b.HasIndex(new[] { "CorrelationId" }, "IX_WorkflowInboxMessage_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_WorkflowInboxMessage_CreatedAt");
b.HasIndex(new[] { "ExpiresAt" }, "IX_WorkflowInboxMessage_ExpiresAt");
b.HasIndex(new[] { "Hash" }, "IX_WorkflowInboxMessage_Hash");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_WorkflowInboxMessage_WorkflowInstanceId");
b.ToTable("WorkflowInboxMessages", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.Persistence.EFCore.PostgreSql.Migrations.Runtime
{
/// <inheritdoc />
public partial class V3_6 : Migration
{
private readonly Elsa.Persistence.EFCore.IElsaDbContextSchema _schema;
/// <inheritdoc />
public V3_6(Elsa.Persistence.EFCore.IElsaDbContextSchema schema)
{
_schema = schema;
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers",
columns: new[] { "WorkflowDefinitionId", "Hash", "ActivityId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers");
}
}
}

View file

@ -18,7 +18,7 @@ namespace Elsa.Persistence.EFCore.PostgreSql.Migrations.Runtime
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@ -314,6 +314,10 @@ namespace Elsa.Persistence.EFCore.PostgreSql.Migrations.Runtime
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId");
b.ToTable("Triggers", "Elsa");
});

View file

@ -0,0 +1,511 @@
// <auto-generated />
using System;
using Elsa.Persistence.EFCore.Modules.Runtime;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.Persistence.EFCore.SqlServer.Migrations.Runtime
{
[DbContext(typeof(RuntimeElsaDbContext))]
[Migration("20251204150326_V3_6")]
partial class V3_6
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("SerializedValue")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex(new[] { "TenantId" }, "IX_SerializedKeyValuePair_TenantId");
b.ToTable("KeyValuePairs", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.ActivityExecutionRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityName")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("int");
b.Property<int>("AggregateFaultCount")
.HasColumnType("int");
b.Property<DateTimeOffset?>("CompletedAt")
.HasColumnType("datetimeoffset");
b.Property<bool>("HasBookmarks")
.HasColumnType("bit");
b.Property<string>("SerializedActivityState")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedActivityStateCompressionAlgorithm")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedException")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedMetadata")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedOutputs")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedPayload")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedProperties")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("StartedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityTypeVersion");
b.HasIndex("CompletedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_CompletedAt");
b.HasIndex("HasBookmarks")
.HasDatabaseName("IX_ActivityExecutionRecord_HasBookmarks");
b.HasIndex("StartedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_StartedAt");
b.HasIndex("Status")
.HasDatabaseName("IX_ActivityExecutionRecord_Status");
b.HasIndex("TenantId")
.HasDatabaseName("IX_ActivityExecutionRecord_TenantId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_ActivityExecutionRecord_WorkflowInstanceId");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType_ActivityTypeVersion");
b.ToTable("ActivityExecutionRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.BookmarkQueueItem", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityTypeName")
.HasColumnType("nvarchar(450)");
b.Property<string>("BookmarkId")
.HasColumnType("nvarchar(450)");
b.Property<string>("CorrelationId")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("SerializedOptions")
.HasColumnType("nvarchar(max)");
b.Property<string>("StimulusHash")
.HasColumnType("nvarchar(450)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_BookmarkQueueItem_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_BookmarkQueueItem_ActivityTypeName");
b.HasIndex(new[] { "BookmarkId" }, "IX_BookmarkQueueItem_BookmarkId");
b.HasIndex(new[] { "CorrelationId" }, "IX_BookmarkQueueItem_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_BookmarkQueueItem_CreatedAt");
b.HasIndex(new[] { "StimulusHash" }, "IX_BookmarkQueueItem_StimulusHash");
b.HasIndex(new[] { "TenantId" }, "IX_BookmarkQueueItem_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_BookmarkQueueItem_WorkflowInstanceId");
b.ToTable("BookmarkQueueItems", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredBookmark", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("CorrelationId")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("SerializedMetadata")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedPayload")
.HasColumnType("nvarchar(max)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_StoredBookmark_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_StoredBookmark_ActivityTypeName");
b.HasIndex(new[] { "ActivityTypeName", "Hash" }, "IX_StoredBookmark_ActivityTypeName_Hash");
b.HasIndex(new[] { "ActivityTypeName", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_ActivityTypeName_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "CreatedAt" }, "IX_StoredBookmark_CreatedAt");
b.HasIndex(new[] { "Hash" }, "IX_StoredBookmark_Hash");
b.HasIndex(new[] { "Name" }, "IX_StoredBookmark_Name");
b.HasIndex(new[] { "Name", "Hash" }, "IX_StoredBookmark_Name_Hash");
b.HasIndex(new[] { "Name", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_Name_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "TenantId" }, "IX_StoredBookmark_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_StoredBookmark_WorkflowInstanceId");
b.ToTable("Bookmarks", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredTrigger", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("Hash")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("SerializedPayload")
.HasColumnType("nvarchar(max)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("Hash")
.HasDatabaseName("IX_StoredTrigger_Hash");
b.HasIndex("Name")
.HasDatabaseName("IX_StoredTrigger_Name");
b.HasIndex("TenantId")
.HasDatabaseName("IX_StoredTrigger_TenantId");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId")
.HasFilter("[Hash] IS NOT NULL");
b.ToTable("Triggers", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowExecutionLogRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityInstanceId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityName")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("int");
b.Property<string>("EventName")
.HasColumnType("nvarchar(450)");
b.Property<string>("Message")
.HasColumnType("nvarchar(max)");
b.Property<string>("ParentActivityInstanceId")
.HasColumnType("nvarchar(450)");
b.Property<long>("Sequence")
.HasColumnType("bigint");
b.Property<string>("SerializedActivityState")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedPayload")
.HasColumnType("nvarchar(max)");
b.Property<string>("Source")
.HasColumnType("nvarchar(max)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetimeoffset");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int>("WorkflowVersion")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityId");
b.HasIndex("ActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityInstanceId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityTypeVersion");
b.HasIndex("EventName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_EventName");
b.HasIndex("ParentActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ParentActivityInstanceId");
b.HasIndex("Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Sequence");
b.HasIndex("TenantId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_TenantId");
b.HasIndex("Timestamp")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowInstanceId");
b.HasIndex("WorkflowVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowVersion");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType_ActivityTypeVersion");
b.HasIndex("Timestamp", "Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp_Sequence");
b.ToTable("WorkflowExecutionLogRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowInboxMessage", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityInstanceId")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("CorrelationId")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("SerializedBookmarkPayload")
.HasColumnType("nvarchar(max)");
b.Property<string>("SerializedInput")
.HasColumnType("nvarchar(max)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(max)");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_WorkflowInboxMessage_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_WorkflowInboxMessage_ActivityTypeName");
b.HasIndex(new[] { "CorrelationId" }, "IX_WorkflowInboxMessage_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_WorkflowInboxMessage_CreatedAt");
b.HasIndex(new[] { "ExpiresAt" }, "IX_WorkflowInboxMessage_ExpiresAt");
b.HasIndex(new[] { "Hash" }, "IX_WorkflowInboxMessage_Hash");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_WorkflowInboxMessage_WorkflowInstanceId");
b.ToTable("WorkflowInboxMessages", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.Persistence.EFCore.SqlServer.Migrations.Runtime
{
/// <inheritdoc />
public partial class V3_6 : Migration
{
private readonly Elsa.Persistence.EFCore.IElsaDbContextSchema _schema;
/// <inheritdoc />
public V3_6(Elsa.Persistence.EFCore.IElsaDbContextSchema schema)
{
_schema = schema;
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ActivityId",
schema: _schema.Schema,
table: "Triggers",
type: "nvarchar(450)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
migrationBuilder.CreateIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers",
columns: new[] { "WorkflowDefinitionId", "Hash", "ActivityId" },
unique: true,
filter: "[Hash] IS NOT NULL");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers");
migrationBuilder.AlterColumn<string>(
name: "ActivityId",
schema: _schema.Schema,
table: "Triggers",
type: "nvarchar(max)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(450)");
}
}
}

View file

@ -18,7 +18,7 @@ namespace Elsa.Persistence.EFCore.SqlServer.Migrations.Runtime
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@ -275,7 +275,7 @@ namespace Elsa.Persistence.EFCore.SqlServer.Migrations.Runtime
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("nvarchar(max)");
.HasColumnType("nvarchar(450)");
b.Property<string>("Hash")
.HasColumnType("nvarchar(450)");
@ -314,6 +314,11 @@ namespace Elsa.Persistence.EFCore.SqlServer.Migrations.Runtime
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId")
.HasFilter("[Hash] IS NOT NULL");
b.ToTable("Triggers", "Elsa");
});

View file

@ -0,0 +1,506 @@
// <auto-generated />
using System;
using Elsa.Persistence.EFCore.Modules.Runtime;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.Persistence.EFCore.Sqlite.Migrations.Runtime
{
[DbContext(typeof(RuntimeElsaDbContext))]
[Migration("20251204150006_V3_6")]
partial class V3_6
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.11");
modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("SerializedValue")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex(new[] { "TenantId" }, "IX_SerializedKeyValuePair_TenantId");
b.ToTable("KeyValuePairs", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.ActivityExecutionRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ActivityName")
.HasColumnType("TEXT");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("INTEGER");
b.Property<int>("AggregateFaultCount")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("CompletedAt")
.HasColumnType("TEXT");
b.Property<bool>("HasBookmarks")
.HasColumnType("INTEGER");
b.Property<string>("SerializedActivityState")
.HasColumnType("TEXT");
b.Property<string>("SerializedActivityStateCompressionAlgorithm")
.HasColumnType("TEXT");
b.Property<string>("SerializedException")
.HasColumnType("TEXT");
b.Property<string>("SerializedMetadata")
.HasColumnType("TEXT");
b.Property<string>("SerializedOutputs")
.HasColumnType("TEXT");
b.Property<string>("SerializedPayload")
.HasColumnType("TEXT");
b.Property<string>("SerializedProperties")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("StartedAt")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityTypeVersion");
b.HasIndex("CompletedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_CompletedAt");
b.HasIndex("HasBookmarks")
.HasDatabaseName("IX_ActivityExecutionRecord_HasBookmarks");
b.HasIndex("StartedAt")
.HasDatabaseName("IX_ActivityExecutionRecord_StartedAt");
b.HasIndex("Status")
.HasDatabaseName("IX_ActivityExecutionRecord_Status");
b.HasIndex("TenantId")
.HasDatabaseName("IX_ActivityExecutionRecord_TenantId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_ActivityExecutionRecord_WorkflowInstanceId");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_ActivityExecutionRecord_ActivityType_ActivityTypeVersion");
b.ToTable("ActivityExecutionRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.BookmarkQueueItem", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActivityInstanceId")
.HasColumnType("TEXT");
b.Property<string>("ActivityTypeName")
.HasColumnType("TEXT");
b.Property<string>("BookmarkId")
.HasColumnType("TEXT");
b.Property<string>("CorrelationId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("SerializedOptions")
.HasColumnType("TEXT");
b.Property<string>("StimulusHash")
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_BookmarkQueueItem_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_BookmarkQueueItem_ActivityTypeName");
b.HasIndex(new[] { "BookmarkId" }, "IX_BookmarkQueueItem_BookmarkId");
b.HasIndex(new[] { "CorrelationId" }, "IX_BookmarkQueueItem_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_BookmarkQueueItem_CreatedAt");
b.HasIndex(new[] { "StimulusHash" }, "IX_BookmarkQueueItem_StimulusHash");
b.HasIndex(new[] { "TenantId" }, "IX_BookmarkQueueItem_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_BookmarkQueueItem_WorkflowInstanceId");
b.ToTable("BookmarkQueueItems", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredBookmark", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActivityInstanceId")
.HasColumnType("TEXT");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CorrelationId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("SerializedMetadata")
.HasColumnType("TEXT");
b.Property<string>("SerializedPayload")
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_StoredBookmark_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_StoredBookmark_ActivityTypeName");
b.HasIndex(new[] { "ActivityTypeName", "Hash" }, "IX_StoredBookmark_ActivityTypeName_Hash");
b.HasIndex(new[] { "ActivityTypeName", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_ActivityTypeName_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "CreatedAt" }, "IX_StoredBookmark_CreatedAt");
b.HasIndex(new[] { "Hash" }, "IX_StoredBookmark_Hash");
b.HasIndex(new[] { "Name" }, "IX_StoredBookmark_Name");
b.HasIndex(new[] { "Name", "Hash" }, "IX_StoredBookmark_Name_Hash");
b.HasIndex(new[] { "Name", "Hash", "WorkflowInstanceId" }, "IX_StoredBookmark_Name_Hash_WorkflowInstanceId");
b.HasIndex(new[] { "TenantId" }, "IX_StoredBookmark_TenantId");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_StoredBookmark_WorkflowInstanceId");
b.ToTable("Bookmarks", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.StoredTrigger", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Hash")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("SerializedPayload")
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Hash")
.HasDatabaseName("IX_StoredTrigger_Hash");
b.HasIndex("Name")
.HasDatabaseName("IX_StoredTrigger_Name");
b.HasIndex("TenantId")
.HasDatabaseName("IX_StoredTrigger_TenantId");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId");
b.ToTable("Triggers", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowExecutionLogRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActivityId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ActivityInstanceId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ActivityName")
.HasColumnType("TEXT");
b.Property<string>("ActivityNodeId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ActivityType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ActivityTypeVersion")
.HasColumnType("INTEGER");
b.Property<string>("EventName")
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<string>("ParentActivityInstanceId")
.HasColumnType("TEXT");
b.Property<long>("Sequence")
.HasColumnType("INTEGER");
b.Property<string>("SerializedActivityState")
.HasColumnType("TEXT");
b.Property<string>("SerializedPayload")
.HasColumnType("TEXT");
b.Property<string>("Source")
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("TEXT");
b.Property<string>("WorkflowDefinitionId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("WorkflowDefinitionVersionId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("WorkflowInstanceId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("WorkflowVersion")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ActivityId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityId");
b.HasIndex("ActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityInstanceId");
b.HasIndex("ActivityName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityName");
b.HasIndex("ActivityNodeId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityNodeId");
b.HasIndex("ActivityType")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType");
b.HasIndex("ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityTypeVersion");
b.HasIndex("EventName")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_EventName");
b.HasIndex("ParentActivityInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ParentActivityInstanceId");
b.HasIndex("Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Sequence");
b.HasIndex("TenantId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_TenantId");
b.HasIndex("Timestamp")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp");
b.HasIndex("WorkflowDefinitionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionId");
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowInstanceId")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowInstanceId");
b.HasIndex("WorkflowVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowVersion");
b.HasIndex("ActivityType", "ActivityTypeVersion")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_ActivityType_ActivityTypeVersion");
b.HasIndex("Timestamp", "Sequence")
.HasDatabaseName("IX_WorkflowExecutionLogRecord_Timestamp_Sequence");
b.ToTable("WorkflowExecutionLogRecords", "Elsa");
});
modelBuilder.Entity("Elsa.Workflows.Runtime.Entities.WorkflowInboxMessage", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActivityInstanceId")
.HasColumnType("TEXT");
b.Property<string>("ActivityTypeName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CorrelationId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SerializedBookmarkPayload")
.HasColumnType("TEXT");
b.Property<string>("SerializedInput")
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("WorkflowInstanceId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex(new[] { "ActivityInstanceId" }, "IX_WorkflowInboxMessage_ActivityInstanceId");
b.HasIndex(new[] { "ActivityTypeName" }, "IX_WorkflowInboxMessage_ActivityTypeName");
b.HasIndex(new[] { "CorrelationId" }, "IX_WorkflowInboxMessage_CorrelationId");
b.HasIndex(new[] { "CreatedAt" }, "IX_WorkflowInboxMessage_CreatedAt");
b.HasIndex(new[] { "ExpiresAt" }, "IX_WorkflowInboxMessage_ExpiresAt");
b.HasIndex(new[] { "Hash" }, "IX_WorkflowInboxMessage_Hash");
b.HasIndex(new[] { "WorkflowInstanceId" }, "IX_WorkflowInboxMessage_WorkflowInstanceId");
b.ToTable("WorkflowInboxMessages", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.Persistence.EFCore.Sqlite.Migrations.Runtime
{
/// <inheritdoc />
public partial class V3_6 : Migration
{
private readonly Elsa.Persistence.EFCore.IElsaDbContextSchema _schema;
/// <inheritdoc />
public V3_6(Elsa.Persistence.EFCore.IElsaDbContextSchema schema)
{
_schema = schema;
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers",
columns: new[] { "WorkflowDefinitionId", "Hash", "ActivityId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId",
schema: _schema.Schema,
table: "Triggers");
}
}
}

View file

@ -17,7 +17,7 @@ namespace Elsa.Persistence.EFCore.Sqlite.Migrations.Runtime
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.5");
.HasAnnotation("ProductVersion", "9.0.11");
modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b =>
{
@ -310,6 +310,10 @@ namespace Elsa.Persistence.EFCore.Sqlite.Migrations.Runtime
b.HasIndex("WorkflowDefinitionVersionId")
.HasDatabaseName("IX_StoredTrigger_WorkflowDefinitionVersionId");
b.HasIndex("WorkflowDefinitionId", "Hash", "ActivityId")
.IsUnique()
.HasDatabaseName("IX_StoredTrigger_Unique_WorkflowDefinitionId_Hash_ActivityId");
b.ToTable("Triggers", "Elsa");
});

View file

@ -123,6 +123,17 @@ public class Configurations :
builder.HasIndex(x => x.Name).HasDatabaseName($"IX_{nameof(StoredTrigger)}_{nameof(StoredTrigger.Name)}");
builder.HasIndex(x => x.Hash).HasDatabaseName($"IX_{nameof(StoredTrigger)}_{nameof(StoredTrigger.Hash)}");
builder.HasIndex(x => x.TenantId).HasDatabaseName($"IX_{nameof(StoredTrigger)}_{nameof(StoredTrigger.TenantId)}");
// Add unique constraint to prevent duplicate trigger registrations in multi-engine environments
// A trigger is uniquely identified by WorkflowDefinitionId + Hash + ActivityId
builder.HasIndex(x => new
{
x.WorkflowDefinitionId,
x.Hash,
x.ActivityId
})
.IsUnique()
.HasDatabaseName($"IX_{nameof(StoredTrigger)}_Unique_{nameof(StoredTrigger.WorkflowDefinitionId)}_{nameof(StoredTrigger.Hash)}_{nameof(StoredTrigger.ActivityId)}");
}
/// <inheritdoc />

View file

@ -13,7 +13,9 @@ namespace Elsa.Persistence.EFCore.Modules.Runtime;
/// <inheritdoc />
[UsedImplicitly]
public class EFCoreTriggerStore(EntityStore<RuntimeElsaDbContext, StoredTrigger> store, IPayloadSerializer serializer) : ITriggerStore
public class EFCoreTriggerStore(
EntityStore<RuntimeElsaDbContext, StoredTrigger> store,
IPayloadSerializer serializer) : ITriggerStore
{
/// <inheritdoc />
public async ValueTask SaveAsync(StoredTrigger record, CancellationToken cancellationToken = default)

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Define the modules to update
mods=("Management")
mods=("Management", "Runtime")
# Define the list of providers
providers=("MySql" "SqlServer" "Sqlite" "PostgreSql" "Oracle")

View file

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using Elsa.Common.DistributedHosting;
using Elsa.Expressions.Contracts;
using Elsa.Extensions;
using Elsa.Mediator.Contracts;
@ -10,7 +11,9 @@ using Elsa.Workflows.Runtime.Comparers;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Filters;
using Elsa.Workflows.Runtime.Notifications;
using Medallion.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Open.Linq.AsyncExtensions;
namespace Elsa.Workflows.Runtime;
@ -27,6 +30,8 @@ public class TriggerIndexer : ITriggerIndexer
private readonly INotificationSender _notificationSender;
private readonly IServiceProvider _serviceProvider;
private readonly IStimulusHasher _hasher;
private readonly IDistributedLockProvider _distributedLockProvider;
private readonly DistributedLockingOptions _lockingOptions;
private readonly ILogger _logger;
/// <summary>
@ -42,6 +47,8 @@ public class TriggerIndexer : ITriggerIndexer
INotificationSender notificationSender,
IServiceProvider serviceProvider,
IStimulusHasher hasher,
IDistributedLockProvider distributedLockProvider,
IOptions<DistributedLockingOptions> lockingOptions,
ILogger<TriggerIndexer> logger)
{
_activityVisitor = activityVisitor;
@ -52,6 +59,8 @@ public class TriggerIndexer : ITriggerIndexer
_notificationSender = notificationSender;
_serviceProvider = serviceProvider;
_hasher = hasher;
_distributedLockProvider = distributedLockProvider;
_lockingOptions = lockingOptions.Value;
_logger = logger;
_workflowDefinitionService = workflowDefinitionService;
}
@ -89,6 +98,16 @@ public class TriggerIndexer : ITriggerIndexer
/// <inheritdoc />
public async Task<IndexedWorkflowTriggers> IndexTriggersAsync(Workflow workflow, CancellationToken cancellationToken = default)
{
// Use distributed lock to prevent concurrent trigger indexing race conditions
var lockResource = $"trigger-indexer:{workflow.Identity.DefinitionId}";
await using (await _distributedLockProvider.AcquireLockAsync(lockResource, _lockingOptions.LockAcquisitionTimeout, cancellationToken))
{
return await IndexTriggersInternalAsync(workflow, cancellationToken);
}
}
private async Task<IndexedWorkflowTriggers> IndexTriggersInternalAsync(Workflow workflow, CancellationToken cancellationToken)
{
// Get current triggers
var currentTriggers = await GetCurrentTriggersAsync(workflow.Identity.DefinitionId, cancellationToken).ToList();

View file

@ -0,0 +1,236 @@
using Elsa.Http;
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.ConcurrentTriggerIndexing;
/// <summary>
/// Tests for reproducing and preventing duplicate trigger registration in multi-engine scenarios.
/// Simulates the race condition that occurs when multiple engines load and index
/// workflows from blob storage simultaneously.
/// </summary>
public class ConcurrentTriggerIndexingTests(App app) : AppComponentTest(app)
{
[Theory(DisplayName = "Concurrent trigger indexing should not create duplicates")]
[InlineData(10, 0, false, "Synchronized start with 10 operations")]
[InlineData(10, 5, false, "Staggered start with random delays")]
[InlineData(3, 0, true, "Multiple rounds of concurrent indexing")]
public async Task ConcurrentIndexing_ShouldNotCreateDuplicates(
int concurrentOperations,
int maxRandomDelayMs,
bool multipleRounds,
string scenario)
{
// Arrange
var (workflow, workflowDefinition) = await CreateAndSaveTestWorkflowAsync();
// Act
var rounds = multipleRounds ? 3 : 1;
for (var round = 0; round < rounds; round++)
{
await ExecuteConcurrentIndexingAsync(workflowDefinition, concurrentOperations, maxRandomDelayMs);
}
// Assert
await AssertSingleTriggerExistsAsync(workflow.Identity.DefinitionId, scenario);
}
[Fact(DisplayName = "Concurrent workflow refreshes should not create duplicates")]
public async Task ConcurrentWorkflowRefresh_ShouldNotCreateDuplicates()
{
// Arrange
var (workflow, _) = await CreateAndSaveTestWorkflowAsync();
// Act: Simulate concurrent refresh calls from different engines (via API or file watcher)
var refreshTasks = AllPods.Select(pod => Task.Run(async () =>
{
using var scope = pod.Services.CreateScope();
var refresher = scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionsRefresher>();
return await refresher.RefreshWorkflowDefinitionsAsync(new()
{
DefinitionIds = [workflow.Identity.DefinitionId]
}, CancellationToken.None);
})).ToArray();
await Task.WhenAll(refreshTasks);
// Assert
await AssertSingleTriggerExistsAsync(workflow.Identity.DefinitionId);
}
[Fact(DisplayName = "Attempting to create duplicate triggers should fail with unique constraint violation")]
public async Task ManuallyCreatedDuplicates_ShouldViolateUniqueConstraint()
{
// Arrange: This test verifies the database unique constraint protection layer
var (workflow, workflowDefinition) = await CreateAndSaveTestWorkflowAsync();
var triggerStore = GetTriggerStore();
// Index the workflow first
var indexer = Scope.ServiceProvider.GetRequiredService<ITriggerIndexer>();
await indexer.IndexTriggersAsync(workflowDefinition);
// Act & Assert: Attempting to create a duplicate should throw DbUpdateException
var existingTriggers = await GetTriggersAsync(workflow.Identity.DefinitionId);
var duplicateTrigger = CreateDuplicateTrigger(existingTriggers[0]);
var exception = await Assert.ThrowsAsync<DbUpdateException>(async () =>
await triggerStore.SaveAsync(duplicateTrigger));
// Verify it's specifically a unique constraint violation
var message = exception.InnerException?.Message ?? exception.Message;
Assert.True(
message.Contains("duplicate key", StringComparison.OrdinalIgnoreCase) ||
message.Contains("unique constraint", StringComparison.OrdinalIgnoreCase) ||
message.Contains("23505", StringComparison.OrdinalIgnoreCase),
$"Expected unique constraint violation, but got: {message}");
}
private async Task<(Workflow workflow, WorkflowDefinition definition)> CreateAndSaveTestWorkflowAsync()
{
var workflow = CreateTestWorkflow();
var definition = await SaveWorkflowDefinitionAsync(workflow);
return (workflow, definition);
}
private async Task ExecuteConcurrentIndexingAsync(
WorkflowDefinition workflowDefinition,
int concurrentOperations,
int maxRandomDelayMs)
{
var startBarrier = new TaskCompletionSource();
var indexingTasks = Enumerable.Range(0, concurrentOperations)
.Select(i => CreateIndexingTask(workflowDefinition, startBarrier, i, maxRandomDelayMs))
.ToArray();
// Small delay to ensure all tasks are waiting
await Task.Delay(50);
// Release all tasks simultaneously
startBarrier.SetResult();
await Task.WhenAll(indexingTasks);
}
private Task CreateIndexingTask(
WorkflowDefinition workflowDefinition,
TaskCompletionSource startBarrier,
int taskIndex,
int maxRandomDelayMs)
{
return Task.Run(async () =>
{
await startBarrier.Task;
// Add random delay if specified (simulates real-world timing variations)
if (maxRandomDelayMs > 0)
await Task.Delay(Random.Shared.Next(1, maxRandomDelayMs + 1));
var pod = GetPodByIndex(taskIndex);
using var scope = pod.Services.CreateScope();
var indexer = scope.ServiceProvider.GetRequiredService<ITriggerIndexer>();
return await indexer.IndexTriggersAsync(workflowDefinition);
});
}
private WorkflowServer GetPodByIndex(int taskIndex)
{
return (taskIndex % AllPods.Length) switch
{
0 => Cluster.Pod1,
1 => Cluster.Pod2,
_ => Cluster.Pod3
};
}
private WorkflowServer[] AllPods => [Cluster.Pod1, Cluster.Pod2, Cluster.Pod3];
private async Task AssertSingleTriggerExistsAsync(string workflowDefinitionId, string? scenario = null)
{
var triggers = await GetTriggersAsync(workflowDefinitionId);
var message = scenario != null
? $"Expected exactly 1 trigger for scenario: {scenario}, but found {triggers.Count}"
: $"Expected exactly 1 trigger, but found {triggers.Count}";
Assert.True(triggers.Count == 1, message);
}
private async Task<List<StoredTrigger>> GetTriggersAsync(string workflowDefinitionId)
{
var triggerStore = GetTriggerStore();
return (await triggerStore.FindManyAsync(new()
{
WorkflowDefinitionId = workflowDefinitionId
})).ToList();
}
private ITriggerStore GetTriggerStore() => Scope.ServiceProvider.GetRequiredService<ITriggerStore>();
private static Workflow CreateTestWorkflow()
{
var httpEndpoint = new HttpEndpoint
{
Path = new($"/test-concurrent/{Guid.NewGuid()}"),
SupportedMethods = new(["GET"]),
CanStartWorkflow = true
};
var definitionId = Guid.NewGuid().ToString();
var workflowId = Guid.NewGuid().ToString();
return new()
{
Identity = new(definitionId, 1, workflowId),
Root = new Sequence
{
Activities =
{
httpEndpoint
}
},
Publication = new(IsLatest: true, IsPublished: true)
};
}
private async Task<WorkflowDefinition> SaveWorkflowDefinitionAsync(Workflow workflow)
{
var workflowDefinitionStore = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionStore>();
var activitySerializer = Scope.ServiceProvider.GetRequiredService<IActivitySerializer>();
var definition = new WorkflowDefinition
{
Id = workflow.Identity.Id,
DefinitionId = workflow.Identity.DefinitionId,
Version = workflow.Identity.Version,
IsLatest = true,
IsPublished = true,
Name = "Test Concurrent Workflow",
Description = "Workflow for testing concurrent trigger indexing",
MaterializerName = "Json",
StringData = activitySerializer.Serialize(workflow.Root)
};
await workflowDefinitionStore.SaveAsync(definition);
return definition;
}
private static StoredTrigger CreateDuplicateTrigger(StoredTrigger original)
{
return new()
{
Id = Guid.NewGuid().ToString(),
WorkflowDefinitionId = original.WorkflowDefinitionId,
WorkflowDefinitionVersionId = original.WorkflowDefinitionVersionId,
Name = original.Name,
ActivityId = original.ActivityId,
Hash = original.Hash,
Payload = original.Payload
};
}
}

View file

@ -0,0 +1,62 @@
using Elsa.Common.Entities;
using Elsa.Common.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Filters;
using Elsa.Workflows.Runtime.OrderDefinitions;
namespace Elsa.Workflows.ComponentTests.Scenarios.ConcurrentTriggerIndexing;
/// <summary>
/// A decorator for ITriggerStore that adds artificial delays to simulate slow database operations
/// and increase the window for race conditions to occur during testing.
/// </summary>
public class DelayedTriggerStore(ITriggerStore inner, int findDelayMs = 50, int replaceDelayMs = 50) : ITriggerStore
{
public async ValueTask<StoredTrigger?> FindAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
{
await Task.Delay(findDelayMs, cancellationToken);
return await inner.FindAsync(filter, cancellationToken);
}
public async ValueTask<IEnumerable<StoredTrigger>> FindManyAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
{
// Add delay BEFORE reading to maximize race condition window
await Task.Delay(findDelayMs, cancellationToken);
return await inner.FindManyAsync(filter, cancellationToken);
}
public async ValueTask<Page<StoredTrigger>> FindManyAsync(TriggerFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default)
{
await Task.Delay(findDelayMs, cancellationToken);
return await inner.FindManyAsync(filter, pageArgs, cancellationToken);
}
public async ValueTask<Page<StoredTrigger>> FindManyAsync<TProp>(TriggerFilter filter, PageArgs pageArgs, StoredTriggerOrder<TProp> order, CancellationToken cancellationToken = default)
{
await Task.Delay(findDelayMs, cancellationToken);
return await inner.FindManyAsync(filter, pageArgs, order, cancellationToken);
}
public async ValueTask ReplaceAsync(IEnumerable<StoredTrigger> removed, IEnumerable<StoredTrigger> added, CancellationToken cancellationToken = default)
{
// Add delay BEFORE replacing to maximize race condition window
await Task.Delay(replaceDelayMs, cancellationToken);
await inner.ReplaceAsync(removed, added, cancellationToken);
}
public ValueTask SaveAsync(StoredTrigger record, CancellationToken cancellationToken = default)
{
return inner.SaveAsync(record, cancellationToken);
}
public ValueTask SaveManyAsync(IEnumerable<StoredTrigger> records, CancellationToken cancellationToken = default)
{
return inner.SaveManyAsync(records, cancellationToken);
}
public ValueTask<long> DeleteManyAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
{
return inner.DeleteManyAsync(filter, cancellationToken);
}
}

View file

@ -0,0 +1,26 @@
using Elsa.Http;
using Elsa.Workflows.Activities;
using Microsoft.AspNetCore.Http;
namespace Elsa.Workflows.ComponentTests.Scenarios.ConcurrentTriggerIndexing;
public class HttpWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
builder.Root = new Sequence
{
Activities =
[
new HttpEndpoint
{
Path = new($"my-workflow-{Guid.NewGuid()}"),
SupportedMethods = new([HttpMethods.Get]),
CanStartWorkflow = true
}
]
};
}
}

View file

@ -0,0 +1,89 @@
# Concurrent Trigger Indexing Tests
This test suite validates the fix for duplicate trigger registration in multi-engine environments.
GitHub Issue: [7130](https://github.com/elsa-workflows/elsa-core/issues/7130)
## Problem Description
In a multi-engine deployment where workflows are loaded dynamically from blob storage, HTTP endpoint triggers could be registered multiple times due to race conditions, leading to:
- Ambiguous routing errors: `"The call is ambiguous and matches multiple workflows"`
- Duplicate trigger entries in the database
- Unusable HTTP endpoints
## Solution
The fix implements three layers of protection:
1. **Distributed Locking** (`TriggerIndexer.cs`)
- Ensures only one engine can index triggers for a specific workflow at a time
- Lock key: `trigger-indexer:{workflowDefinitionId}`
2. **Database Unique Constraint** (`Configurations.cs`)
- Unique index on `(WorkflowDefinitionId, Hash, ActivityId)`
- Database-level protection against duplicates
3. **Retry Logic with Duplicate Detection** (`TriggerStore.cs`)
- Catches duplicate key violations
- Filters out existing triggers on retry
- Gracefully handles edge cases
## Test Scenarios
### Theory: `ConcurrentIndexing_ShouldNotCreateDuplicates`
Tests three scenarios using a parameterized theory:
- **Synchronized start** (10 operations, no delay): All engines start simultaneously
- **Staggered start** (10 operations, 1-5ms random delays): Simulates real-world timing variations
- **Multiple rounds** (3 operations × 3 rounds): Tests repeated indexing operations
### Fact: `ConcurrentWorkflowRefresh_ShouldNotCreateDuplicates`
Tests concurrent calls to the workflow refresh API from multiple engines, simulating:
- Manual API refresh requests
- File watcher triggered refreshes
- Simultaneous blob storage updates
### Fact: `ManuallyCreatedDuplicates_ShouldHaveSameHash`
Documents the original bug symptom by deliberately creating duplicates to verify they have the same hash, which would cause ambiguous workflow matching.
## Test Architecture
### Helper Methods
- **`CreateAndSaveTestWorkflowAsync()`**: Creates a workflow with an HTTP endpoint trigger
- **`ExecuteConcurrentIndexingAsync()`**: Simulates concurrent trigger indexing with configurable parameters
- **`CreateIndexingTask()`**: Creates a single indexing task with optional delay
- **`GetPodByIndex()`**: Cycles through available pods (Pod1, Pod2, Pod3)
- **`AssertSingleTriggerExistsAsync()`**: Verifies no duplicate triggers exist
### Test Infrastructure
Uses the existing component test infrastructure:
- **App**: Test application fixture with infrastructure setup
- **Cluster**: Multi-pod cluster (Pod1, Pod2, Pod3) simulating multi-engine deployment
- **Infrastructure**: Manages Docker containers (PostgreSQL, RabbitMQ)
## Running the Tests
```bash
# Run all concurrent trigger tests
dotnet test --filter "FullyQualifiedName~ConcurrentTriggerIndexing"
# Run specific test
dotnet test --filter "FullyQualifiedName~ConcurrentIndexing_ShouldNotCreateDuplicates"
```
## Test Validation
These tests:
- **Failed before the fix**: Detecting 2-10 duplicate triggers
- **Pass after the fix**: Asserting exactly 1 trigger exists
- **Prevent regression**: Will fail if the race condition is reintroduced
## Related Files
- `src/modules/Elsa.Workflows.Runtime/Services/TriggerIndexer.cs`
- `src/modules/Elsa.Persistence.EFCore/Modules/Runtime/Configurations.cs`
- `src/modules/Elsa.Persistence.EFCore/Modules/Runtime/TriggerStore.cs`