< Summary

Information
Class: Elsa.Persistence.EFCore.Modules.Runtime.EFCoreTriggerStore
Assembly: Elsa.Persistence.EFCore
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore/Modules/Runtime/TriggerStore.cs
Line coverage
78%
Covered lines: 59
Uncovered lines: 16
Coverable lines: 75
Total lines: 167
Line coverage: 78.6%
Branch coverage
70%
Covered branches: 17
Total branches: 24
Branch coverage: 70.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
SaveAsync()100%1150%
SaveManyAsync()100%210%
FindAsync()100%11100%
FindManyAsync()100%11100%
FindManyAsync(...)100%210%
FindManyAsync()100%210%
ReplaceAsync()70%131070%
DeleteManyAsync()100%11100%
OnSaveAsync(...)50%22100%
OnLoadAsync(...)50%4480%
GetExistingLogicalKeysAsync()100%11100%
GetMissingLogicalTriggersAsync()100%11100%
ApplyCurrentTenant(...)75%4475%
DistinctByLogicalKey()100%44100%
GetLogicalKey(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore/Modules/Runtime/TriggerStore.cs

#LineLine coverage
 1using Elsa.Common.Entities;
 2using Elsa.Common.Models;
 3using Elsa.Common.Multitenancy;
 4using Elsa.Extensions;
 5using Elsa.Workflows;
 6using Elsa.Workflows.Runtime;
 7using Elsa.Workflows.Runtime.Entities;
 8using Elsa.Workflows.Runtime.Filters;
 9using Elsa.Workflows.Runtime.OrderDefinitions;
 10using JetBrains.Annotations;
 11using Open.Linq.AsyncExtensions;
 12
 13namespace Elsa.Persistence.EFCore.Modules.Runtime;
 14
 15/// <inheritdoc />
 16[UsedImplicitly]
 42317public class EFCoreTriggerStore(
 42318    EntityStore<RuntimeElsaDbContext, StoredTrigger> store,
 42319    ITenantAccessor tenantAccessor,
 42320    IPayloadSerializer serializer) : ITriggerStore
 21{
 22    /// <inheritdoc />
 23    public async ValueTask SaveAsync(StoredTrigger record, CancellationToken cancellationToken = default)
 24    {
 125        await store.SaveAsync(record, OnSaveAsync, cancellationToken);
 026    }
 27
 28    /// <inheritdoc />
 29    public async ValueTask SaveManyAsync(IEnumerable<StoredTrigger> records, CancellationToken cancellationToken = defau
 30    {
 031        await store.SaveManyAsync(records, OnSaveAsync, cancellationToken);
 032    }
 33
 34    /// <inheritdoc />
 35    public async ValueTask<StoredTrigger?> FindAsync(TriggerFilter filter, CancellationToken cancellationToken = default
 36    {
 237        return await store.FindAsync(filter.Apply, OnLoadAsync, filter.TenantAgnostic, cancellationToken);
 238    }
 39
 40    /// <inheritdoc />
 41    public async ValueTask<IEnumerable<StoredTrigger>> FindManyAsync(TriggerFilter filter, CancellationToken cancellatio
 42    {
 146943        return await store.QueryAsync(filter.Apply, OnLoadAsync, filter.TenantAgnostic, cancellationToken);
 146944    }
 45
 46    public ValueTask<Page<StoredTrigger>> FindManyAsync(TriggerFilter filter, PageArgs pageArgs, CancellationToken cance
 47    {
 048        return FindManyAsync(filter, pageArgs, new StoredTriggerOrder<string>(x => x.Id, OrderDirection.Ascending), canc
 49    }
 50
 51    public async ValueTask<Page<StoredTrigger>> FindManyAsync<TProp>(TriggerFilter filter, PageArgs pageArgs, StoredTrig
 52    {
 053        var count = await store.QueryAsync(filter.Apply, OnLoadAsync, cancellationToken).LongCount();
 054        var results = await store.QueryAsync(queryable => filter.Apply(queryable).OrderBy(order).Paginate(pageArgs).Orde
 055        return new(results, count);
 056    }
 57
 58    /// <inheritdoc />
 59    public async ValueTask ReplaceAsync(IEnumerable<StoredTrigger> removed, IEnumerable<StoredTrigger> added, Cancellati
 60    {
 135161        var removedList = removed.ToList();
 135162        var addedList = added.ToList();
 63
 289464        foreach (var trigger in addedList)
 9665            ApplyCurrentTenant(trigger);
 66
 135167        addedList = DistinctByLogicalKey(addedList).ToList();
 68
 135169        if (removedList.Count > 0)
 70        {
 3671            var filter = new TriggerFilter { Ids = removedList.Select(r => r.Id).ToList() };
 1872            await DeleteManyAsync(filter, cancellationToken);
 73        }
 74
 135175        if (addedList.Count == 0)
 126776            return;
 77
 8478        var newTriggers = await GetMissingLogicalTriggersAsync(addedList, cancellationToken);
 79
 8480        if (newTriggers.Count == 0)
 081            return;
 82
 83        try
 84        {
 8485            await store.SaveManyAsync(newTriggers, OnSaveAsync, cancellationToken);
 8486        }
 087        catch (Exception ex) when (DbExceptionClassifier.IsDuplicateKey(ex))
 88        {
 089            var remainingTriggers = await GetMissingLogicalTriggersAsync(newTriggers, cancellationToken);
 90
 091            if (remainingTriggers.Count == 0)
 092                return;
 93
 094            await store.SaveManyAsync(remainingTriggers, OnSaveAsync, cancellationToken);
 95        }
 135196    }
 97
 98    /// <inheritdoc />
 99    public async ValueTask<long> DeleteManyAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
 100    {
 18101        return await store.DeleteWhereAsync(filter.Apply, cancellationToken);
 18102    }
 103
 104    private ValueTask OnSaveAsync(RuntimeElsaDbContext dbContext, StoredTrigger entity, CancellationToken cancellationTo
 105    {
 97106        dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? serializer.Seriali
 97107        return default;
 108    }
 109
 110    private ValueTask OnLoadAsync(RuntimeElsaDbContext dbContext, StoredTrigger? entity, CancellationToken cancellationT
 111    {
 609112        if (entity is null)
 0113            return ValueTask.CompletedTask;
 114
 609115        var json = dbContext.Entry(entity).Property<string>("SerializedPayload").CurrentValue;
 609116        entity.Payload = !string.IsNullOrEmpty(json) ? serializer.Deserialize(json) : null;
 117
 609118        return ValueTask.CompletedTask;
 119    }
 120
 121    private async Task<HashSet<string>> GetExistingLogicalKeysAsync(ICollection<StoredTrigger> triggers, CancellationTok
 122    {
 180123        var workflowDefinitionIds = triggers.Select(x => x.WorkflowDefinitionId).Distinct().ToList();
 84124        var existingTriggers = await store.QueryAsync(
 84125            queryable => queryable.Where(trigger => workflowDefinitionIds.Contains(trigger.WorkflowDefinitionId)),
 84126            cancellationToken);
 127
 84128        return existingTriggers
 84129            .Select(GetLogicalKey)
 84130            .ToHashSet(StringComparer.Ordinal);
 84131    }
 132
 133    private async Task<List<StoredTrigger>> GetMissingLogicalTriggersAsync(ICollection<StoredTrigger> triggers, Cancella
 134    {
 84135        var existingKeys = await GetExistingLogicalKeysAsync(triggers, cancellationToken);
 84136        return triggers
 96137            .Where(trigger => !existingKeys.Contains(GetLogicalKey(trigger)))
 84138            .ToList();
 84139    }
 140
 141    private void ApplyCurrentTenant(StoredTrigger trigger)
 142    {
 96143        if (trigger.TenantId == Tenant.AgnosticTenantId)
 0144            return;
 145
 96146        trigger.TenantId ??= tenantAccessor.TenantId;
 96147    }
 148
 149    private static IEnumerable<StoredTrigger> DistinctByLogicalKey(IEnumerable<StoredTrigger> triggers)
 150    {
 1351151        var seen = new HashSet<string>(StringComparer.Ordinal);
 152
 2894153        foreach (var trigger in triggers)
 154        {
 96155            if (seen.Add(GetLogicalKey(trigger)))
 96156                yield return trigger;
 157        }
 1351158    }
 159
 160    private static string GetLogicalKey(StoredTrigger trigger) =>
 192161        string.Join(
 192162            '\u001f',
 192163            trigger.WorkflowDefinitionId,
 192164            trigger.Hash,
 192165            trigger.ActivityId,
 192166            trigger.TenantId);
 167}