elsa-workflows/elsa-core · error · InvalidOperationException

AI conversation table metadata was not found.

Error message

AI conversation table metadata was not found.

What it means

After finding the AIConversationRecord entity type, EFCoreAIConversationCleanup calls GetTableName(); a null result means the entity is mapped but has no table mapping (e.g. keyless or table-less configuration). Cleanup needs a concrete table name to build its SQL and throws InvalidOperationException otherwise.

Solutions

  1. Add an explicit ToTable("AIConversations") mapping for AIConversationRecord.
  2. Remove HasNoKey/ExcludeFromMigrations configuration for the entity.
  3. Use a real relational provider (SQLite/SqlServer) instead of InMemory for cleanup operations.

Example fix

// before
modelBuilder.Entity<AIConversationRecord>().HasNoKey();
// after
modelBuilder.Entity<AIConversationRecord>().ToTable("AIConversations").HasKey(x => x.Id);
Defensive patterns

Strategy: validation

Validate before calling

var entityType = dbContext.Model.FindEntityType(typeof(AIConversationRecord));
var table = entityType?.GetTableName();
if (table is null) throw new InvalidOperationException("AIConversationRecord has no table mapping");

Type guard

bool HasTableMapping(AIDbContext db) => db.Model.FindEntityType(typeof(AIConversationRecord))?.GetTableName() is not null;

Try / catch

try { await cleanup.RunAsync(ct); } catch (InvalidOperationException ex) when (ex.Message.Contains("table metadata")) { logger.LogError(ex, "AIConversationRecord lacks a table mapping"); }

Prevention

When it happens

Trigger: AIConversationRecord is configured without ToTable/table mapping (e.g. keyless entity, entity split or excluded from migrations) so GetTableName() returns null.

Common situations: Custom OnModelCreating overrides that call HasNoKey or ExcludeFromMigrations; partially applied mapping conventions after upgrades; test doubles using InMemory provider where table mapping differs.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/636f0333d2500319. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.AI.Persistence.EFCore/Services/EFCoreAIConversationCleanup.cs:49

            var sql = $@"DELETE FROM {QuoteSqliteIdentifier(tableName)}
WHERE ""RetentionMode"" = {{0}}
  AND ""RetentionExpiresAt"" IS NOT NULL
  AND ""RetentionExpiresAt"" <= {{1}}";
            return await dbContext.Database.ExecuteSqlRawAsync(
                sql,
                [configuredRetentionMode, now],
                cancellationToken);
        }

        return await dbContext.Conversations
            .Where(x => x.RetentionMode == configuredRetentionMode && x.RetentionExpiresAt != null && x.RetentionExpiresAt <= now)
            .ExecuteDeleteAsync(cancellationToken);
    }

    private static string ResolveConversationTableName(AIDbContext dbContext)
    {
        var entityType = dbContext.Model.FindEntityType(typeof(AIConversationRecord)) ?? throw new InvalidOperationException("AI conversation entity metadata was not found.");
        return entityType.GetTableName() ?? throw new InvalidOperationException("AI conversation table metadata was not found.");
    }

    private static bool IsSqliteProvider(string? providerName) =>
        providerName?.Contains("Sqlite", StringComparison.OrdinalIgnoreCase) == true;

    private static string QuoteSqliteIdentifier(string identifier) => $"\"{identifier.Replace("\"", "\"\"", StringComparison.Ordinal)}\"";
}

View on GitHub (pinned to fe9217bdfa)