Workflow Execution Log (#530)

* Rename provider folder to locking

* Update project reference paths

* Incremental work on workflow execution log storage

* Fix deadlock

* Add workflow execution log stores
This commit is contained in:
Sipke Schoorstra 2020-12-21 17:08:32 +01:00 committed by GitHub
parent ee89331b91
commit 8dbc3fe100
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 308 additions and 249 deletions

View file

@ -63,12 +63,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0A6747
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared", "test\shared\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj", "{E7C689EB-5AD6-4E91-A007-6116EEA702FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.SqlServer", "src\providers\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj", "{150EF356-E8BC-4922-91C2-362017479409}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.Redis", "src\providers\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj", "{EBD49CCA-5400-4C36-B5DF-7B28190AA323}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.AzureBlob", "src\providers\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj", "{E20238CE-5C64-4F87-8753-3B4E1EC9A740}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "locking", "locking", "{7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Core.IntegrationTests", "test\integration\Elsa.Core.IntegrationTests\Elsa.Core.IntegrationTests.csproj", "{4CB34AB3-01C9-4911-8914-EABAA744EAD3}"
@ -177,6 +171,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.Entity", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.EntityChanged", "src\samples\console\Elsa.Samples.EntityChanged\Elsa.Samples.EntityChanged.csproj", "{A3149FE4-FD39-4DC2-AA9F-208C7216BC03}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.AzureBlob", "src\locking\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj", "{C3842132-35BC-4F6D-85CC-7098887F4307}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.Redis", "src\locking\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj", "{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.SqlServer", "src\locking\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj", "{78F2A9DC-3092-4371-ABFE-87C5DA4DE351}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -239,18 +239,6 @@ Global
{E7C689EB-5AD6-4E91-A007-6116EEA702FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7C689EB-5AD6-4E91-A007-6116EEA702FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7C689EB-5AD6-4E91-A007-6116EEA702FC}.Release|Any CPU.Build.0 = Release|Any CPU
{150EF356-E8BC-4922-91C2-362017479409}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{150EF356-E8BC-4922-91C2-362017479409}.Debug|Any CPU.Build.0 = Debug|Any CPU
{150EF356-E8BC-4922-91C2-362017479409}.Release|Any CPU.ActiveCfg = Release|Any CPU
{150EF356-E8BC-4922-91C2-362017479409}.Release|Any CPU.Build.0 = Release|Any CPU
{EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Release|Any CPU.Build.0 = Release|Any CPU
{E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Release|Any CPU.Build.0 = Release|Any CPU
{4CB34AB3-01C9-4911-8914-EABAA744EAD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CB34AB3-01C9-4911-8914-EABAA744EAD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CB34AB3-01C9-4911-8914-EABAA744EAD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -423,6 +411,18 @@ Global
{A3149FE4-FD39-4DC2-AA9F-208C7216BC03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3149FE4-FD39-4DC2-AA9F-208C7216BC03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3149FE4-FD39-4DC2-AA9F-208C7216BC03}.Release|Any CPU.Build.0 = Release|Any CPU
{C3842132-35BC-4F6D-85CC-7098887F4307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3842132-35BC-4F6D-85CC-7098887F4307}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3842132-35BC-4F6D-85CC-7098887F4307}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3842132-35BC-4F6D-85CC-7098887F4307}.Release|Any CPU.Build.0 = Release|Any CPU
{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Release|Any CPU.Build.0 = Release|Any CPU
{78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -449,9 +449,6 @@ Global
{0A3C36AB-2ACF-4175-A512-66B4C3DB9371} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6}
{0A6747EA-7AC6-4B6F-BC0C-C40C8A80C44D} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6}
{E7C689EB-5AD6-4E91-A007-6116EEA702FC} = {0A6747EA-7AC6-4B6F-BC0C-C40C8A80C44D}
{150EF356-E8BC-4922-91C2-362017479409} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
{EBD49CCA-5400-4C36-B5DF-7B28190AA323} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
{E20238CE-5C64-4F87-8753-3B4E1EC9A740} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
{7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{4CB34AB3-01C9-4911-8914-EABAA744EAD3} = {0A3C36AB-2ACF-4175-A512-66B4C3DB9371}
{FC47A50F-03B2-47D8-AAFB-075314FBA186} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6}
@ -505,6 +502,9 @@ Global
{0AA2A003-C79E-4B43-803E-E1150254D2B0} = {E02BD53A-5B08-47A0-920E-DD537A6AFAAA}
{A0A8431F-3A2F-433A-BB0D-EB9627DFA739} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
{A3149FE4-FD39-4DC2-AA9F-208C7216BC03} = {FC9F520F-BA51-4AD2-BFEE-EF787798E734}
{C3842132-35BC-4F6D-85CC-7098887F4307} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
{78F2A9DC-3092-4371-ABFE-87C5DA4DE351} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158}

View file

@ -25,11 +25,11 @@ namespace Elsa.Activities.AzureServiceBus.Services
{
await _semaphore.WaitAsync(cancellationToken);
if (_senders.TryGetValue(queueName, out var messageSender))
return messageSender;
try
{
if (_senders.TryGetValue(queueName, out var messageSender))
return messageSender;
await _managementClient.EnsureQueueExistsAsync(queueName, cancellationToken);
var newMessageSender = new MessageSender(_connection, queueName);
_senders.Add(queueName, newMessageSender);

View file

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Elsa.Activities.AzureServiceBus.Services
{
public class QueueWorker
public class QueueWorker : IAsyncDisposable
{
private readonly IMessageReceiver _messageReceiver;
private readonly IServiceProvider _serviceProvider;
@ -63,5 +63,10 @@ namespace Elsa.Activities.AzureServiceBus.Services
return Task.CompletedTask;
}
public async ValueTask DisposeAsync()
{
await _messageReceiver.CloseAsync();
}
}
}

View file

@ -4,5 +4,6 @@
{
public static string WorkflowDefinitions = "WorkflowDefinitions";
public static string WorkflowInstances = "WorkflowInstances";
public static string WorkflowExecutionLog = "WorkflowExecutionLog";
}
}

View file

@ -1,20 +0,0 @@
using NodaTime;
namespace Elsa.Models
{
public class ExecutionLogEntry
{
public ExecutionLogEntry()
{
}
public ExecutionLogEntry(string activityId, Instant timestamp)
{
ActivityId = activityId;
Timestamp = timestamp;
}
public string ActivityId { get; set; } = default!;
public Instant Timestamp { get; set; }= default!;
}
}

View file

@ -0,0 +1,30 @@
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Models
{
public class WorkflowExecutionLogRecord : Entity, ITenantScope
{
public WorkflowExecutionLogRecord()
{
}
public WorkflowExecutionLogRecord(string id, string? tenantId, string workflowInstanceId, string activityId, Instant timestamp, string? message, JObject? data = default)
{
Id = id;
TenantId = tenantId;
WorkflowInstanceId = workflowInstanceId;
ActivityId = activityId;
Timestamp = timestamp;
Message = message;
Data = data;
}
public string? TenantId { get; }
public string WorkflowInstanceId { get; set; } = default!;
public string ActivityId { get; set; } = default!;
public Instant Timestamp { get; set; }= default!;
public string? Message { get; set; }
public JObject? Data { get; set; }
}
}

View file

@ -0,0 +1,8 @@
using Elsa.Models;
namespace Elsa.Persistence
{
public interface IWorkflowExecutionLogStore : IStore<WorkflowExecutionLogRecord>
{
}
}

View file

@ -70,8 +70,7 @@ namespace Elsa.Services.Models
}
public bool DeleteCompletedInstances => WorkflowBlueprint.DeleteCompletedInstances;
//public ICollection<ExecutionLogEntry> ExecutionLog => WorkflowInstance.ExecutionLog;
public ICollection<ExecutionLogEntry> ExecutionLog => new List<ExecutionLogEntry>();
public ICollection<string> ExecutionLog => new List<string>();
public WorkflowStatus Status => WorkflowInstance.WorkflowStatus;
public bool HasBlockingActivities => WorkflowInstance.BlockingActivities.Any();
public object? WorkflowContext { get; set; }

View file

@ -25,7 +25,7 @@ namespace Elsa.ActivityResults
if (previousEntry == null)
return;
var activityId = previousEntry.ActivityId;
var activityId = previousEntry;
workflowExecutionContext.ScheduleActivity(activityId, _input);
}
}

View file

@ -25,6 +25,7 @@ namespace Elsa
WorkflowDefinitionStoreFactory = sp => ActivatorUtilities.CreateInstance<InMemoryWorkflowDefinitionStore>(sp);
WorkflowInstanceStoreFactory = sp => ActivatorUtilities.CreateInstance<InMemoryWorkflowInstanceStore>(sp);
WorkflowExecutionLogStoreFactory = sp => ActivatorUtilities.CreateInstance<InMemoryWorkflowExecutionLogStore>(sp);
StorageFactory = sp => Storage.Net.StorageFactory.Blobs.InMemory();
DistributedLockProviderFactory = sp => new DefaultLockProvider();
SignalFactory = sp => new Signal();
@ -54,6 +55,7 @@ namespace Elsa
internal Func<IServiceProvider, IBlobStorage> StorageFactory { get; set; }
internal Func<IServiceProvider, IWorkflowDefinitionStore> WorkflowDefinitionStoreFactory { get; set; }
internal Func<IServiceProvider, IWorkflowInstanceStore> WorkflowInstanceStoreFactory { get; set; }
internal Func<IServiceProvider, IWorkflowExecutionLogStore> WorkflowExecutionLogStoreFactory { get; set; }
internal Func<IServiceProvider, IDistributedLockProvider> DistributedLockProviderFactory { get; private set; }
internal Func<IServiceProvider, ISignal> SignalFactory { get; private set; }
internal Func<IServiceProvider, JsonSerializer> CreateJsonSerializer { get; private set; }
@ -92,6 +94,12 @@ namespace Elsa
WorkflowInstanceStoreFactory = factory;
return this;
}
public ElsaOptions UseWorkflowExecutionLogStore(Func<IServiceProvider, IWorkflowExecutionLogStore> factory)
{
WorkflowExecutionLogStoreFactory = factory;
return this;
}
public ElsaOptions UseAutoMapper(Action addAutoMapper)
{

View file

@ -43,6 +43,7 @@ namespace Microsoft.Extensions.DependencyInjection
.AddSingleton(options)
.AddScoped(options.WorkflowDefinitionStoreFactory)
.AddScoped(options.WorkflowInstanceStoreFactory)
.AddScoped(options.WorkflowExecutionLogStoreFactory)
.AddSingleton(options.DistributedLockProviderFactory)
.AddSingleton(options.SignalFactory)
.AddSingleton(options.StorageFactory);

View file

@ -1,21 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Models;
using MediatR;
using NodaTime;
namespace Elsa.Handlers
{
public class AppendExecutionLog : INotificationHandler<ActivityExecuted>
{
private readonly IClock _clock;
public AppendExecutionLog(IClock clock) => _clock = clock;
public Task Handle(ActivityExecuted notification, CancellationToken cancellationToken)
{
notification.WorkflowExecutionContext.ExecutionLog.Add(new ExecutionLogEntry(notification.Activity.Id, _clock.GetCurrentInstant()));
return Task.CompletedTask;
}
}
}

View file

@ -3,7 +3,6 @@ using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Services;
using Elsa.Services.Models;
using MediatR;
using Microsoft.Extensions.Logging;

View file

@ -0,0 +1,51 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Services;
using MediatR;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Handlers
{
public class WriteWorkflowExecutionLog : INotificationHandler<ActivityExecuted>
{
private readonly IWorkflowExecutionLogStore _store;
private readonly IIdGenerator _idGenerator;
private readonly IClock _clock;
private readonly JsonSerializer _jsonSerializer;
public WriteWorkflowExecutionLog(IWorkflowExecutionLogStore store, IIdGenerator idGenerator, IClock clock, JsonSerializer jsonSerializer)
{
_store = store;
_idGenerator = idGenerator;
_clock = clock;
_jsonSerializer = jsonSerializer;
}
public async Task Handle(ActivityExecuted notification, CancellationToken cancellationToken)
{
var workflowInstance = notification.WorkflowExecutionContext.WorkflowInstance;
var id = _idGenerator.Generate();
var tenantId = workflowInstance.TenantId;
var workflowInstanceId = workflowInstance.Id;
var activityId = notification.Activity.Id;
var timeStamp = _clock.GetCurrentInstant();
const string message = "Activity Executed";
var state = new
{
notification.ActivityExecutionContext.Output,
notification.ActivityExecutionContext.Data
};
var logData = JObject.FromObject(state, _jsonSerializer);
var record = new WorkflowExecutionLogRecord(id, tenantId, workflowInstanceId, activityId, timeStamp, message, logData);
await _store.SaveAsync(record, cancellationToken);
}
}
}

View file

@ -1,7 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Services;
@ -12,23 +8,5 @@ namespace Elsa.Persistence.InMemory
public InMemoryWorkflowDefinitionStore(IIdGenerator idGenerator) : base(idGenerator)
{
}
public Task<int> CountAsync(VersionOptions? version = null, CancellationToken cancellationToken = default) => Task.FromResult(Entities.Values.WithVersion(version).Count());
public Task<WorkflowDefinition> GetAsync(string workflowDefinitionId, VersionOptions version, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values.WithVersion(version).FirstOrDefault(x => x.Id == workflowDefinitionId));
public Task<WorkflowDefinition> GetByVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values.FirstOrDefault(x => x.DefinitionVersionId == workflowDefinitionVersionId));
public Task<IEnumerable<WorkflowDefinition>> ListAsync(int? skip = null, int? take = null, VersionOptions? version = null, CancellationToken cancellationToken = default)
{
var query = Entities.Values.WithVersion();
if (skip.HasValue) query = query.Skip(skip.Value);
if (take.HasValue) query = query.Take(take.Value);
return Task.FromResult(query);
}
}
}

View file

@ -0,0 +1,12 @@
using Elsa.Models;
using Elsa.Services;
namespace Elsa.Persistence.InMemory
{
public class InMemoryWorkflowExecutionLogStore : InMemoryStore<WorkflowExecutionLogRecord>, IWorkflowExecutionLogStore
{
public InMemoryWorkflowExecutionLogStore(IIdGenerator idGenerator) : base(idGenerator)
{
}
}
}

View file

@ -1,7 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Services;
@ -12,30 +8,5 @@ namespace Elsa.Persistence.InMemory
public InMemoryWorkflowInstanceStore(IIdGenerator idGenerator) : base(idGenerator)
{
}
public Task<WorkflowInstance?> GetByCorrelationIdAsync(string correlationId, WorkflowStatus status, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values.FirstOrDefault(instance => instance.CorrelationId == correlationId && instance.WorkflowStatus == status))!;
public Task<WorkflowInstance?> GetByIdAsync(string workflowInstanceId, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values
.FirstOrDefault(instance => instance.Id == workflowInstanceId))!;
public Task<IEnumerable<WorkflowInstance>> ListAsync(int page, int pageSize, CancellationToken cancellationToken = default) => Task.FromResult(Entities.Values.Skip(page * pageSize).Take(pageSize));
public Task<IEnumerable<WorkflowInstance>> ListByBlockingActivityTypeAsync(string activityType, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values
.Where(instance => instance.BlockingActivities.Any(a => a.ActivityType == activityType)));
public Task<IEnumerable<WorkflowInstance>> ListByCorrelationIdAsync(string correlationId, WorkflowStatus status, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values.Where(instance => instance.CorrelationId == correlationId && instance.WorkflowStatus == status));
public Task<IEnumerable<WorkflowInstance>> ListByDefinitionAndStatusAsync(string workflowDefinitionId, string tenantId, WorkflowStatus workflowStatus, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values
.Where(instance => instance.DefinitionId == workflowDefinitionId && instance.WorkflowStatus == workflowStatus && instance.TenantId == tenantId));
public Task<IEnumerable<WorkflowInstance>> ListByDefinitionAsync(string workflowDefinitionId, string tenantId, CancellationToken cancellationToken = default) =>
Task.FromResult(Entities.Values.Where(x => x.DefinitionId == workflowDefinitionId && x.TenantId == tenantId));
public Task<IEnumerable<WorkflowInstance>> ListByStatusAsync(WorkflowStatus workflowStatus, CancellationToken cancellationToken = default) => Task.FromResult(Entities.Values.Where(instance => instance.WorkflowStatus == workflowStatus));
}
}

View file

@ -259,6 +259,7 @@ namespace Elsa.Services
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
workflowExecutionContext.Fault(null, new LocalizedString(e.Message, e.Message));
}
}
@ -279,6 +280,7 @@ namespace Elsa.Services
await _mediator.Publish(new ActivityExecuting(activityExecutionContext), cancellationToken);
await result.ExecuteAsync(activityExecutionContext, cancellationToken);
workflowExecutionContext.WorkflowInstance.Output = activityExecutionContext.Output;
workflowExecutionContext.ExecutionLog.Add(activity.Id);
await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken);
activityOperation = Execute;

View file

@ -32,14 +32,17 @@ namespace Elsa.Persistence.MongoDb.Extensions
elsa.Services
.AddSingleton<MongoDbWorkflowDefinitionStore>()
.AddSingleton<MongoDbWorkflowInstanceStore>()
.AddSingleton<MongoDbWorkflowExecutionLogStore>()
.AddSingleton<ElsaMongoDbClient>()
.AddSingleton(sp => sp.GetRequiredService<ElsaMongoDbClient>().WorkflowDefinitions)
.AddSingleton(sp => sp.GetRequiredService<ElsaMongoDbClient>().WorkflowInstances)
.AddSingleton(sp => sp.GetRequiredService<ElsaMongoDbClient>().WorkflowExecutionLog)
.AddStartupTask<DatabaseInitializer>();
elsa
.UseWorkflowDefinitionStore(sp => sp.GetRequiredService<MongoDbWorkflowDefinitionStore>())
.UseWorkflowInstanceStore(sp => sp.GetRequiredService<MongoDbWorkflowInstanceStore>());
.UseWorkflowInstanceStore(sp => sp.GetRequiredService<MongoDbWorkflowInstanceStore>())
.UseWorkflowExecutionLogStore(sp => sp.GetRequiredService<MongoDbWorkflowExecutionLogStore>());
DatabaseRegister.RegisterMapsAndSerializers();
}

View file

@ -20,6 +20,7 @@ namespace Elsa.Persistence.MongoDb.Services
{
await CreateWorkflowInstancesIndexes(cancellationToken);
await CreateWorkflowDefinitionsIndexes(cancellationToken);
await CreateWorkflowExecutionLogIndexes(cancellationToken);
}
private async Task CreateWorkflowInstancesIndexes(CancellationToken cancellationToken)
@ -40,6 +41,15 @@ namespace Elsa.Persistence.MongoDb.Services
await CreateIndexesAsync(_mongoClient.WorkflowDefinitions, cancellationToken, tenantKeysDefinition, definitionVersionIdKeysDefinition, versionKeysDefinition, nameKeysDefinition);
}
private async Task CreateWorkflowExecutionLogIndexes(CancellationToken cancellationToken)
{
var tenantKeysDefinition = Builders<WorkflowExecutionLogRecord>.IndexKeys.Ascending(x => x.TenantId);
var workflowInstanceIdKeysDefinition = Builders<WorkflowExecutionLogRecord>.IndexKeys.Ascending(x => x.WorkflowInstanceId);
var timestampKeysDefinition = Builders<WorkflowExecutionLogRecord>.IndexKeys.Ascending(x => x.Timestamp);
await CreateIndexesAsync(_mongoClient.WorkflowExecutionLog, cancellationToken, tenantKeysDefinition, workflowInstanceIdKeysDefinition, timestampKeysDefinition);
}
private async Task CreateIndexesAsync<T>(IMongoCollection<T> collection, CancellationToken cancellationToken, params IndexKeysDefinition<T>[] definitions)
{

View file

@ -18,5 +18,6 @@ namespace Elsa.Persistence.MongoDb.Services
public IMongoCollection<WorkflowDefinition> WorkflowDefinitions => _mongoDatabase.GetCollection<WorkflowDefinition>(CollectionNames.WorkflowDefinitions);
public IMongoCollection<WorkflowInstance> WorkflowInstances => _mongoDatabase.GetCollection<WorkflowInstance>(CollectionNames.WorkflowInstances);
public IMongoCollection<WorkflowExecutionLogRecord> WorkflowExecutionLog => _mongoDatabase.GetCollection<WorkflowExecutionLogRecord>(CollectionNames.WorkflowExecutionLog);
}
}

View file

@ -0,0 +1,13 @@
using Elsa.Models;
using Elsa.Services;
using MongoDB.Driver;
namespace Elsa.Persistence.MongoDb.Stores
{
public class MongoDbWorkflowExecutionLogStore : MongoDbStore<WorkflowExecutionLogRecord>, IWorkflowExecutionLogStore
{
public MongoDbWorkflowExecutionLogStore(IMongoCollection<WorkflowExecutionLogRecord> collection, IIdGenerator idGenerator) : base(collection, idGenerator)
{
}
}
}

View file

@ -6,7 +6,6 @@ namespace Elsa.Persistence.YesSql.Documents
public class WorkflowDefinitionDocument : YesSqlDocument
{
public string DefinitionId { get; set; } = default!;
public string DefinitionVersionId { get; set; } = default!;
public string? TenantId { get; set; }
public string? Name { get; set; }

View file

@ -0,0 +1,16 @@
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Persistence.YesSql.Documents
{
public class WorkflowExecutionLogRecordDocument : YesSqlDocument
{
public string RecordId { get; } = default!;
public string? TenantId { get; }
public string WorkflowInstanceId { get; set; } = default!;
public string ActivityId { get; set; } = default!;
public Instant Timestamp { get; set; }= default!;
public string? Message { get; set; }
public JObject? Data { get; set; }
}
}

View file

@ -32,7 +32,7 @@ namespace Elsa.Persistence.YesSql.Documents
set => _blockingActivities = new HashSet<BlockingActivity>(value, BlockingActivityEqualityComparer.Instance);
}
public ICollection<ExecutionLogEntry> ExecutionLog { get; set; } = new List<ExecutionLogEntry>();
public ICollection<WorkflowExecutionLogRecord> ExecutionLog { get; set; } = new List<WorkflowExecutionLogRecord>();
public WorkflowFault? Fault { get; set; }
public Stack<ScheduledActivity> ScheduledActivities { get; set; } = new();
public Stack<ScheduledActivity> PostScheduledActivities { get; set; } = new();

View file

@ -3,7 +3,6 @@ using System.Data;
using System.Linq;
using Elsa.Data;
using Elsa.Persistence.YesSql.Data;
using Elsa.Persistence.YesSql.Documents;
using Elsa.Persistence.YesSql.Indexes;
using Elsa.Persistence.YesSql.Mapping;
using Elsa.Persistence.YesSql.Services;
@ -11,7 +10,6 @@ using Elsa.Persistence.YesSql.Stores;
using Elsa.Runtime;
using Microsoft.Extensions.DependencyInjection;
using YesSql;
using YesSql.Data;
using YesSql.Indexes;
using YesSql.Provider.Sqlite;
@ -27,6 +25,7 @@ namespace Elsa.Persistence.YesSql.Extensions
elsa.Services
.AddScoped<YesSqlWorkflowDefinitionStore>()
.AddScoped<YesSqlWorkflowInstanceStore>()
.AddScoped<YesSqlWorkflowExecutionLogStore>()
.AddSingleton(sp => CreateStore(sp, configure))
.AddScoped(CreateSession)
.AddScoped<IDataMigrationManager, DataMigrationManager>()
@ -35,11 +34,13 @@ namespace Elsa.Persistence.YesSql.Extensions
.AddDataMigration<Migrations>()
.AddAutoMapperProfile<AutoMapperProfile>()
.AddIndexProvider<WorkflowDefinitionIndexProvider>()
.AddIndexProvider<WorkflowInstanceIndexProvider>();
.AddIndexProvider<WorkflowInstanceIndexProvider>()
.AddIndexProvider<WorkflowExecutionLogRecordIndexProvider>();
return elsa
.UseWorkflowDefinitionStore(sp => sp.GetRequiredService<YesSqlWorkflowDefinitionStore>())
.UseWorkflowInstanceStore(sp => sp.GetRequiredService<YesSqlWorkflowInstanceStore>());
.UseWorkflowInstanceStore(sp => sp.GetRequiredService<YesSqlWorkflowInstanceStore>())
.UseWorkflowExecutionLogStore(sp => sp.GetRequiredService<YesSqlWorkflowExecutionLogStore>());
}
public static IServiceCollection AddIndexProvider<T>(this IServiceCollection services) where T : class, IIndexProvider => services.AddSingleton<IIndexProvider, T>();

View file

@ -1,5 +1,4 @@
using Elsa.Data;
using Elsa.Models;
using Elsa.Persistence.YesSql.Documents;
using YesSql.Indexes;
@ -8,7 +7,7 @@ namespace Elsa.Persistence.YesSql.Indexes
public class WorkflowDefinitionIndex : MapIndex
{
public string? TenantId { get; set; }
public string EntityId { get; set; } = default!;
public string DefinitionId { get; set; } = default!;
public string DefinitionVersionId { get; set; } = default!;
public int Version { get; set; }
public bool IsLatest { get; set; }
@ -26,7 +25,7 @@ namespace Elsa.Persistence.YesSql.Indexes
.Map(
workflowDefinition => new WorkflowDefinitionIndex
{
EntityId = workflowDefinition.DefinitionId,
DefinitionId = workflowDefinition.DefinitionId,
TenantId = workflowDefinition.TenantId,
DefinitionVersionId = workflowDefinition.DefinitionVersionId,
Version = workflowDefinition.Version,

View file

@ -0,0 +1,34 @@
using System;
using Elsa.Data;
using Elsa.Persistence.YesSql.Documents;
using YesSql.Indexes;
namespace Elsa.Persistence.YesSql.Indexes
{
public class WorkflowExecutionLogRecordIndex : MapIndex
{
public string? TenantId { get; set; }
public string RecordId { get; set; } = default!;
public string WorkflowInstanceId { get; set; } = default!;
public DateTime Timestamp { get; set; }
}
public class WorkflowExecutionLogRecordIndexProvider : IndexProvider<WorkflowExecutionLogRecordDocument>
{
public WorkflowExecutionLogRecordIndexProvider() => CollectionName = CollectionNames.WorkflowExecutionLog;
public override void Describe(DescribeContext<WorkflowExecutionLogRecordDocument> context)
{
context.For<WorkflowExecutionLogRecordIndex>()
.Map(
record => new WorkflowExecutionLogRecordIndex
{
RecordId = record.RecordId,
TenantId = record.TenantId,
WorkflowInstanceId = record.WorkflowInstanceId,
Timestamp = record.Timestamp.ToDateTimeUtc()
}
);
}
}
}

View file

@ -19,6 +19,12 @@ namespace Elsa.Persistence.YesSql.Mapping
.ForMember(d => d.Id, d => d.Ignore())
.ReverseMap()
.ForMember(d => d.Id, d => d.MapFrom(s => s.DefinitionId));
CreateMap<WorkflowExecutionLogRecord, WorkflowExecutionLogRecordDocument>()
.ForMember(d => d.RecordId, d => d.MapFrom(s => s.Id))
.ForMember(d => d.Id, d => d.Ignore())
.ReverseMap()
.ForMember(d => d.Id, d => d.MapFrom(s => s.RecordId));
}
}
}

View file

@ -13,7 +13,7 @@ namespace Elsa.Persistence.YesSql
SchemaBuilder.CreateMapIndexTable<WorkflowDefinitionIndex>(
table => table
.Column<string?>(nameof(WorkflowDefinitionIndex.TenantId))
.Column<string>(nameof(WorkflowDefinitionIndex.EntityId))
.Column<string>(nameof(WorkflowDefinitionIndex.DefinitionId))
.Column<string>(nameof(WorkflowDefinitionIndex.DefinitionVersionId))
.Column<int>(nameof(WorkflowDefinitionIndex.Version))
.Column<bool>(nameof(WorkflowDefinitionIndex.IsLatest))
@ -40,13 +40,21 @@ namespace Elsa.Persistence.YesSql
SchemaBuilder.CreateMapIndexTable<WorkflowInstanceBlockingActivitiesIndex>(
table => table
.Column<string?>("TenantId")
.Column<string>("ActivityId")
.Column<string>("ActivityType")
.Column<string?>("CorrelationId")
.Column("WorkflowStatus", DbType.String)
.Column("CreatedAt", DbType.DateTimeOffset),
.Column<string?>(nameof(WorkflowInstanceBlockingActivitiesIndex.TenantId))
.Column<string>(nameof(WorkflowInstanceBlockingActivitiesIndex.ActivityId))
.Column<string>(nameof(WorkflowInstanceBlockingActivitiesIndex.ActivityType))
.Column<string?>(nameof(WorkflowInstanceBlockingActivitiesIndex.CorrelationId))
.Column(nameof(WorkflowInstanceBlockingActivitiesIndex.WorkflowStatus), DbType.String)
.Column(nameof(WorkflowInstanceBlockingActivitiesIndex.CreatedAt), DbType.DateTimeOffset),
CollectionNames.WorkflowInstances);
SchemaBuilder.CreateMapIndexTable<WorkflowExecutionLogRecordIndex>(
table => table
.Column<string?>(nameof(WorkflowExecutionLogRecordIndex.RecordId))
.Column<string?>(nameof(WorkflowExecutionLogRecordIndex.TenantId))
.Column<string>(nameof(WorkflowExecutionLogRecordIndex.WorkflowInstanceId))
.Column(nameof(WorkflowExecutionLogRecordIndex.Timestamp), DbType.DateTimeOffset),
CollectionNames.WorkflowExecutionLog);
return 1;
}

View file

@ -1,9 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Data;
using Elsa.Services;
using YesSql;
namespace Elsa.Persistence.YesSql.Services
@ -21,6 +19,7 @@ namespace Elsa.Persistence.YesSql.Services
{
await _store.InitializeCollectionAsync(CollectionNames.WorkflowDefinitions);
await _store.InitializeCollectionAsync(CollectionNames.WorkflowInstances);
await _store.InitializeCollectionAsync(CollectionNames.WorkflowExecutionLog);
}
}
}

View file

@ -16,6 +16,8 @@ namespace Elsa.Persistence.YesSql.Stores
{
public abstract class YesSqlStore<T, TDocument> : IStore<T> where T : class, IEntity where TDocument : class
{
private readonly SemaphoreSlim _semaphore = new(1);
public YesSqlStore(ISession session, IIdGenerator idGenerator, IMapper mapper, string collectionName)
{
IdGenerator = idGenerator;
@ -31,10 +33,19 @@ namespace Elsa.Persistence.YesSql.Stores
public async Task SaveAsync(T entity, CancellationToken cancellationToken = default)
{
var existingDocument = await FindDocumentAsync(entity, cancellationToken);
var document = Mapper.Map(entity, existingDocument);
Session.Save(document, CollectionName);
await Session.CommitAsync();
await _semaphore.WaitAsync(cancellationToken);
try
{
var existingDocument = await FindDocumentAsync(entity, cancellationToken);
var document = Mapper.Map(entity, existingDocument);
Session.Save(document, CollectionName);
await Session.CommitAsync();
}
finally
{
_semaphore.Release();
}
}
public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
@ -68,14 +79,14 @@ namespace Elsa.Persistence.YesSql.Stores
var documents = await Query(specification, orderBy, paging, cancellationToken).ListAsync();
return Map(documents);
}
protected virtual IQuery<TDocument> Query(ISpecification<T> specification, IOrderBy<T>? orderBy = default, IPaging? paging = default, CancellationToken cancellationToken = default)
{
var query = ToQuery(specification);
if (orderBy != null)
if (orderBy != null)
query = OrderBy(query, orderBy, specification);
if (paging != null)
query = query.Skip(paging.Skip).Take(paging.Take);

View file

@ -17,12 +17,12 @@ namespace Elsa.Persistence.YesSql.Stores
{
}
protected override async Task<WorkflowDefinitionDocument?> FindDocumentAsync(WorkflowDefinition entity, CancellationToken cancellationToken) => await Query<WorkflowDefinitionIndex>(x => x.EntityId == entity.Id).FirstOrDefaultAsync();
protected override async Task<WorkflowDefinitionDocument?> FindDocumentAsync(WorkflowDefinition entity, CancellationToken cancellationToken) => await Query<WorkflowDefinitionIndex>(x => x.DefinitionId == entity.Id).FirstOrDefaultAsync();
protected override IQuery<WorkflowDefinitionDocument> MapSpecification(ISpecification<WorkflowDefinition> specification)
{
if (specification is EntityIdSpecification<WorkflowDefinition> entityIdSpecification)
return Query<WorkflowDefinitionIndex>(x => x.EntityId == entityIdSpecification.Id);
return Query<WorkflowDefinitionIndex>(x => x.DefinitionId == entityIdSpecification.Id);
return AutoMapSpecification<WorkflowDefinitionIndex>(specification);
}

View file

@ -0,0 +1,23 @@
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Data;
using Elsa.Models;
using Elsa.Persistence.Specifications;
using Elsa.Persistence.YesSql.Documents;
using Elsa.Persistence.YesSql.Indexes;
using YesSql;
using IIdGenerator = Elsa.Services.IIdGenerator;
namespace Elsa.Persistence.YesSql.Stores
{
public class YesSqlWorkflowExecutionLogStore : YesSqlStore<WorkflowExecutionLogRecord, WorkflowExecutionLogRecordDocument>, IWorkflowExecutionLogStore
{
public YesSqlWorkflowExecutionLogStore(ISession session, IIdGenerator idGenerator, IMapper mapper) : base(session, idGenerator, mapper, CollectionNames.WorkflowExecutionLog)
{
}
protected override async Task<WorkflowExecutionLogRecordDocument?> FindDocumentAsync(WorkflowExecutionLogRecord entity, CancellationToken cancellationToken) => await Query<WorkflowExecutionLogRecordIndex>(x => x.RecordId == entity.Id).FirstOrDefaultAsync();
protected override IQuery<WorkflowExecutionLogRecordDocument> MapSpecification(ISpecification<WorkflowExecutionLogRecord> specification) => AutoMapSpecification<WorkflowExecutionLogRecordIndex>(specification);
}
}

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -6,11 +6,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\activities\Elsa.Activities.AzureServiceBus\Elsa.Activities.AzureServiceBus.csproj"/>
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj"/>
<ProjectReference Include="..\..\..\activities\Elsa.Activities.AzureServiceBus\Elsa.Activities.AzureServiceBus.csproj" />
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
<ProjectReference Include="..\..\..\persistence\Elsa.Persistence.YesSql\Elsa.Persistence.YesSql.csproj" />
</ItemGroup>
</Project>

View file

@ -1,5 +1,6 @@
using Elsa.Activities.AzureServiceBus.Extensions;
using Elsa.Persistence.InMemory;
using Elsa.Persistence.YesSql.Extensions;
using Elsa.Samples.AzureServiceBusWorker.Workflows;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -19,7 +20,7 @@ namespace Elsa.Samples.AzureServiceBusWorker
.ConfigureServices((hostContext, services) =>
{
services
.AddElsa()
.AddElsa(options => options.UseYesSqlPersistence())
.AddConsoleActivities()
.AddTimerActivities()
.AddAzureServiceBusActivities(options => options.ConnectionString = hostContext.Configuration.GetConnectionString("AzureServiceBus"))

View file

@ -7,6 +7,6 @@
}
},
"ConnectionStrings": {
"AzureServiceBus": ""
"AzureServiceBus": "Endpoint=sb://elsa-samples.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=MAwkyEFjQdDFX4gI6jM/nvBMwHcEZQ4RHQ2XC6fFB9Q="
}
}

View file

@ -12,10 +12,10 @@
<ItemGroup>
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj"/>
<ProjectReference Include="..\..\..\providers\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj"/>
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj"/>
<ProjectReference Include="..\..\..\providers\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj"/>
<ProjectReference Include="..\..\..\providers\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj"/>
<ProjectReference Include="..\..\..\locking\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj"/>
<ProjectReference Include="..\..\..\locking\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj"/>
<ProjectReference Include="..\..\..\locking\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj"/>
</ItemGroup>
</Project>

View file

@ -16,7 +16,7 @@ namespace Elsa.Server.Api.Models
{
Variables = new Variables();
Activities = new List<ActivityInstance>();
ExecutionLog = new List<ExecutionLogEntry>();
ExecutionLog = new List<WorkflowExecutionLogRecord>();
ScheduledActivities = new Stack<ScheduledActivity>();
PostScheduledActivities = new Stack<ScheduledActivity>();
}
@ -42,7 +42,7 @@ namespace Elsa.Server.Api.Models
set => _blockingActivities = new HashSet<BlockingActivity>(value, BlockingActivityEqualityComparer.Instance);
}
[DataMember(Order = 16)] public ICollection<ExecutionLogEntry> ExecutionLog { get; set; }
[DataMember(Order = 16)] public ICollection<WorkflowExecutionLogRecord> ExecutionLog { get; set; }
[DataMember(Order = 17)] public WorkflowFault? Fault { get; set; }
[DataMember(Order = 18)] public Stack<ScheduledActivity> ScheduledActivities { get; set; }
[DataMember(Order = 19)] public Stack<ScheduledActivity> PostScheduledActivities { get; set; }