diff --git a/src/bundles/Elsa.Server.Web/Program.cs b/src/bundles/Elsa.Server.Web/Program.cs index ec8a8263b..cf1b19952 100644 --- a/src/bundles/Elsa.Server.Web/Program.cs +++ b/src/bundles/Elsa.Server.Web/Program.cs @@ -54,7 +54,13 @@ services if (useDapper) elsa.UseDapper(dapper => { - dapper.UseMigrations(); + dapper.UseMigrations(feature => + { + if (useSqlServer) + feature.UseSqlServer(); + else + feature.UseSqlite(); + }); dapper.DbConnectionProvider = sp => { if (useSqlServer) @@ -108,8 +114,8 @@ services else ef.UseSqlite(sqliteConnectionString); }); - - if(useZipCompression) + + if (useZipCompression) management.SetCompressionAlgorithm(nameof(Zstd)); }) .UseWorkflowRuntime(runtime => @@ -138,10 +144,11 @@ services }); } - if(useMassTransit) + if (useMassTransit) { runtime.UseMassTransitDispatcher(); } + runtime.WorkflowInboxCleanupOptions = options => configuration.GetSection("Runtime:WorkflowInboxCleanup").Bind(options); runtime.WorkflowDispatcherOptions = options => configuration.GetSection("Runtime:WorkflowDispatcher").Bind(options); }) @@ -187,19 +194,29 @@ services .UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options)) .UseAlterations(alterations => { - alterations.UseEntityFrameworkCore(ef => + if (useMongoDb) { - if (useSqlServer) - ef.UseSqlServer(sqlServerConnectionString); - else - ef.UseSqlite(sqliteConnectionString); - }); + // TODO: alterations.UseMongoDb(); + } + else if (useDapper) + { + // TODO: alterations.UseDapper(); + } + else + { + alterations.UseEntityFrameworkCore(ef => + { + if (useSqlServer) + ef.UseSqlServer(sqlServerConnectionString); + else + ef.UseSqlite(sqliteConnectionString); + }); + } if (useMassTransit) { alterations.UseMassTransitDispatcher(); } - }) .UseWorkflowContexts(); diff --git a/src/modules/Elsa.Dapper.Migrations/Identity/Initial.cs b/src/modules/Elsa.Dapper.Migrations/Identity/Initial.cs index cabb59edf..a01fd1cc4 100644 --- a/src/modules/Elsa.Dapper.Migrations/Identity/Initial.cs +++ b/src/modules/Elsa.Dapper.Migrations/Identity/Initial.cs @@ -1,5 +1,6 @@ using FluentMigrator; using JetBrains.Annotations; +using static System.Int32; namespace Elsa.Dapper.Migrations.Identity; @@ -17,13 +18,13 @@ public class Initial : Migration .WithColumn("Name").AsString().NotNullable() .WithColumn("HashedPassword").AsString().NotNullable() .WithColumn("HashedPasswordSalt").AsString().NotNullable() - .WithColumn("Roles").AsString().NotNullable(); + .WithColumn("Roles").AsString(MaxValue).NotNullable(); Create .Table("Roles") .WithColumn("Id").AsString().PrimaryKey() .WithColumn("Name").AsString().NotNullable() - .WithColumn("Permissions").AsString().NotNullable(); + .WithColumn("Permissions").AsString(MaxValue).NotNullable(); Create .Table("Applications") @@ -34,7 +35,7 @@ public class Initial : Migration .WithColumn("Name").AsString().NotNullable() .WithColumn("HashedApiKey").AsString().NotNullable() .WithColumn("HashedApiKeySalt").AsString().NotNullable() - .WithColumn("Roles").AsString().NotNullable(); + .WithColumn("Roles").AsString(MaxValue).NotNullable(); } /// diff --git a/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs b/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs index 1e7a94e64..b7aa69db9 100644 --- a/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs +++ b/src/modules/Elsa.Dapper.Migrations/Management/Initial.cs @@ -1,5 +1,6 @@ using FluentMigrator; using JetBrains.Annotations; +using static System.Int32; namespace Elsa.Dapper.Migrations.Management; @@ -18,14 +19,14 @@ public class Initial : Migration .WithColumn("DefinitionId").AsString().NotNullable() .WithColumn("Name").AsString().Nullable() .WithColumn("ToolVersion").AsString().Nullable() - .WithColumn("Description").AsString().Nullable() + .WithColumn("Description").AsString(MaxValue).Nullable() .WithColumn("ProviderName").AsString().Nullable() .WithColumn("MaterializerName").AsString().NotNullable() - .WithColumn("MaterializerContext").AsString().Nullable() - .WithColumn("Props").AsString().NotNullable() + .WithColumn("MaterializerContext").AsString(MaxValue).Nullable() + .WithColumn("Props").AsString(MaxValue).NotNullable() .WithColumn("UsableAsActivity").AsBoolean().Nullable() - .WithColumn("StringData").AsString().Nullable() - .WithColumn("BinaryData").AsBinary().Nullable() + .WithColumn("StringData").AsString(MaxValue).Nullable() + .WithColumn("BinaryData").AsBinary(MaxValue).Nullable() .WithColumn("CreatedAt").AsDateTimeOffset().NotNullable() .WithColumn("Version").AsInt32().NotNullable() .WithColumn("IsLatest").AsBoolean().NotNullable() @@ -40,14 +41,14 @@ public class Initial : Migration .WithColumn("DefinitionId").AsString().NotNullable() .WithColumn("Name").AsString().Nullable() .WithColumn("ToolVersion").AsString().Nullable() - .WithColumn("Description").AsString().Nullable() + .WithColumn("Description").AsString(MaxValue).Nullable() .WithColumn("ProviderName").AsString().Nullable() .WithColumn("MaterializerName").AsString().NotNullable() .WithColumn("MaterializerContext").AsString().Nullable() - .WithColumn("Props").AsString().NotNullable() + .WithColumn("Props").AsString(MaxValue).NotNullable() .WithColumn("UsableAsActivity").AsBoolean().Nullable() - .WithColumn("StringData").AsString().Nullable() - .WithColumn("BinaryData").AsBinary().Nullable() + .WithColumn("StringData").AsString(MaxValue).Nullable() + .WithColumn("BinaryData").AsBinary(MaxValue).Nullable() .WithColumn("CreatedAt").AsDateTime2().NotNullable() .WithColumn("Version").AsInt32().NotNullable() .WithColumn("IsLatest").AsBoolean().NotNullable() @@ -61,7 +62,7 @@ public class Initial : Migration .WithColumn("DefinitionId").AsString().NotNullable() .WithColumn("DefinitionVersionId").AsString().NotNullable() .WithColumn("Version").AsInt32().NotNullable() - .WithColumn("WorkflowState").AsString().NotNullable() + .WithColumn("WorkflowState").AsString(MaxValue).NotNullable() .WithColumn("Status").AsString().NotNullable() .WithColumn("SubStatus").AsString().NotNullable() .WithColumn("CorrelationId").AsString().Nullable() @@ -78,7 +79,7 @@ public class Initial : Migration .WithColumn("DefinitionId").AsString().NotNullable() .WithColumn("DefinitionVersionId").AsString().NotNullable() .WithColumn("Version").AsInt32().NotNullable() - .WithColumn("WorkflowState").AsString().NotNullable() + .WithColumn("WorkflowState").AsString(MaxValue).NotNullable() .WithColumn("Status").AsString().NotNullable() .WithColumn("SubStatus").AsString().NotNullable() .WithColumn("CorrelationId").AsString().Nullable() diff --git a/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs b/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs index 04fe01593..221838fbd 100644 --- a/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs +++ b/src/modules/Elsa.Dapper.Migrations/Runtime/Initial.cs @@ -1,5 +1,6 @@ using FluentMigrator; using JetBrains.Annotations; +using static System.Int32; namespace Elsa.Dapper.Migrations.Runtime; @@ -8,6 +9,7 @@ namespace Elsa.Dapper.Migrations.Runtime; [PublicAPI] public class Initial : Migration { + private const int MaxNodeIdColumnLength = 2048; /// public override void Up() { @@ -19,7 +21,7 @@ public class Initial : Migration .WithColumn("Name").AsString().NotNullable().Indexed() .WithColumn("ActivityId").AsString().NotNullable().Indexed() .WithColumn("Hash").AsString().Nullable().Indexed() - .WithColumn("SerializedPayload").AsString().Nullable(); + .WithColumn("SerializedPayload").AsString(MaxValue).Nullable(); IfDatabase("SqlServer", "Oracle", "MySql", "Postgres") .Create @@ -30,8 +32,8 @@ public class Initial : Migration .WithColumn("CorrelationId").AsString().Nullable().Indexed() .WithColumn("ActivityInstanceId").AsString().NotNullable().Indexed() .WithColumn("Hash").AsString().Nullable().Indexed() - .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("SerializedMetadata").AsString().Nullable() + .WithColumn("SerializedPayload").AsString(MaxValue).Nullable() + .WithColumn("SerializedMetadata").AsString(MaxValue).Nullable() .WithColumn("CreatedAt").AsDateTimeOffset().NotNullable().Indexed(); IfDatabase("Sqlite") @@ -43,8 +45,8 @@ public class Initial : Migration .WithColumn("CorrelationId").AsString().Nullable().Indexed() .WithColumn("ActivityInstanceId").AsString().NotNullable().Indexed() .WithColumn("Hash").AsString().Nullable().Indexed() - .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("SerializedMetadata").AsString().Nullable() + .WithColumn("SerializedPayload").AsString(MaxValue).Nullable() + .WithColumn("SerializedMetadata").AsString(MaxValue).Nullable() .WithColumn("CreatedAt").AsDateTime2().NotNullable().Indexed(); IfDatabase("SqlServer", "Oracle", "MySql", "Postgres") @@ -61,13 +63,13 @@ public class Initial : Migration .WithColumn("ActivityType").AsString().NotNullable().Indexed() .WithColumn("ActivityTypeVersion").AsInt32().NotNullable().Indexed() .WithColumn("ActivityName").AsString().Nullable().Indexed() - .WithColumn("ActivityNodeId").AsString().NotNullable().Indexed() + .WithColumn("ActivityNodeId").AsString(MaxNodeIdColumnLength).NotNullable().Indexed() .WithColumn("EventName").AsString().Nullable().Indexed() - .WithColumn("Message").AsString().Nullable() + .WithColumn("Message").AsString(MaxValue).Nullable() .WithColumn("Source").AsString().Nullable() - .WithColumn("SerializedActivityState").AsString().Nullable() - .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("SerializedException").AsString().Nullable() + .WithColumn("SerializedActivityState").AsString(MaxValue).Nullable() + .WithColumn("SerializedPayload").AsString(MaxValue).Nullable() + .WithColumn("SerializedException").AsString(MaxValue).Nullable() .WithColumn("Timestamp").AsDateTimeOffset().NotNullable().Indexed() .WithColumn("Sequence").AsInt64().NotNullable().Indexed() ; @@ -86,14 +88,14 @@ public class Initial : Migration .WithColumn("ActivityType").AsString().NotNullable().Indexed() .WithColumn("ActivityTypeVersion").AsInt32().NotNullable().Indexed() .WithColumn("ActivityName").AsString().Nullable().Indexed() - .WithColumn("ActivityNodeId").AsString().NotNullable().Indexed() + .WithColumn("ActivityNodeId").AsString(MaxNodeIdColumnLength).NotNullable().Indexed() .WithColumn("EventName").AsString().Nullable().Indexed() - .WithColumn("Message").AsString().Nullable() + .WithColumn("Message").AsString(MaxValue).Nullable() .WithColumn("Source").AsString().Nullable() - .WithColumn("SerializedActivityState").AsString().Nullable() - .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("SerializedOutputs").AsString().Nullable() - .WithColumn("SerializedException").AsString().Nullable() + .WithColumn("SerializedActivityState").AsString(MaxValue).Nullable() + .WithColumn("SerializedPayload").AsString(MaxValue).Nullable() + .WithColumn("SerializedOutputs").AsString(MaxValue).Nullable() + .WithColumn("SerializedException").AsString(MaxValue).Nullable() .WithColumn("Timestamp").AsDateTime2().NotNullable().Indexed() .WithColumn("Sequence").AsInt64().NotNullable().Indexed() ; @@ -104,14 +106,14 @@ public class Initial : Migration .WithColumn("Id").AsString().PrimaryKey() .WithColumn("WorkflowInstanceId").AsString().NotNullable().Indexed() .WithColumn("ActivityId").AsString().NotNullable().Indexed() - .WithColumn("ActivityNodeId").AsString().NotNullable().Indexed() + .WithColumn("ActivityNodeId").AsString(MaxNodeIdColumnLength).NotNullable().Indexed() .WithColumn("ActivityType").AsString().NotNullable().Indexed() .WithColumn("ActivityTypeVersion").AsInt32().NotNullable().Indexed() .WithColumn("ActivityName").AsString().Nullable().Indexed() - .WithColumn("SerializedActivityState").AsString().Nullable() - .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("SerializedOutputs").AsString().Nullable() - .WithColumn("SerializedException").AsString().Nullable() + .WithColumn("SerializedActivityState").AsString(MaxValue).Nullable() + .WithColumn("SerializedPayload").AsString(MaxValue).Nullable() + .WithColumn("SerializedOutputs").AsString(MaxValue).Nullable() + .WithColumn("SerializedException").AsString(MaxValue).Nullable() .WithColumn("StartedAt").AsDateTimeOffset().NotNullable().Indexed() .WithColumn("CompletedAt").AsDateTimeOffset().Nullable().Indexed() .WithColumn("HasBookmarks").AsBoolean().NotNullable().Indexed() @@ -124,14 +126,14 @@ public class Initial : Migration .WithColumn("Id").AsString().PrimaryKey() .WithColumn("WorkflowInstanceId").AsString().NotNullable().Indexed() .WithColumn("ActivityId").AsString().NotNullable().Indexed() - .WithColumn("ActivityNodeId").AsString().NotNullable().Indexed() + .WithColumn("ActivityNodeId").AsString(MaxNodeIdColumnLength).NotNullable().Indexed() .WithColumn("ActivityType").AsString().NotNullable().Indexed() .WithColumn("ActivityTypeVersion").AsInt32().NotNullable().Indexed() .WithColumn("ActivityName").AsString().Nullable().Indexed() - .WithColumn("SerializedActivityState").AsString().Nullable() - .WithColumn("SerializedPayload").AsString().Nullable() - .WithColumn("SerializedOutputs").AsString().Nullable() - .WithColumn("SerializedException").AsString().Nullable() + .WithColumn("SerializedActivityState").AsString(MaxValue).Nullable() + .WithColumn("SerializedPayload").AsString(MaxValue).Nullable() + .WithColumn("SerializedOutputs").AsString(MaxValue).Nullable() + .WithColumn("SerializedException").AsString(MaxValue).Nullable() .WithColumn("StartedAt").AsDateTime2().NotNullable().Indexed() .WithColumn("CompletedAt").AsDateTime2().Nullable().Indexed() .WithColumn("HasBookmarks").AsBoolean().NotNullable().Indexed() @@ -147,8 +149,8 @@ public class Initial : Migration .WithColumn("ActivityInstanceId").AsString().Nullable().Indexed() .WithColumn("CorrelationId").AsString().Nullable().Indexed() .WithColumn("Hash").AsString().NotNullable().Indexed() - .WithColumn("SerializedBookmarkPayload").AsString() - .WithColumn("SerializedInput").AsString().Nullable() + .WithColumn("SerializedBookmarkPayload").AsString(MaxValue) + .WithColumn("SerializedInput").AsString(MaxValue).Nullable() .WithColumn("CreatedAt").AsDateTimeOffset().Indexed() .WithColumn("ExpiresAt").AsDateTimeOffset().Indexed() ; @@ -162,8 +164,8 @@ public class Initial : Migration .WithColumn("ActivityInstanceId").AsString().Nullable().Indexed() .WithColumn("CorrelationId").AsString().Nullable().Indexed() .WithColumn("Hash").AsString().NotNullable().Indexed() - .WithColumn("SerializedBookmarkPayload").AsString() - .WithColumn("SerializedInput").AsString().Nullable() + .WithColumn("SerializedBookmarkPayload").AsString(MaxValue) + .WithColumn("SerializedInput").AsString(MaxValue).Nullable() .WithColumn("CreatedAt").AsDateTime2().Indexed() .WithColumn("ExpiresAt").AsDateTime2().Indexed() ; diff --git a/src/modules/Elsa.Dapper.Migrations/Runtime/V3_1.cs b/src/modules/Elsa.Dapper.Migrations/Runtime/V3_1.cs index 3cefe7ac2..e453a8b41 100644 --- a/src/modules/Elsa.Dapper.Migrations/Runtime/V3_1.cs +++ b/src/modules/Elsa.Dapper.Migrations/Runtime/V3_1.cs @@ -1,11 +1,14 @@ +using System.Diagnostics.CodeAnalysis; using FluentMigrator; using JetBrains.Annotations; +using static System.Int32; namespace Elsa.Dapper.Migrations.Runtime; /// [Migration(20002, "Elsa:Runtime:AddKeyValueStore")] [PublicAPI] +[SuppressMessage("ReSharper", "InconsistentNaming")] public class V3_1 : Migration { /// @@ -14,7 +17,7 @@ public class V3_1 : Migration Create .Table("KeyValuePairs") .WithColumn("Key").AsString().PrimaryKey() - .WithColumn("Value").AsString().NotNullable(); + .WithColumn("Value").AsString(MaxValue).NotNullable(); } /// diff --git a/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs b/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs index f95831fa6..e14b57c5f 100644 --- a/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs +++ b/src/modules/Elsa.Dapper/Abstractions/SqlDialectBase.cs @@ -1,4 +1,6 @@ +using System.Text; using Elsa.Common.Entities; +using Elsa.Common.Models; using Elsa.Dapper.Contracts; namespace Elsa.Dapper.Abstractions; @@ -53,10 +55,25 @@ public abstract class SqlDialectBase : ISqlDialect } /// - public virtual string Skip(int count) => $"offset {count}"; + public virtual string Skip(int count) => $"offset {count} rows"; /// - public virtual string Take(int count) => $"limit {count}"; + public virtual string Take(int count) => $"fetch next {count} rows only"; + + /// + public virtual string Page(PageArgs pageArgs) + { + var sb = new StringBuilder(); + + // Attention: the order is important here for SQL Server (OFFSET before FETCH NEXT). + if (pageArgs.Offset != null) + sb.AppendLine(Skip(pageArgs.Offset.Value)); + + if (pageArgs.Limit != null) + sb.AppendLine(Take(pageArgs.Limit.Value)); + + return sb.ToString(); + } /// public string Insert(string table, string[] fields, Func? getParamName = default) @@ -75,6 +92,15 @@ public abstract class SqlDialectBase : ISqlDialect var fieldList = string.Join(", ", fields); var fieldParamNames = fields.Select(x => $"@{getParamName(x)}"); var fieldParamList = string.Join(", ", fieldParamNames); - return $"INSERT OR REPLACE INTO {table} ({primaryKeyField}, {fieldList}) VALUES (@{getParamName(primaryKeyField)}, {fieldParamList});"; + return @$" + MERGE INTO {table} WITH (HOLDLOCK) AS Target + USING (VALUES (@{getParamName(primaryKeyField)}, {fieldParamList})) + AS Source ({primaryKeyField}, {fieldList}) + ON Target.{primaryKeyField} = Source.{primaryKeyField} + WHEN MATCHED THEN + UPDATE SET {string.Join(", ", fields.Select(x => $"{x} = Source.{x}"))} + WHEN NOT MATCHED THEN + INSERT ({primaryKeyField}, {fieldList}) + VALUES (Source.{primaryKeyField}, {string.Join(", ", fields.Select(x => $"Source.{x}"))});"; } } \ 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 505451383..c83f4fc53 100644 --- a/src/modules/Elsa.Dapper/Contracts/ISqlDialect.cs +++ b/src/modules/Elsa.Dapper/Contracts/ISqlDialect.cs @@ -1,4 +1,5 @@ using Elsa.Common.Entities; +using Elsa.Common.Models; namespace Elsa.Dapper.Contracts; @@ -96,6 +97,13 @@ public interface ISqlDialect /// The number of records to take. string Take(int count); + /// + /// Adds pagination to a SQL query. + /// + /// The pagination arguments. + /// The modified SQL query with pagination. + string Page(PageArgs pageArgs); + /// /// Builds an INSERT query. /// diff --git a/src/modules/Elsa.Dapper/Dialects/SqlServerDialect.cs b/src/modules/Elsa.Dapper/Dialects/SqlServerDialect.cs index 4c4760a9f..665de65c5 100644 --- a/src/modules/Elsa.Dapper/Dialects/SqlServerDialect.cs +++ b/src/modules/Elsa.Dapper/Dialects/SqlServerDialect.cs @@ -8,8 +8,8 @@ namespace Elsa.Dapper.Dialects; public class SqlServerDialect : SqlDialectBase { /// - public override string Skip(int count) => $"OFFSET {count} ROWS"; + public override string Skip(int count) => $"Offset {count} Rows"; /// - public override string Take(int count) => $"FETCH NEXT {count} ROWS ONLY"; + public override string Take(int count) => $"fetch next {count} rows only"; } \ No newline at end of file diff --git a/src/modules/Elsa.Dapper/Dialects/SqliteDialect.cs b/src/modules/Elsa.Dapper/Dialects/SqliteDialect.cs index d4d1caf0a..f852ec22b 100644 --- a/src/modules/Elsa.Dapper/Dialects/SqliteDialect.cs +++ b/src/modules/Elsa.Dapper/Dialects/SqliteDialect.cs @@ -1,3 +1,5 @@ +using System.Text; +using Elsa.Common.Models; using Elsa.Dapper.Abstractions; namespace Elsa.Dapper.Dialects; @@ -7,4 +9,33 @@ namespace Elsa.Dapper.Dialects; /// public class SqliteDialect : SqlDialectBase { + /// + public override string Skip(int count) => $"offset {count}"; + + /// + public override string Take(int count) => $"limit {count}"; + + /// + public override string Page(PageArgs pageArgs) + { + var sb = new StringBuilder(); + + // Attention: the order is important here for SQLite (LIMIT must come before OFFSET). + if (pageArgs.Limit != null) + sb.AppendLine(Take(pageArgs.Limit.Value)); + + if (pageArgs.Offset != null) + sb.AppendLine(Skip(pageArgs.Offset.Value)); + + return sb.ToString(); + } + + public override string Upsert(string table, string primaryKeyField, string[] fields, Func? getParamName = default) + { + getParamName ??= x => x; + var fieldList = string.Join(", ", fields); + var fieldParamNames = fields.Select(x => $"@{getParamName(x)}"); + var fieldParamList = string.Join(", ", fieldParamNames); + 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/Extensions/ParameterizedQueryBuilderExtensions.cs b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs index ed20ce54a..e6dec0a26 100644 --- a/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs +++ b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs @@ -1,3 +1,4 @@ +using System.Data; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using Elsa.Common.Entities; @@ -75,11 +76,12 @@ public static class ParameterizedQueryBuilderExtensions /// /// The query. /// The table. + /// The primary key. /// The inner query. - public static ParameterizedQuery Delete(this ParameterizedQuery query, string table, ParameterizedQuery innerQuery) + public static ParameterizedQuery Delete(this ParameterizedQuery query, string table, string primaryKey, ParameterizedQuery innerQuery) { query.Sql.AppendLine(query.Dialect.Delete(table)); - query.Sql.AppendLine("and rowid in ("); + query.Sql.AppendLine($"and {primaryKey} in ("); query.Sql.AppendLine(innerQuery.Sql.ToString()); query.Sql.AppendLine(")"); return query; @@ -324,13 +326,7 @@ public static class ParameterizedQueryBuilderExtensions /// The page arguments. public static ParameterizedQuery Page(this ParameterizedQuery query, PageArgs pageArgs) { - // Attention: the order is important here for SQLite (LIMIT must come before OFFSET). - if (pageArgs.Limit != null) - query.Take(pageArgs.Limit.Value); - - if (pageArgs.Offset != null) - query.Skip(pageArgs.Offset.Value); - + query.Sql.AppendLine(query.Dialect.Page(pageArgs)); return query; } @@ -350,16 +346,20 @@ public static class ParameterizedQueryBuilderExtensions .ToArray(); getParameterName ??= x => x; - + query.Sql.AppendLine(query.Dialect.Upsert(table, primaryKeyField, fields, getParameterName)); - + var primaryKeyValue = record.GetType().GetProperty(primaryKeyField)?.GetValue(record); query.Parameters.Add($"@{getParameterName(primaryKeyField)}", primaryKeyValue); - + + var recordType = record.GetType(); foreach (var field in fields) { - var value = record.GetType().GetProperty(field)?.GetValue(record); - query.Parameters.Add($"@{getParameterName(field)}", value); + var prop = recordType.GetProperty(field)!; + var propType = prop.PropertyType; + var value = prop.GetValue(record); + var dbType = value == null ? GetDbType(propType) : default; + query.Parameters.Add($"@{getParameterName(field)}", value, dbType); } return query; @@ -389,4 +389,10 @@ public static class ParameterizedQueryBuilderExtensions return query; } + + private static DbType? GetDbType(Type type) + { + if (type == typeof(byte[])) return DbType.Binary; + return null; + } } \ No newline at end of file diff --git a/src/modules/Elsa.Dapper/Features/DapperFeature.cs b/src/modules/Elsa.Dapper/Features/DapperFeature.cs index 90194cdd5..a9bb0c4fa 100644 --- a/src/modules/Elsa.Dapper/Features/DapperFeature.cs +++ b/src/modules/Elsa.Dapper/Features/DapperFeature.cs @@ -17,9 +17,7 @@ public class DapperFeature : FeatureBase public DapperFeature(IModule module) : base(module) { // See: https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/dapper-limitations#data-types - SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); SqlMapper.AddTypeHandler(new GuidHandler()); - SqlMapper.AddTypeHandler(new TimeSpanHandler()); } /// diff --git a/src/modules/Elsa.Dapper/Features/DapperMigrationsFeature.cs b/src/modules/Elsa.Dapper/Features/DapperMigrationsFeature.cs index 45856bc84..9cee0c44f 100644 --- a/src/modules/Elsa.Dapper/Features/DapperMigrationsFeature.cs +++ b/src/modules/Elsa.Dapper/Features/DapperMigrationsFeature.cs @@ -18,11 +18,28 @@ public class DapperMigrationsFeature : FeatureBase { } + /// + /// Configures migrations to use SQLite. + /// + public DapperMigrationsFeature UseSqlite() + { + ConfigureRunner += runner => runner.AddSQLite(); + return this; + } + + /// + /// Configures migrations to use SQLite. + /// + public DapperMigrationsFeature UseSqlServer() + { + ConfigureRunner += runner => runner.AddSqlServer(); + return this; + } + /// /// Gets or sets a delegate to configure migrations. /// public Action ConfigureRunner { get; set; } = runner => runner - .AddSQLite() .WithGlobalConnectionString(sp => sp.GetRequiredService().GetConnectionString()) .WithMigrationsIn(typeof(Initial).Assembly); diff --git a/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs b/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs index 61dfb5fce..26e4aa73b 100644 --- a/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs +++ b/src/modules/Elsa.Dapper/Services/SqliteDbConnectionProvider.cs @@ -1,6 +1,8 @@ using System.Data; +using Dapper; using Elsa.Dapper.Contracts; using Elsa.Dapper.Dialects; +using Elsa.Dapper.TypeHandlers.Sqlite; using JetBrains.Annotations; using Microsoft.Data.Sqlite; @@ -12,7 +14,14 @@ namespace Elsa.Dapper.Services; [PublicAPI] public class SqliteDbConnectionProvider : IDbConnectionProvider { - private readonly string _connectionString = "Data Source=elsa.dapper.db"; + private readonly string _connectionString = "Data Source=:memory:;Cache=Shared"; + + static SqliteDbConnectionProvider() + { + // See: https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/dapper-limitations#data-types + SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); + SqlMapper.AddTypeHandler(new TimeSpanHandler()); + } /// /// Initializes a new instance of the class. @@ -29,9 +38,9 @@ public class SqliteDbConnectionProvider : IDbConnectionProvider { _connectionString = connectionString; } - + /// - public string GetConnectionString() =>_connectionString; + 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 eb6fde73c..0526cae05 100644 --- a/src/modules/Elsa.Dapper/Services/Store.cs +++ b/src/modules/Elsa.Dapper/Services/Store.cs @@ -264,7 +264,7 @@ public class Store where T : notnull filter(selectQuery); selectQuery = selectQuery.OrderBy(orderFields.ToArray()).Page(pageArgs); - var deleteQuery = _dbConnectionProvider.CreateQuery().Delete(TableName, selectQuery); + var deleteQuery = _dbConnectionProvider.CreateQuery().Delete(TableName, primaryKey, selectQuery); using var connection = _dbConnectionProvider.GetConnection(); return await deleteQuery.ExecuteAsync(connection); }