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
- Add an explicit ToTable("AIConversations") mapping for AIConversationRecord.
- Remove HasNoKey/ExcludeFromMigrations configuration for the entity.
- 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
- Always configure ToTable for AIConversationRecord.
- Avoid HasNoKey/ExcludeFromMigrations on entities used by raw SQL cleanup.
- Run cleanup only against relational providers.
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 conversation entity metadata was not found.
- Register with configured before calling , or call with a…
- Cannot overwrite an AI conversation that belongs to another…
- Failed to insert AI conversation
- A replacement-compensation link refers to a deleted user…
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)