diff --git a/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs index 712a0e7dc..ed20ce54a 100644 --- a/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs +++ b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs @@ -200,6 +200,18 @@ public static class ParameterizedQueryBuilderExtensions return query; } + public static ParameterizedQuery StartsWith(this ParameterizedQuery query, string field, bool startsWith, string? value) + { + if (!startsWith || value == null || string.IsNullOrWhiteSpace(value)) + return query; + + var searchTermLike = $"{value}%"; + query.Sql.AppendLine($"and {field} like @SearchTermLike"); + query.Parameters.Add($"@{field}", searchTermLike); + + return query; + } + /// /// Appends an AND clause to the query if the value is not null. /// diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/KeyValueStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/KeyValueStore.cs index 1c0c2e392..f0326274a 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/KeyValueStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/KeyValueStore.cs @@ -1,9 +1,11 @@ using Elsa.Dapper.Contracts; using Elsa.Dapper.Extensions; +using Elsa.Dapper.Models; using Elsa.Dapper.Modules.Runtime.Records; using Elsa.Dapper.Services; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Models; namespace Elsa.Dapper.Modules.Runtime.Stores; @@ -32,18 +34,31 @@ public class DapperKeyValueStore : IKeyValueStore } /// - public async Task GetValue(string key, CancellationToken cancellationToken) + public async Task FindAsync(KeyValueFilter filter, CancellationToken cancellationToken) { - var record = await _store.FindAsync(query => query.Is(nameof(KeyValuePairRecord.Key), key), cancellationToken); + var record = await _store.FindAsync(q => ApplyFilter(q, filter), cancellationToken); return record == null ? null : Map(record); } + public Task> FindManyAsync(KeyValueFilter filter, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + /// public Task DeleteAsync(string key, CancellationToken cancellationToken) { return _store.DeleteAsync(query => query.Is(nameof(KeyValuePairRecord.Key), key), cancellationToken); } + private void ApplyFilter(ParameterizedQuery query, KeyValueFilter filter) + { + query + .Is(nameof(KeyValuePairRecord.Key), filter.Key) + .In(nameof(KeyValuePairRecord.Key), filter.Keys) + .StartsWith(nameof(KeyValuePairRecord.Key), filter.StartsWith, filter.Key); + } + private KeyValuePairRecord Map(SerializedKeyValuePair kvp) { return new() diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/KeyValueStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/KeyValueStore.cs index ee692344b..22a704dbe 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/KeyValueStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/KeyValueStore.cs @@ -1,6 +1,7 @@ using Elsa.EntityFrameworkCore.Common; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Models; namespace Elsa.EntityFrameworkCore.Modules.Runtime; @@ -23,9 +24,15 @@ public class EFCoreKeyValueStore : IKeyValueStore } /// - public Task GetValue(string key, CancellationToken cancellationToken) + public Task FindAsync(KeyValueFilter filter, CancellationToken cancellationToken) { - return _store.FindAsync(x => x.Key == key, cancellationToken); + return _store.FindAsync(filter.Apply, cancellationToken); + } + + /// + public Task> FindManyAsync(KeyValueFilter filter, CancellationToken cancellationToken) + { + return _store.QueryAsync(filter.Apply, cancellationToken); } /// diff --git a/src/modules/Elsa.MongoDb/Modules/Runtime/KeyValueStore.cs b/src/modules/Elsa.MongoDb/Modules/Runtime/KeyValueStore.cs index 14b409606..62f4901ab 100644 --- a/src/modules/Elsa.MongoDb/Modules/Runtime/KeyValueStore.cs +++ b/src/modules/Elsa.MongoDb/Modules/Runtime/KeyValueStore.cs @@ -1,6 +1,9 @@ using Elsa.MongoDb.Common; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Models; +using MongoDB.Driver.Linq; +using Open.Linq.AsyncExtensions; namespace Elsa.MongoDb.Modules.Runtime; @@ -26,9 +29,15 @@ public class MongoKeyValueStore : IKeyValueStore } /// - public Task GetValue(string key, CancellationToken cancellationToken) + public Task FindAsync(KeyValueFilter filter, CancellationToken cancellationToken) { - return _keyValueMongoDbStore.FindAsync(x => x.Key == key, cancellationToken); + return _keyValueMongoDbStore.FindAsync(query => Filter(query, filter), cancellationToken); + } + + /// + public async Task> FindManyAsync(KeyValueFilter filter, CancellationToken cancellationToken) + { + return await _keyValueMongoDbStore.FindManyAsync(query => Filter(query, filter), cancellationToken).ToList(); } /// @@ -36,4 +45,7 @@ public class MongoKeyValueStore : IKeyValueStore { return _keyValueMongoDbStore.DeleteWhereAsync(x => x.Key == key, cancellationToken); } + + private IMongoQueryable Filter(IMongoQueryable queryable, KeyValueFilter filter) => + (filter.Apply(queryable) as IMongoQueryable)!; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IKeyValueStore.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IKeyValueStore.cs index d3543d4a3..82e81976d 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IKeyValueStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IKeyValueStore.cs @@ -1,5 +1,7 @@ +using System.Linq.Expressions; using Elsa.Workflows.Runtime.Entities; - +using Elsa.Workflows.Runtime.Models; + namespace Elsa.Workflows.Runtime.Contracts; /// @@ -16,7 +18,12 @@ public interface IKeyValueStore /// Retrieves the key value pair from the store. /// /// if the key is found, otherwise null. - Task GetValue(string key, CancellationToken cancellationToken); + Task FindAsync(KeyValueFilter filter, CancellationToken cancellationToken); + + /// + /// Retrieves all key value pairs which match the predicate. + /// + Task> FindManyAsync(KeyValueFilter filter, CancellationToken cancellationToken); /// /// If the key is found it deletes the record from the store. diff --git a/src/modules/Elsa.Workflows.Runtime/Models/KeyValueFilter.cs b/src/modules/Elsa.Workflows.Runtime/Models/KeyValueFilter.cs new file mode 100644 index 000000000..cac333501 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Models/KeyValueFilter.cs @@ -0,0 +1,40 @@ +using Elsa.Workflows.Runtime.Entities; + +namespace Elsa.Workflows.Runtime.Models; + +public class KeyValueFilter +{ + /// + /// Gets or sets whether the needs to match the beginning of the key found. + /// + public bool StartsWith { get; set; } + + /// + /// Gets or sets the key to filter for. + /// + public string? Key { get; set; } + + /// + /// Gets or sets the keys to filter for. + /// + public ICollection? Keys { get; set; } + + /// + /// Applies the filter to the specified queryable. + /// + /// The queryable. + /// The filtered queryable. + public IQueryable Apply(IQueryable queryable) + { + var filter = this; + if (filter.Key != null) + { + queryable = StartsWith + ? queryable.Where(x => x.Key.StartsWith(filter.Key)) + : queryable.Where(x => x.Key == filter.Key); + } + if (filter.Keys != null) queryable = queryable.Where(x => filter.Keys.Contains(x.Key)); + + return queryable; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Stores/MemoryKeyValueStore.cs b/src/modules/Elsa.Workflows.Runtime/Stores/MemoryKeyValueStore.cs index 0d62b183c..6e17260e0 100644 --- a/src/modules/Elsa.Workflows.Runtime/Stores/MemoryKeyValueStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/Stores/MemoryKeyValueStore.cs @@ -1,6 +1,7 @@ using Elsa.Common.Services; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Stores; @@ -27,12 +28,19 @@ public class MemoryKeyValueStore : IKeyValueStore } /// - public Task GetValue(string key, CancellationToken cancellationToken) + public Task FindAsync(KeyValueFilter filter, CancellationToken cancellationToken) { - var result = _store.Find(x => x.Key == key); + var result = _store.Query(filter.Apply).FirstOrDefault(); return Task.FromResult(result); } + /// + public Task> FindManyAsync(KeyValueFilter filter, CancellationToken cancellationToken) + { + var result = _store.Query(filter.Apply); + return Task.FromResult(result); + } + /// public Task DeleteAsync(string key, CancellationToken cancellationToken) {