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.
///