< 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
85%
Covered lines: 64
Uncovered lines: 11
Coverable lines: 75
Total lines: 166
Line coverage: 85.3%
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%11100%
FindManyAsync()100%11100%
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;
 11
 12namespace Elsa.Persistence.EFCore.Modules.Runtime;
 13
 14/// <inheritdoc />
 15[UsedImplicitly]
 42816public class EFCoreTriggerStore(
 42817    EntityStore<RuntimeElsaDbContext, StoredTrigger> store,
 42818    ITenantAccessor tenantAccessor,
 42819    IPayloadSerializer serializer) : ITriggerStore
 20{
 21    /// <inheritdoc />
 22    public async ValueTask SaveAsync(StoredTrigger record, CancellationToken cancellationToken = default)
 23    {
 124        await store.SaveAsync(record, OnSaveAsync, cancellationToken);
 025    }
 26
 27    /// <inheritdoc />
 28    public async ValueTask SaveManyAsync(IEnumerable<StoredTrigger> records, CancellationToken cancellationToken = defau
 29    {
 030        await store.SaveManyAsync(records, OnSaveAsync, cancellationToken);
 031    }
 32
 33    /// <inheritdoc />
 34    public async ValueTask<StoredTrigger?> FindAsync(TriggerFilter filter, CancellationToken cancellationToken = default
 35    {
 236        return await store.FindAsync(filter.Apply, OnLoadAsync, filter.TenantAgnostic, cancellationToken);
 237    }
 38
 39    /// <inheritdoc />
 40    public async ValueTask<IEnumerable<StoredTrigger>> FindManyAsync(TriggerFilter filter, CancellationToken cancellatio
 41    {
 147042        return await store.QueryAsync(filter.Apply, OnLoadAsync, filter.TenantAgnostic, cancellationToken);
 147043    }
 44
 45    public ValueTask<Page<StoredTrigger>> FindManyAsync(TriggerFilter filter, PageArgs pageArgs, CancellationToken cance
 46    {
 1247        return FindManyAsync(filter, pageArgs, new StoredTriggerOrder<string>(x => x.Id, OrderDirection.Ascending), canc
 48    }
 49
 50    public async ValueTask<Page<StoredTrigger>> FindManyAsync<TProp>(TriggerFilter filter, PageArgs pageArgs, StoredTrig
 51    {
 1252        var count = await store.CountAsync(filter.Apply, filter.TenantAgnostic, cancellationToken);
 2453        var results = (await store.QueryAsync(queryable => filter.Apply(queryable).OrderBy(order).Paginate(pageArgs).Ord
 1254        return new(results, count);
 1255    }
 56
 57    /// <inheritdoc />
 58    public async ValueTask ReplaceAsync(IEnumerable<StoredTrigger> removed, IEnumerable<StoredTrigger> added, Cancellati
 59    {
 135160        var removedList = removed.ToList();
 135161        var addedList = added.ToList();
 62
 289463        foreach (var trigger in addedList)
 9664            ApplyCurrentTenant(trigger);
 65
 135166        addedList = DistinctByLogicalKey(addedList).ToList();
 67
 135168        if (removedList.Count > 0)
 69        {
 3670            var filter = new TriggerFilter { Ids = removedList.Select(r => r.Id).ToList() };
 1871            await DeleteManyAsync(filter, cancellationToken);
 72        }
 73
 135174        if (addedList.Count == 0)
 126775            return;
 76
 8477        var newTriggers = await GetMissingLogicalTriggersAsync(addedList, cancellationToken);
 78
 8479        if (newTriggers.Count == 0)
 080            return;
 81
 82        try
 83        {
 8484            await store.SaveManyAsync(newTriggers, OnSaveAsync, cancellationToken);
 8485        }
 086        catch (Exception ex) when (DbExceptionClassifier.IsDuplicateKey(ex))
 87        {
 088            var remainingTriggers = await GetMissingLogicalTriggersAsync(newTriggers, cancellationToken);
 89
 090            if (remainingTriggers.Count == 0)
 091                return;
 92
 093            await store.SaveManyAsync(remainingTriggers, OnSaveAsync, cancellationToken);
 94        }
 135195    }
 96
 97    /// <inheritdoc />
 98    public async ValueTask<long> DeleteManyAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
 99    {
 18100        return await store.DeleteWhereAsync(filter.Apply, cancellationToken);
 18101    }
 102
 103    private ValueTask OnSaveAsync(RuntimeElsaDbContext dbContext, StoredTrigger entity, CancellationToken cancellationTo
 104    {
 97105        dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? serializer.Seriali
 97106        return default;
 107    }
 108
 109    private ValueTask OnLoadAsync(RuntimeElsaDbContext dbContext, StoredTrigger? entity, CancellationToken cancellationT
 110    {
 622111        if (entity is null)
 0112            return ValueTask.CompletedTask;
 113
 622114        var json = dbContext.Entry(entity).Property<string>("SerializedPayload").CurrentValue;
 622115        entity.Payload = !string.IsNullOrEmpty(json) ? serializer.Deserialize(json) : null;
 116
 622117        return ValueTask.CompletedTask;
 118    }
 119
 120    private async Task<HashSet<string>> GetExistingLogicalKeysAsync(ICollection<StoredTrigger> triggers, CancellationTok
 121    {
 180122        var workflowDefinitionIds = triggers.Select(x => x.WorkflowDefinitionId).Distinct().ToList();
 84123        var existingTriggers = await store.QueryAsync(
 84124            queryable => queryable.Where(trigger => workflowDefinitionIds.Contains(trigger.WorkflowDefinitionId)),
 84125            cancellationToken);
 126
 84127        return existingTriggers
 84128            .Select(GetLogicalKey)
 84129            .ToHashSet(StringComparer.Ordinal);
 84130    }
 131
 132    private async Task<List<StoredTrigger>> GetMissingLogicalTriggersAsync(ICollection<StoredTrigger> triggers, Cancella
 133    {
 84134        var existingKeys = await GetExistingLogicalKeysAsync(triggers, cancellationToken);
 84135        return triggers
 96136            .Where(trigger => !existingKeys.Contains(GetLogicalKey(trigger)))
 84137            .ToList();
 84138    }
 139
 140    private void ApplyCurrentTenant(StoredTrigger trigger)
 141    {
 96142        if (trigger.TenantId == Tenant.AgnosticTenantId)
 0143            return;
 144
 96145        trigger.TenantId ??= tenantAccessor.TenantId;
 96146    }
 147
 148    private static IEnumerable<StoredTrigger> DistinctByLogicalKey(IEnumerable<StoredTrigger> triggers)
 149    {
 1351150        var seen = new HashSet<string>(StringComparer.Ordinal);
 151
 2894152        foreach (var trigger in triggers)
 153        {
 96154            if (seen.Add(GetLogicalKey(trigger)))
 96155                yield return trigger;
 156        }
 1351157    }
 158
 159    private static string GetLogicalKey(StoredTrigger trigger) =>
 192160        string.Join(
 192161            '\u001f',
 192162            trigger.WorkflowDefinitionId,
 192163            trigger.Hash,
 192164            trigger.ActivityId,
 192165            trigger.TenantId);
 166}