diff --git a/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs b/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs index 8b96a2c32..e7f18a6d9 100644 --- a/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs +++ b/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs @@ -28,11 +28,6 @@ public class ElasticsearchOptions /// The API key to use when connecting with the Elasticsearch server. /// public string? ApiKey { get; set; } - - /// - /// The interval to attempt a rollover. - /// - public TimeSpan RolloverInterval { get; set; } = TimeSpan.FromDays(10); /// /// A map between type and index name to use. When no index name is configured for a given type, the name of the type is used. diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj index 068431fe6..026ef03e2 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj @@ -11,16 +11,8 @@ elsa module persistence efcore postgresql - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj index 8afb22c1e..786787792 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj @@ -11,20 +11,12 @@ elsa module persistence efcore sqlserver - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj index 1c3519d70..9b2b11ef9 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj @@ -11,19 +11,10 @@ elsa module persistence efcore sqlite - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/modules/Elsa.EntityFrameworkCore/Common/Store.cs b/src/modules/Elsa.EntityFrameworkCore/Common/Store.cs index c9fd2ea82..972621b96 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Common/Store.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Common/Store.cs @@ -1,5 +1,4 @@ using System.Linq.Expressions; -using EFCore.BulkExtensions; using Elsa.Common.Entities; using Elsa.Common.Models; using Elsa.EntityFrameworkCore.Extensions; @@ -17,18 +16,27 @@ public class Store where TDbContext : DbContext where TEnti } public async Task CreateDbContextAsync(CancellationToken cancellationToken = default) => await _dbContextFactory.CreateDbContextAsync(cancellationToken); - public async Task SaveAsync(TEntity entity, CancellationToken cancellationToken = default) => await SaveAsync(entity, default, cancellationToken); + + public async Task SaveAsync(TEntity entity, CancellationToken cancellationToken = default) => await SaveAsync(entity, default, default, cancellationToken); + + public async Task SaveAsync(TEntity entity, Expression>? uniqueField = default, CancellationToken cancellationToken = default) => await SaveAsync(entity, uniqueField, default, cancellationToken); + + public async Task SaveAsync(TEntity entity, Func? onSaving = default, CancellationToken cancellationToken = default) => await SaveAsync(entity, default, onSaving, cancellationToken); - public async Task SaveAsync(TEntity entity, Func? onSaving = default, CancellationToken cancellationToken = default) + public async Task SaveAsync(TEntity entity, Expression>? uniqueField = default, Func? onSaving = default, CancellationToken cancellationToken = default) { await using var dbContext = await CreateDbContextAsync(cancellationToken); entity = onSaving?.Invoke(dbContext, entity) ?? entity; - await dbContext.BulkUpsertAsync(new[] {entity}, cancellationToken); + await dbContext.BulkUpsertAsync(new[] {entity}, uniqueField, cancellationToken); } - public async Task SaveManyAsync(IEnumerable entities, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, default, cancellationToken); - - public async Task SaveManyAsync(IEnumerable entities, Func? onSaving = default, CancellationToken cancellationToken = default) + public async Task SaveManyAsync(IEnumerable entities, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, default, default, cancellationToken); + + public async Task SaveManyAsync(IEnumerable entities, Expression>? uniqueField = default, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, uniqueField, default, cancellationToken); + + public async Task SaveManyAsync(IEnumerable entities, Func? onSaving = default, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, default, onSaving, cancellationToken); + + public async Task SaveManyAsync(IEnumerable entities, Expression>? uniqueField = default, Func? onSaving = default, CancellationToken cancellationToken = default) { await using var dbContext = await CreateDbContextAsync(cancellationToken); var entityList = entities.ToList(); @@ -36,7 +44,7 @@ public class Store where TDbContext : DbContext where TEnti if (onSaving != null) entityList = entityList.Select(x => onSaving(dbContext, x)).ToList(); - await dbContext.BulkUpsertAsync(entityList, cancellationToken); + await dbContext.BulkUpsertAsync(entityList, uniqueField, cancellationToken); } public async Task FindAsync(Expression> predicate, CancellationToken cancellationToken = default) => await FindAsync(predicate, default, cancellationToken); @@ -111,19 +119,11 @@ public class Store where TDbContext : DbContext where TEnti return await dbContext.SaveChangesAsync(cancellationToken) == 1; } - public async Task DeleteManyAsync(IEnumerable entities, CancellationToken cancellationToken = default) - { - await using var dbContext = await CreateDbContextAsync(cancellationToken); - var list = entities.ToList(); - await dbContext.BulkDeleteAsync(list, cancellationToken: cancellationToken); - return list.Count; - } - public async Task DeleteWhereAsync(Expression> predicate, CancellationToken cancellationToken = default) { await using var dbContext = await CreateDbContextAsync(cancellationToken); var set = dbContext.Set(); - return await set.DeleteWhereAsync(dbContext, predicate, cancellationToken); + return await set.Where(predicate).ExecuteDeleteAsync(cancellationToken); } public async Task> QueryAsync(Func, IQueryable> query, CancellationToken cancellationToken = default) => await QueryAsync(query, default, cancellationToken); diff --git a/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj b/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj index 035a7c61b..84c8f8282 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj +++ b/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj @@ -12,30 +12,18 @@ - + - - - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + diff --git a/src/modules/Elsa.EntityFrameworkCore/Extensions/DbSetExtensions.cs b/src/modules/Elsa.EntityFrameworkCore/Extensions/DbSetExtensions.cs deleted file mode 100644 index eabc68985..000000000 --- a/src/modules/Elsa.EntityFrameworkCore/Extensions/DbSetExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; - -namespace Elsa.EntityFrameworkCore.Extensions; - -/// -/// Provides extensions to . -/// -public static class DbSetExtensions -{ - /// - /// Deletes matching results in bulk. - /// - public static async Task DeleteWhereAsync(this DbSet set, DbContext dbContext, Expression> predicate, CancellationToken cancellationToken = default) where T : class => - await set.Where(predicate).BulkDeleteAsync(dbContext, cancellationToken); -} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Extensions/ExpressionExtensions.cs b/src/modules/Elsa.EntityFrameworkCore/Extensions/ExpressionExtensions.cs new file mode 100644 index 000000000..e6e16f442 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Extensions/ExpressionExtensions.cs @@ -0,0 +1,20 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace Elsa.EntityFrameworkCore.Extensions; + +public static class ExpressionExtensions +{ + public static Expression> BuildContainsExpression(this Func uniqueFieldDelegate, IEnumerable entities, PropertyInfo property) where TEntity : class + { + var list = entities.Select(uniqueFieldDelegate.Invoke); + var param = Expression.Parameter(typeof(TEntity)); + var body = Expression.Call( + typeof(Enumerable), + "Contains", + new[] {uniqueFieldDelegate.Method.ReturnType}, + Expression.Constant(list), Expression.Property(param, property)); + + return Expression.Lambda>(body, param); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Extensions/QueryableExtensions.cs b/src/modules/Elsa.EntityFrameworkCore/Extensions/QueryableExtensions.cs index 1d13381d7..654d64154 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Extensions/QueryableExtensions.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Extensions/QueryableExtensions.cs @@ -1,6 +1,7 @@ using System.Linq.Expressions; -using EFCore.BulkExtensions; +using Elsa.Common.Entities; using Elsa.Common.Models; +using Elsa.Extensions; using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore.Extensions; @@ -10,34 +11,29 @@ namespace Elsa.EntityFrameworkCore.Extensions; /// public static class QueryableExtensions { - /// - /// Deletes the matching results in bulk. - /// - public static async Task BulkDeleteAsync(this IQueryable queryable, DbContext elsaContext, CancellationToken cancellationToken = default) where T : class - { -#if NET7_0_OR_GREATER - return await queryable.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); -#else - if (!elsaContext.Database.IsPostgres() && !elsaContext.Database.IsMySql() && !elsaContext.Database.IsOracle()) - return await queryable.BatchDeleteAsync(cancellationToken); - - // Need this workaround https://github.com/borisdj/EFCore.BulkExtensions/issues/553 is solved. - // Oracle also https://github.com/borisdj/EFCore.BulkExtensions/issues/375 - var records = await queryable.ToListAsync(cancellationToken); - - foreach (var record in records) - elsaContext.Remove(record); - - return records.Count; - -#endif - } - /// /// Inserts or updates a list of entities in bulk. /// - public static async Task BulkUpsertAsync(this TDbContext dbContext, IList entities, CancellationToken cancellationToken = default) where TDbContext : DbContext where TEntity : class => - await dbContext.BulkInsertOrUpdateAsync(entities, config => { config.EnableShadowProperties = true; }, cancellationToken: cancellationToken); + public static async Task BulkUpsertAsync(this TDbContext dbContext, IList entities, Expression>? uniqueFieldExpression = default, CancellationToken cancellationToken = default) where TDbContext : DbContext where TEntity : class + { + uniqueFieldExpression = ResolveUniqueFieldExpression(uniqueFieldExpression); + var uniqueFieldDelegate = uniqueFieldExpression.Compile(); + var propertyInfo = uniqueFieldExpression.GetProperty()!; + + var set = dbContext.Set(); + var lambda = uniqueFieldDelegate.BuildContainsExpression(entities, propertyInfo); + + var existingEntities = await set.AsNoTracking().Where(lambda).ToListAsync(cancellationToken); + var entitiesToUpdate = entities.Where(e => existingEntities.Any(ex => uniqueFieldDelegate.Invoke(ex).ToString() == uniqueFieldDelegate.Invoke(e).ToString())).ToList(); + var entitiesToInsert = entities.Except(entitiesToUpdate).ToList(); + + if (entitiesToUpdate.Any()) + set.UpdateRange(entitiesToUpdate); + if (entitiesToInsert.Any()) + await set.AddRangeAsync(entitiesToInsert, cancellationToken); + + await dbContext.SaveChangesAsync(cancellationToken); + } /// /// Returns a paged result from the specified query. @@ -62,4 +58,20 @@ public static class QueryableExtensions var results = await queryable.ToListAsync(); return Page.Of(results, count); } + + private static Expression> ResolveUniqueFieldExpression(Expression>? uniqueFieldExpression) where TEntity : class + { + if (uniqueFieldExpression != null) return uniqueFieldExpression; + try + { + uniqueFieldExpression = e => ((Entity)(object)e).Id; + } + catch (Exception) + { + throw new Exception( + "Unique field expression must be passed via BulkUpsertAsync if default object to upsert is not of type Entity."); + } + + return uniqueFieldExpression; + } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Labels/WorkflowDefinitionLabelStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Labels/WorkflowDefinitionLabelStore.cs index 92e3ed661..663cc6638 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Labels/WorkflowDefinitionLabelStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Labels/WorkflowDefinitionLabelStore.cs @@ -18,7 +18,8 @@ public class EFCoreWorkflowDefinitionLabelStore : IWorkflowDefinitionLabelStore public async Task ReplaceAsync(IEnumerable removed, IEnumerable added, CancellationToken cancellationToken = default) { - await _store.DeleteManyAsync(removed, cancellationToken); + var idList = removed.Select(r => r.Id); + await _store.DeleteWhereAsync(w => idList.Contains(w.Id), cancellationToken); await _store.SaveManyAsync(added, cancellationToken); } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs index bc102cb73..24fbed359 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs @@ -16,14 +16,19 @@ namespace Elsa.EntityFrameworkCore.Modules.Management; public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore { private readonly Store _store; + private readonly Store _workflowInstanceStore; private readonly SerializerOptionsProvider _serializerOptionsProvider; /// /// Constructor. /// - public EFCoreWorkflowDefinitionStore(Store store, SerializerOptionsProvider serializerOptionsProvider) + public EFCoreWorkflowDefinitionStore( + Store store, + Store workflowInstanceStore, + SerializerOptionsProvider serializerOptionsProvider) { _store = store; + _workflowInstanceStore = workflowInstanceStore; _serializerOptionsProvider = serializerOptionsProvider; } @@ -93,16 +98,16 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore public async Task DeleteByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default) { await using var dbContext = await _store.CreateDbContextAsync(cancellationToken); - await dbContext.WorkflowInstances.DeleteWhereAsync(dbContext, x => x.DefinitionId == definitionId, cancellationToken); - return await dbContext.WorkflowDefinitions.DeleteWhereAsync(dbContext, x => x.DefinitionId == definitionId, cancellationToken); + await _workflowInstanceStore.DeleteWhereAsync(x => x.DefinitionId == definitionId, cancellationToken); + return await _store.DeleteWhereAsync(x => x.DefinitionId == definitionId, cancellationToken); } /// public async Task DeleteByDefinitionIdAndVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default) { await using var dbContext = await _store.CreateDbContextAsync(cancellationToken); - await dbContext.WorkflowInstances.DeleteWhereAsync(dbContext, x => x.DefinitionId == definitionId && x.Version == version, cancellationToken); - return await dbContext.WorkflowDefinitions.DeleteWhereAsync(dbContext, x => x.DefinitionId == definitionId && x.Version == version, cancellationToken); + await _workflowInstanceStore.DeleteWhereAsync(x => x.DefinitionId == definitionId && x.Version == version, cancellationToken); + return await _store.DeleteWhereAsync(x => x.DefinitionId == definitionId && x.Version == version, cancellationToken); } /// @@ -110,7 +115,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore { var definitionIdList = definitionIds.ToList(); await using var dbContext = await _store.CreateDbContextAsync(cancellationToken); - await dbContext.WorkflowInstances.DeleteWhereAsync(dbContext, x => definitionIdList.Contains(x.DefinitionId), cancellationToken); + await _workflowInstanceStore.DeleteWhereAsync(x => definitionIdList.Contains(x.DefinitionId), cancellationToken); return await _store.DeleteWhereAsync(x => definitionIdList.Contains(x.DefinitionId), cancellationToken); } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/BookmarkStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/BookmarkStore.cs index 0ee4a28a2..2ea014976 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/BookmarkStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/BookmarkStore.cs @@ -14,7 +14,7 @@ public class EFCoreBookmarkStore : IBookmarkStore public EFCoreBookmarkStore(Store store) => _store = store; /// - public async ValueTask SaveAsync(StoredBookmark record, CancellationToken cancellationToken = default) => await _store.SaveAsync(record, cancellationToken); + public async ValueTask SaveAsync(StoredBookmark record, CancellationToken cancellationToken = default) => await _store.SaveAsync(record, s => s.BookmarkId, cancellationToken); /// public async ValueTask> FindByWorkflowInstanceAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs index 7da1002d1..70b60988d 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs @@ -34,7 +34,7 @@ public class EFCoreTriggerStore : ITriggerStore /// public async ValueTask ReplaceAsync(IEnumerable removed, IEnumerable added, CancellationToken cancellationToken = default) { - await _store.DeleteManyAsync(removed, cancellationToken); + await DeleteManyAsync(removed.Select(r => r.Id), cancellationToken); await _store.SaveManyAsync(added, cancellationToken); } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowStateStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowStateStore.cs index a6b4f0ae1..f8bd26e62 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowStateStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowStateStore.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -using EFCore.BulkExtensions; using Elsa.Common.Services; +using Elsa.EntityFrameworkCore.Common; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.Serialization; using Elsa.Workflows.Core.Serialization.Converters; @@ -17,11 +17,13 @@ public class EFCoreWorkflowStateStore : IWorkflowStateStore private readonly SerializerOptionsProvider _serializerOptionsProvider; private readonly ISystemClock _systemClock; private readonly IDbContextFactory _dbContextFactory; + private readonly Store _store; /// /// Constructor. /// public EFCoreWorkflowStateStore( + Store store, IDbContextFactory dbContextFactory, SerializerOptionsProvider serializerOptionsProvider, ISystemClock systemClock) @@ -29,26 +31,12 @@ public class EFCoreWorkflowStateStore : IWorkflowStateStore _serializerOptionsProvider = serializerOptionsProvider; _systemClock = systemClock; _dbContextFactory = dbContextFactory; + _store = store; } /// - public async ValueTask SaveAsync(string id, WorkflowState state, CancellationToken cancellationToken = default) - { - await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); - var options = _serializerOptionsProvider.CreatePersistenceOptions(ReferenceHandler.Preserve); - var json = JsonSerializer.Serialize(state, options); - var now = _systemClock.UtcNow; - var entry = dbContext.Entry(state); - - if (entry.Property("CreatedAt").CurrentValue == DateTimeOffset.MinValue) - entry.Property("CreatedAt").CurrentValue = now; - - entry.Property("Data").CurrentValue = json; - entry.Property("UpdatedAt").CurrentValue = now; - - var entities = new[] { state }; - await dbContext.BulkInsertOrUpdateAsync(entities, new BulkConfig { EnableShadowProperties = true }, cancellationToken: cancellationToken); - } + public async ValueTask SaveAsync(string id, WorkflowState state, CancellationToken cancellationToken = default) => + await _store.SaveAsync(state, Save, cancellationToken: cancellationToken); /// public async ValueTask LoadAsync(string id, CancellationToken cancellationToken = default) @@ -80,4 +68,21 @@ public class EFCoreWorkflowStateStore : IWorkflowStateStore return await query.CountAsync(cancellationToken); } + + private WorkflowState Save(RuntimeElsaDbContext dbContext, WorkflowState entity) + { + var options = _serializerOptionsProvider.CreatePersistenceOptions(ReferenceHandler.Preserve); + var json = JsonSerializer.Serialize(entity, options); + var now = _systemClock.UtcNow; + var entry = dbContext.Entry(entity); + + if (entry.Property("CreatedAt").CurrentValue == DateTimeOffset.MinValue) + entry.Property("CreatedAt").CurrentValue = now; + + entry.Property("Data").CurrentValue = json; + entry.Property("UpdatedAt").CurrentValue = now; + + dbContext.Entry(entity).Property("Data").CurrentValue = json; + return entity; + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/State/WorkflowState.cs b/src/modules/Elsa.Workflows.Core/State/WorkflowState.cs index a8b7d8d9d..32a919bfb 100644 --- a/src/modules/Elsa.Workflows.Core/State/WorkflowState.cs +++ b/src/modules/Elsa.Workflows.Core/State/WorkflowState.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using Elsa.Common.Entities; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.Serialization.Converters; @@ -7,13 +8,8 @@ namespace Elsa.Workflows.Core.State; /// /// Represents the current state of a workflow. /// -public class WorkflowState +public class WorkflowState : Entity { - /// - /// A unique workflow instance ID. - /// - public string Id { get; set; } = default!; - /// /// The workflow definition ID. ///