From fe02011c8b0d1d771a7d1216ed121bc9fc7efe37 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 30 Aug 2023 22:01:25 +0200 Subject: [PATCH] Fix Dapper persistence provider (#4381) --- .../Elsa.WorkflowServer.Web.csproj | 2 + .../Elsa.WorkflowServer.Web/Program.cs | 30 ++++++++++-- .../Elsa.WorkflowServer.Web/appsettings.json | 2 +- .../Management/Initial.cs | 1 + .../Elsa.Dapper.Migrations/Runtime/Initial.cs | 35 +++++++++----- .../Abstractions/SqlDialectBase.cs | 18 +++---- .../Elsa.Dapper/Contracts/ISqlDialect.cs | 8 ++-- .../ParameterizedQueryBuilderExtensions.cs | 44 ++++++++++++----- .../Elsa.Dapper/Features/DapperFeature.cs | 1 + .../Records/WorkflowDefinitionRecord.cs | 1 + .../Services/DapperWorkflowDefinitionStore.cs | 2 + ...DapperWorkflowRuntimePersistenceFeature.cs | 2 + .../WorkflowExecutionLogRecordRecord.cs | 1 + .../Records/WorkflowInboxMessageRecord.cs | 4 +- .../DapperActivityExecutionRecordStore.cs | 9 ++-- .../Stores/DapperWorkflowExecutionLogStore.cs | 2 + .../Stores/DapperWorkflowInboxMessageStore.cs | 10 ++-- .../Services/SqlServerDbConnectionProvider.cs | 47 +++++++++++++++++++ .../Services/SqliteDbConnectionProvider.cs | 20 +++++++- src/modules/Elsa.Dapper/Services/Store.cs | 17 +++++-- .../WorkflowDefinitions/List/Endpoint.cs | 2 +- 21 files changed, 198 insertions(+), 60 deletions(-) create mode 100644 src/modules/Elsa.Dapper/Services/SqlServerDbConnectionProvider.cs diff --git a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj index 3f6bf4503..39c60741a 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj +++ b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/bundles/Elsa.WorkflowServer.Web/Program.cs b/src/bundles/Elsa.WorkflowServer.Web/Program.cs index 80397f625..6e6e7602a 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Program.cs +++ b/src/bundles/Elsa.WorkflowServer.Web/Program.cs @@ -1,5 +1,6 @@ -using System.Text; using System.Text.Encodings.Web; +using Elsa.Dapper.Extensions; +using Elsa.Dapper.Services; using Elsa.EntityFrameworkCore.Extensions; using Elsa.EntityFrameworkCore.Modules.Identity; using Elsa.EntityFrameworkCore.Modules.Management; @@ -11,13 +12,13 @@ using Elsa.MongoDb.Modules.Identity; using Elsa.MongoDb.Modules.Management; using Elsa.MongoDb.Modules.Runtime; using Elsa.WorkflowServer.Web; -using Fluid; using Microsoft.Data.Sqlite; using Proto.Persistence.Sqlite; using Proto.Persistence.SqlServer; const bool useMongoDb = false; const bool useSqlServer = false; +const bool useDapper = true; const bool useProtoActor = true; const bool useHangfire = false; @@ -26,8 +27,8 @@ var services = builder.Services; var configuration = builder.Configuration; var identitySection = configuration.GetSection("Identity"); var identityTokenSection = identitySection.GetSection("Tokens"); -var sqliteConnectionString = configuration.GetConnectionString("Sqlite"); -var sqlServerConnectionString = configuration.GetConnectionString("SqlServer"); +var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!; +var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!; var mongoDbConnectionString = configuration.GetConnectionString("MongoDb")!; // Add Elsa services. @@ -37,6 +38,19 @@ services if(useMongoDb) elsa.UseMongoDb(mongoDbConnectionString); + if(useDapper) + elsa.UseDapper(dapper => + { + dapper.UseMigrations(); + dapper.DbConnectionProvider = sp => + { + if(useSqlServer) + return new SqlServerDbConnectionProvider(sqlServerConnectionString!); + else + return new SqliteDbConnectionProvider(sqliteConnectionString); + }; + }); + if (useHangfire) elsa.UseHangfire(); @@ -49,6 +63,8 @@ services { if(useMongoDb) identity.UseMongoDb(); + else if (useDapper) + identity.UseDapper(); else identity.UseEntityFrameworkCore(ef => { @@ -69,6 +85,8 @@ services { if(useMongoDb) management.UseMongoDb(); + else if (useDapper) + management.UseDapper(); else management.UseEntityFrameworkCore(ef => { @@ -86,6 +104,8 @@ services { if(useMongoDb) runtime.UseMongoDb(); + else if (useDapper) + runtime.UseDapper(); else runtime.UseEntityFrameworkCore(ef => { @@ -118,7 +138,7 @@ services .UseWorkflowsApi(api => api.AddFastEndpointsAssembly()) .UseRealTimeWorkflows() .UseJavaScript(js => js.JintOptions = options => options.AllowClrAccess = true) - .UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = NullEncoder.Default) + .UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = HtmlEncoder.Default) .UseHttp(http => http.HttpEndpointAuthorizationHandler = sp => sp.GetRequiredService()) .UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options)); }); diff --git a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json index a3bbd52f5..87f580513 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json +++ b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json @@ -26,7 +26,7 @@ "Tokens": { "SigningKey": "secret-signing-key", "AccessTokenLifetime": "1:00:00:00", - "RefreshTokenLifetime": "1:00:10:00" + "RefreshTokenLifetime": "7:00:00:00" }, "Roles": [ { diff --git a/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs b/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs index d5c662847..daa5453f3 100644 --- a/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs +++ b/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs @@ -39,6 +39,7 @@ public class Initial : Migration .WithColumn("Id").AsString().PrimaryKey() .WithColumn("DefinitionId").AsString().NotNullable() .WithColumn("Name").AsString().Nullable() + .WithColumn("ToolVersion").AsString().Nullable() .WithColumn("Description").AsString().Nullable() .WithColumn("ProviderName").AsString().Nullable() .WithColumn("MaterializerName").AsString().NotNullable() diff --git a/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs b/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs index 020c6c4e2..c73a84687 100644 --- a/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs +++ b/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs @@ -68,7 +68,10 @@ public class Initial : Migration .WithColumn("Source").AsString().Nullable() .WithColumn("SerializedActivityState").AsString().Nullable() .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("Timestamp").AsDateTimeOffset().NotNullable(); + .WithColumn("SerializedException").AsString().Nullable() + .WithColumn("Timestamp").AsDateTimeOffset().NotNullable() + .WithColumn("Sequence").AsInt64().NotNullable() + ; IfDatabase("Sqlite") .Create @@ -90,8 +93,11 @@ public class Initial : Migration .WithColumn("Source").AsString().Nullable() .WithColumn("SerializedActivityState").AsString().Nullable() .WithColumn("SerializedPayload").AsString().Nullable() + .WithColumn("SerializedOutputs").AsString().Nullable() + .WithColumn("SerializedException").AsString().Nullable() .WithColumn("Timestamp").AsDateTime2().NotNullable() - .WithColumn("Sequence").AsInt64().NotNullable(); + .WithColumn("Sequence").AsInt64().NotNullable() + ; IfDatabase("SqlServer", "Oracle", "MySql", "Postgres") .Create @@ -104,7 +110,7 @@ public class Initial : Migration .WithColumn("ActivityName").AsString().Nullable() .WithColumn("SerializedActivityState").AsString().Nullable() .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("SerializedOutput").AsString().Nullable() + .WithColumn("SerializedOutputs").AsString().Nullable() .WithColumn("SerializedException").AsString().Nullable() .WithColumn("StartedAt").AsDateTimeOffset().NotNullable() .WithColumn("CompletedAt").AsDateTimeOffset().Nullable() @@ -121,7 +127,10 @@ public class Initial : Migration .WithColumn("ActivityType").AsString().NotNullable() .WithColumn("ActivityTypeVersion").AsInt32().NotNullable() .WithColumn("ActivityName").AsString().Nullable() - .WithColumn("ActivityState").AsString().Nullable() + .WithColumn("SerializedActivityState").AsString().Nullable() + .WithColumn("SerializedPayload").AsString().Nullable() + .WithColumn("SerializedOutputs").AsString().Nullable() + .WithColumn("SerializedException").AsString().Nullable() .WithColumn("StartedAt").AsDateTime2().NotNullable() .WithColumn("CompletedAt").AsDateTime2().Nullable() .WithColumn("HasBookmarks").AsBoolean().NotNullable() @@ -133,11 +142,12 @@ public class Initial : Migration .Table("WorkflowInboxMessages") .WithColumn("Id").AsString().PrimaryKey() .WithColumn("ActivityTypeName").AsString().NotNullable() - .WithColumn("WorkflowInstanceId").AsString().NotNullable() + .WithColumn("WorkflowInstanceId").AsString().Nullable() + .WithColumn("ActivityInstanceId").AsString().Nullable() .WithColumn("CorrelationId").AsString().Nullable() - .WithColumn("Hash").AsString().Nullable() - .WithColumn("BookmarkPayload").AsString() - .WithColumn("Input").AsString().Nullable() + .WithColumn("Hash").AsString().NotNullable() + .WithColumn("SerializedBookmarkPayload").AsString() + .WithColumn("SerializedInput").AsString().Nullable() .WithColumn("CreatedAt").AsDateTimeOffset() .WithColumn("ExpiresAt").AsDateTimeOffset() ; @@ -147,11 +157,12 @@ public class Initial : Migration .Table("WorkflowInboxMessages") .WithColumn("Id").AsString().PrimaryKey() .WithColumn("ActivityTypeName").AsString().NotNullable() - .WithColumn("WorkflowInstanceId").AsString().NotNullable() + .WithColumn("WorkflowInstanceId").AsString().Nullable() + .WithColumn("ActivityInstanceId").AsString().Nullable() .WithColumn("CorrelationId").AsString().Nullable() - .WithColumn("Hash").AsString().Nullable() - .WithColumn("BookmarkPayload").AsString() - .WithColumn("Input").AsString().Nullable() + .WithColumn("Hash").AsString().NotNullable() + .WithColumn("SerializedBookmarkPayload").AsString() + .WithColumn("SerializedInput").AsString().Nullable() .WithColumn("CreatedAt").AsDateTime2() .WithColumn("ExpiresAt").AsDateTime2() ; diff --git a/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs b/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs index 480f3c6e1..f95831fa6 100644 --- a/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs +++ b/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs @@ -53,28 +53,28 @@ public abstract class SqlDialectBase : ISqlDialect } /// - public virtual string Skip(int count) => $"OFFSET {count}"; + public virtual string Skip(int count) => $"offset {count}"; /// - public virtual string Take(int count) => $"LIMIT {count}"; + public virtual string Take(int count) => $"limit {count}"; /// - public string Insert(string table, string[] fields) + public string Insert(string table, string[] fields, Func? getParamName = default) { + getParamName ??= x => x; var fieldList = string.Join(", ", fields); - var fieldParamNames = fields.Select(x => $"@{x}"); + var fieldParamNames = fields.Select(x => $"@{getParamName(x)}"); var fieldParamList = string.Join(", ", fieldParamNames); return $"INSERT INTO {table} ({fieldList}) VALUES ({fieldParamList});"; } /// - public virtual string Upsert(string table, string primaryKeyField, string[] fields) + public virtual string Upsert(string table, string primaryKeyField, string[] fields, Func? getParamName = default) { + getParamName ??= x => x; var fieldList = string.Join(", ", fields); - var fieldParamNames = fields.Select(x => $"@{x}"); + var fieldParamNames = fields.Select(x => $"@{getParamName(x)}"); var fieldParamList = string.Join(", ", fieldParamNames); - //var updateList = string.Join(", ", fields.Select(x => $"{x} = @{x}")); - //return $"insert into {table} ({fieldList}) values ({fieldParamList}) on conflict(id) do update set {updateList}"; - return $"INSERT OR REPLACE INTO {table} ({primaryKeyField}, {fieldList}) VALUES (@{primaryKeyField}, {fieldParamList});"; + return $"INSERT OR REPLACE INTO {table} ({primaryKeyField}, {fieldList}) VALUES (@{getParamName(primaryKeyField)}, {fieldParamList});"; } } \ No newline at end of file diff --git a/src/modules/Elsa.Dapper/Contracts/ISqlDialect.cs b/src/modules/Elsa.Dapper/Contracts/ISqlDialect.cs index 421cf6405..505451383 100644 --- a/src/modules/Elsa.Dapper/Contracts/ISqlDialect.cs +++ b/src/modules/Elsa.Dapper/Contracts/ISqlDialect.cs @@ -101,15 +101,17 @@ public interface ISqlDialect /// /// The table. /// The fields. + /// An optional function to get the parameter name. /// The query. - string Insert(string table, string[] fields); - + string Insert(string table, string[] fields, Func? getParamName = default); + /// /// Builds an UPSERT query. /// /// The table. /// The primary key field. /// The fields. + /// An optional function to get the parameter name. /// The query. - string Upsert(string table, string primaryKeyField, string[] fields); + string Upsert(string table, string primaryKeyField, string[] fields, Func? getParamName = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs index 72a6d22c1..faebf3de5 100644 --- a/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs +++ b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs @@ -70,6 +70,21 @@ public static class ParameterizedQueryBuilderExtensions return query; } + /// + /// Begins a DELETE FROM query. + /// + /// The query. + /// The table. + /// The inner query. + public static ParameterizedQuery Delete(this ParameterizedQuery query, string table, ParameterizedQuery innerQuery) + { + query.Sql.AppendLine(query.Dialect.Delete(table)); + query.Sql.AppendLine("and rowid in ("); + query.Sql.AppendLine(innerQuery.Sql.ToString()); + query.Sql.AppendLine(")"); + return query; + } + /// /// Begins a SELECT COUNT(*) FROM query. /// @@ -122,7 +137,7 @@ public static class ParameterizedQueryBuilderExtensions return query; } - + /// /// Appends an IS NULL clause to the query. /// @@ -133,7 +148,7 @@ public static class ParameterizedQueryBuilderExtensions query.Sql.AppendLine(query.Dialect.IsNull(field)); return query; } - + /// /// Appends an IS NOT NULL clause to the query. /// @@ -262,7 +277,7 @@ public static class ParameterizedQueryBuilderExtensions { if (!orderFields.Any()) return query; - + var clauses = string.Join(",", orderFields.Select(x => $"{x.Field} {(x.Direction == OrderDirection.Ascending ? "asc" : "desc")}")); query.Sql.AppendLine($"order by {clauses}"); return query; @@ -314,22 +329,25 @@ public static class ParameterizedQueryBuilderExtensions /// The table. /// The primary key field. /// The record. - public static ParameterizedQuery Upsert(this ParameterizedQuery query, string table, string primaryKeyField, object record) + /// An optional function to get the parameter name. + public static ParameterizedQuery Upsert(this ParameterizedQuery query, string table, string primaryKeyField, object record, Func? getParameterName = default) { var fields = record.GetType().GetProperties() .Where(x => x.CanRead && x.Name != primaryKeyField) .Select(x => x.Name) .ToArray(); - query.Sql.AppendLine(query.Dialect.Upsert(table, primaryKeyField, fields)); - + getParameterName ??= x => x; + + query.Sql.AppendLine(query.Dialect.Upsert(table, primaryKeyField, fields, getParameterName)); + var primaryKeyValue = record.GetType().GetProperty(primaryKeyField)?.GetValue(record); - query.Parameters.Add($"@{primaryKeyField}", primaryKeyValue); - + query.Parameters.Add($"@{getParameterName(primaryKeyField)}", primaryKeyValue); + foreach (var field in fields) { var value = record.GetType().GetProperty(field)?.GetValue(record); - query.Parameters.Add($"@{field}", value); + query.Parameters.Add($"@{getParameterName(field)}", value); } return query; @@ -341,18 +359,20 @@ public static class ParameterizedQueryBuilderExtensions /// The query. /// The table. /// The record. - public static ParameterizedQuery Insert(this ParameterizedQuery query, string table, object record) + /// An optional function to get the parameter name. + public static ParameterizedQuery Insert(this ParameterizedQuery query, string table, object record, Func? getParameterName = default) { var fields = record.GetType().GetProperties() .Select(x => x.Name) .ToArray(); - query.Sql.AppendLine(query.Dialect.Insert(table, fields)); + getParameterName ??= x => x; + query.Sql.AppendLine(query.Dialect.Insert(table, fields, getParameterName)); foreach (var field in fields) { var value = record.GetType().GetProperty(field)?.GetValue(record); - query.Parameters.Add($"@{field}", value); + query.Parameters.Add($"@{getParameterName(field)}", value); } return query; diff --git a/src/modules/Elsa.Dapper/Features/DapperFeature.cs b/src/modules/Elsa.Dapper/Features/DapperFeature.cs index cfcc3a1f2..90194cdd5 100644 --- a/src/modules/Elsa.Dapper/Features/DapperFeature.cs +++ b/src/modules/Elsa.Dapper/Features/DapperFeature.cs @@ -27,6 +27,7 @@ public class DapperFeature : FeatureBase /// public Func DbConnectionProvider { get; set; } = _ => new SqliteDbConnectionProvider(); + /// public override void Apply() { Services.AddSingleton(DbConnectionProvider); diff --git a/src/modules/Elsa.Dapper/Modules/Management/Records/WorkflowDefinitionRecord.cs b/src/modules/Elsa.Dapper/Modules/Management/Records/WorkflowDefinitionRecord.cs index d1cd75e5d..fff93be31 100644 --- a/src/modules/Elsa.Dapper/Modules/Management/Records/WorkflowDefinitionRecord.cs +++ b/src/modules/Elsa.Dapper/Modules/Management/Records/WorkflowDefinitionRecord.cs @@ -18,4 +18,5 @@ internal class WorkflowDefinitionRecord public int Version { get; set; } = 1; public bool IsLatest { get; set; } public bool IsPublished { get; set; } + public bool IsReadonly { get; set; } = false; } \ No newline at end of file diff --git a/src/modules/Elsa.Dapper/Modules/Management/Services/DapperWorkflowDefinitionStore.cs b/src/modules/Elsa.Dapper/Modules/Management/Services/DapperWorkflowDefinitionStore.cs index b5f6501d0..d1bbefb38 100644 --- a/src/modules/Elsa.Dapper/Modules/Management/Services/DapperWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Management/Services/DapperWorkflowDefinitionStore.cs @@ -195,6 +195,7 @@ public class DapperWorkflowDefinitionStore : IWorkflowDefinitionStore Description = source.Description, IsPublished = source.IsPublished, IsLatest = source.IsLatest, + IsReadonly = source.IsReadonly, CreatedAt = source.CreatedAt, StringData = source.StringData, Options = props.Options, @@ -232,6 +233,7 @@ public class DapperWorkflowDefinitionStore : IWorkflowDefinitionStore IsPublished = source.IsPublished, IsLatest = source.IsLatest, CreatedAt = source.CreatedAt, + IsReadonly = source.IsReadonly, StringData = source.StringData, Props = _payloadSerializer.Serialize(props), MaterializerName = source.MaterializerName, diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Features/DapperWorkflowRuntimePersistenceFeature.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Features/DapperWorkflowRuntimePersistenceFeature.cs index f4a2a98c9..e95c6e173 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Features/DapperWorkflowRuntimePersistenceFeature.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Features/DapperWorkflowRuntimePersistenceFeature.cs @@ -1,8 +1,10 @@ using Elsa.Dapper.Features; using Elsa.Dapper.Modules.Runtime.Stores; +using Elsa.Dapper.Services; using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; +using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Features; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowExecutionLogRecordRecord.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowExecutionLogRecordRecord.cs index e453deb79..2e2805b2b 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowExecutionLogRecordRecord.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowExecutionLogRecordRecord.cs @@ -15,6 +15,7 @@ internal class WorkflowExecutionLogRecordRecord public string? ActivityName { get; set; } = default!; public string NodeId { get; set; } = default!; public DateTimeOffset Timestamp { get; set; } + public long Sequence { get; set; } public string? EventName { get; set; } public string? Message { get; set; } public string? Source { get; set; } diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowInboxMessageRecord.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowInboxMessageRecord.cs index ca57ab3ee..c6eb5c3c3 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowInboxMessageRecord.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowInboxMessageRecord.cs @@ -18,7 +18,7 @@ public class WorkflowInboxMessageRecord /// /// An optional bookmark payload that can be used to filter the workflow instances to deliver the message to. /// - public string BookmarkPayload { get; set; } = default!; + public string SerializedBookmarkPayload { get; set; } = default!; /// /// The hash of the bookmark. @@ -43,7 +43,7 @@ public class WorkflowInboxMessageRecord /// /// An optional set of inputs to deliver to the workflow instance. /// - public string? Input { get; set; } + public string? SerializedInput { get; set; } /// /// The date and time the message was created. diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs index cbddfdc1a..0ce41e597 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs @@ -1,3 +1,4 @@ +using Elsa.Dapper.Contracts; using Elsa.Dapper.Extensions; using Elsa.Dapper.Models; using Elsa.Dapper.Modules.Runtime.Records; @@ -18,7 +19,7 @@ namespace Elsa.Dapper.Modules.Runtime.Stores; /// public class DapperActivityExecutionRecordStore : IActivityExecutionStore { - private const string TableName = "WorkflowExecutionLogRecords"; + private const string TableName = "ActivityExecutionRecords"; private const string PrimaryKeyName = "Id"; private readonly IPayloadSerializer _payloadSerializer; private readonly ISafeSerializer _safeSerializer; @@ -27,11 +28,11 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore /// /// Initializes a new instance of the class. /// - public DapperActivityExecutionRecordStore(IPayloadSerializer payloadSerializer, ISafeSerializer safeSerializer, Store store) + public DapperActivityExecutionRecordStore(IDbConnectionProvider dbConnectionProvider, IPayloadSerializer payloadSerializer, ISafeSerializer safeSerializer) { _payloadSerializer = payloadSerializer; _safeSerializer = safeSerializer; - _store = store; + _store = new Store(dbConnectionProvider, TableName, PrimaryKeyName); } /// @@ -83,7 +84,7 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore if (filter.Completed != null) { - if(filter.Completed == true) + if (filter.Completed == true) query.IsNotNull(nameof(ActivityExecutionRecordRecord.CompletedAt)); else query.IsNull(nameof(ActivityExecutionRecordRecord.CompletedAt)); diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowExecutionLogStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowExecutionLogStore.cs index 1628d6edb..fa4c89527 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowExecutionLogStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowExecutionLogStore.cs @@ -121,6 +121,7 @@ public class DapperWorkflowExecutionLogStore : IWorkflowExecutionLogStore ActivityName = source.ActivityName, NodeId = source.NodeId, Timestamp = source.Timestamp, + Sequence = source.Sequence, EventName = source.EventName, Message = source.Message, Source = source.Source, @@ -146,6 +147,7 @@ public class DapperWorkflowExecutionLogStore : IWorkflowExecutionLogStore ActivityName = source.ActivityName, NodeId = source.NodeId, Timestamp = source.Timestamp, + Sequence = source.Sequence, EventName = source.EventName, Message = source.Message, Source = source.Source, diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxMessageStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxMessageStore.cs index 9a828c1d7..92d83f634 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxMessageStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxMessageStore.cs @@ -83,7 +83,7 @@ public class DapperWorkflowInboxMessageStore : IWorkflowInboxMessageStore clauses.Add(clause); } - var clausesSql = string.Join(" OR ", $"({clauses.Select(x => x.Sql)})"); + var clausesSql = string.Join(" OR ", clauses.Select(x => x.Sql.ToString())); query.Sql.AppendLine(clausesSql); } @@ -99,8 +99,8 @@ public class DapperWorkflowInboxMessageStore : IWorkflowInboxMessageStore CorrelationId = source.CorrelationId, ActivityInstanceId = source.ActivityInstanceId, Hash = source.Hash, - BookmarkPayload = _payloadSerializer.Serialize(source.BookmarkPayload), - Input = source.Input != null ? _payloadSerializer.Serialize(source.Input) : default, + SerializedBookmarkPayload = _payloadSerializer.Serialize(source.BookmarkPayload), + SerializedInput = source.Input != null ? _payloadSerializer.Serialize(source.Input) : default, CreatedAt = source.CreatedAt, ExpiresAt = source.ExpiresAt, }; @@ -116,8 +116,8 @@ public class DapperWorkflowInboxMessageStore : IWorkflowInboxMessageStore CorrelationId = source.CorrelationId, ActivityInstanceId = source.ActivityInstanceId, Hash = source.Hash, - BookmarkPayload = _payloadSerializer.Deserialize(source.BookmarkPayload), - Input = source.Input != null ? _payloadSerializer.Deserialize>(source.Input) : default, + BookmarkPayload = _payloadSerializer.Deserialize(source.SerializedBookmarkPayload), + Input = source.SerializedInput != null ? _payloadSerializer.Deserialize>(source.SerializedInput) : default, CreatedAt = source.CreatedAt, ExpiresAt = source.ExpiresAt, }; diff --git a/src/modules/Elsa.Dapper/Services/SqlServerDbConnectionProvider.cs b/src/modules/Elsa.Dapper/Services/SqlServerDbConnectionProvider.cs new file mode 100644 index 000000000..47b8fc7e9 --- /dev/null +++ b/src/modules/Elsa.Dapper/Services/SqlServerDbConnectionProvider.cs @@ -0,0 +1,47 @@ +using System.Data; +using Elsa.Dapper.Contracts; +using Elsa.Dapper.Dialects; +using JetBrains.Annotations; +using Microsoft.Data.SqlClient; + +namespace Elsa.Dapper.Services; + +/// +/// Provides a SQLite connection to the database. +/// +[PublicAPI] +public class SqlServerDbConnectionProvider : IDbConnectionProvider +{ + private readonly string _connectionString = "Server=localhost;Database=Elsa;Trusted_Connection=True;"; + + /// + /// Initializes a new instance of the class. + /// + public SqlServerDbConnectionProvider() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The connection string to use. + public SqlServerDbConnectionProvider(string connectionString) + { + _connectionString = connectionString; + } + + /// + public string GetConnectionString() =>_connectionString; + + /// + public IDbConnection GetConnection() + { + return new SqlConnection + { + ConnectionString = GetConnectionString() + }; + } + + /// + public ISqlDialect Dialect => new SqliteDialect(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs b/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs index b38dcc88e..61dfb5fce 100644 --- a/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs +++ b/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs @@ -12,8 +12,26 @@ namespace Elsa.Dapper.Services; [PublicAPI] public class SqliteDbConnectionProvider : IDbConnectionProvider { + private readonly string _connectionString = "Data Source=elsa.dapper.db"; + + /// + /// Initializes a new instance of the class. + /// + public SqliteDbConnectionProvider() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The connection string to use. + public SqliteDbConnectionProvider(string connectionString) + { + _connectionString = connectionString; + } + /// - public string GetConnectionString() => "Data Source=elsa.dapper.db"; + public string GetConnectionString() =>_connectionString; /// public IDbConnection GetConnection() diff --git a/src/modules/Elsa.Dapper/Services/Store.cs b/src/modules/Elsa.Dapper/Services/Store.cs index e7574309e..e5dc08b9a 100644 --- a/src/modules/Elsa.Dapper/Services/Store.cs +++ b/src/modules/Elsa.Dapper/Services/Store.cs @@ -205,9 +205,14 @@ public class Store where T : notnull public async Task SaveManyAsync(IEnumerable records, string primaryKey = "Id", CancellationToken cancellationToken = default) { var query = new ParameterizedQuery(_dbConnectionProvider.Dialect); + var currentIndex = 0; foreach (var record in records) - query.Upsert(TableName, primaryKey, record); + { + var index = currentIndex; + query.Upsert(TableName, primaryKey, record, field => $"{field}_{index}"); + currentIndex++; + } using var connection = _dbConnectionProvider.GetConnection(); await query.ExecuteAsync(connection); @@ -249,11 +254,13 @@ public class Store where T : notnull /// The number of records deleted. public async Task DeleteAsync(Action filter, PageArgs pageArgs, IEnumerable orderFields, CancellationToken cancellationToken = default) { + var selectQuery = _dbConnectionProvider.CreateQuery().From(TableName, "rowid"); + filter(selectQuery); + selectQuery = selectQuery.OrderBy(orderFields.ToArray()).Page(pageArgs); + + var deleteQuery = _dbConnectionProvider.CreateQuery().Delete(TableName, selectQuery); using var connection = _dbConnectionProvider.GetConnection(); - var query = _dbConnectionProvider.CreateQuery().Delete(TableName); - filter(query); - query = query.OrderBy(orderFields.ToArray()).Page(pageArgs); - return await query.ExecuteAsync(connection); + return await deleteQuery.ExecuteAsync(connection); } /// diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs index 7167c9d2f..9740edc29 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs @@ -70,7 +70,7 @@ internal class List : ElsaEndpoint { - KeySelector = p => p.Name ?? string.Empty, + KeySelector = p => p.Name!, Direction = direction };