< Summary

Information
Class: Elsa.AI.Persistence.EFCore.Services.EFCoreAIConversationCleanup
Assembly: Elsa.AI.Persistence.EFCore
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Persistence.EFCore/Services/EFCoreAIConversationCleanup.cs
Line coverage
89%
Covered lines: 25
Uncovered lines: 3
Coverable lines: 28
Total lines: 56
Line coverage: 89.2%
Branch coverage
50%
Covered branches: 4
Total branches: 8
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
DeleteExpiredAsync()100%11100%
DeleteExpiredConfiguredAsync()50%2278.57%
ResolveConversationTableName(...)50%44100%
IsSqliteProvider(...)50%22100%
QuoteSqliteIdentifier(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Persistence.EFCore/Services/EFCoreAIConversationCleanup.cs

#LineLine coverage
 1using Elsa.AI.Abstractions.Models;
 2using Elsa.AI.Persistence.EFCore.Entities;
 3using Microsoft.EntityFrameworkCore;
 4
 5namespace Elsa.AI.Persistence.EFCore.Services;
 6
 7public static class EFCoreAIConversationCleanup
 8{
 9    public static async ValueTask<int> DeleteExpiredAsync(AIDbContext dbContext, DateTimeOffset now, CancellationToken c
 10    {
 111        var ephemeralRetentionMode = AIRetentionMode.Ephemeral.ToString();
 112        var configuredRetentionMode = AIRetentionMode.Configured.ToString();
 113        var completedStatus = AIConversationStatus.Completed.ToString();
 114        var failedStatus = AIConversationStatus.Failed.ToString();
 15
 116        var deletedEphemeral = await dbContext.Conversations
 117            .Where(x => x.RetentionMode == ephemeralRetentionMode && (x.Status == completedStatus || x.Status == failedS
 118            .ExecuteDeleteAsync(cancellationToken);
 19
 120        var deletedConfigured = await DeleteExpiredConfiguredAsync(dbContext, configuredRetentionMode, now, cancellation
 21
 122        return deletedEphemeral + deletedConfigured;
 123    }
 24
 25    private static async ValueTask<int> DeleteExpiredConfiguredAsync(AIDbContext dbContext, string configuredRetentionMo
 26    {
 127        if (IsSqliteProvider(dbContext.Database.ProviderName))
 28        {
 29            // EF Core SQLite cannot translate this DateTimeOffset predicate in ExecuteDeleteAsync for this model.
 130            var tableName = ResolveConversationTableName(dbContext);
 131            var sql = $@"DELETE FROM {QuoteSqliteIdentifier(tableName)}
 132WHERE ""RetentionMode"" = {{0}}
 133  AND ""RetentionExpiresAt"" IS NOT NULL
 134  AND ""RetentionExpiresAt"" <= {{1}}";
 135            return await dbContext.Database.ExecuteSqlRawAsync(
 136                sql,
 137                [configuredRetentionMode, now],
 138                cancellationToken);
 39        }
 40
 041        return await dbContext.Conversations
 042            .Where(x => x.RetentionMode == configuredRetentionMode && x.RetentionExpiresAt != null && x.RetentionExpires
 043            .ExecuteDeleteAsync(cancellationToken);
 144    }
 45
 46    private static string ResolveConversationTableName(AIDbContext dbContext)
 47    {
 148        var entityType = dbContext.Model.FindEntityType(typeof(AIConversationRecord)) ?? throw new InvalidOperationExcep
 149        return entityType.GetTableName() ?? throw new InvalidOperationException("AI conversation table metadata was not 
 50    }
 51
 52    private static bool IsSqliteProvider(string? providerName) =>
 153        providerName?.Contains("Sqlite", StringComparison.OrdinalIgnoreCase) == true;
 54
 155    private static string QuoteSqliteIdentifier(string identifier) => $"\"{identifier.Replace("\"", "\"\"", StringCompar
 56}