dotnet/efcore · error · InvalidOperationException
ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator
ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator
Error message
The operation '{operation}' cannot be performed on keyless entity type '{entityType}', since it contains an operator not natively supported by the database provider. What it means
Thrown on the ExecuteDelete fallback path (when the provider cannot natively translate the delete and EF rewrites it as a Contains/IN subquery) if the entity type has no primary key. The fallback needs the primary key to build the containment predicate, so a keyless entity type with an unsupported operator cannot be translated.
Source
Thrown at src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs:117
{
throw new InvalidOperationException(
RelationalStrings.ExecuteDeleteOnTableSplitting(unwrappedTableExpression.Table.SchemaQualifiedName));
}
selectExpression.ReplaceProjection([]);
selectExpression.ApplyProjection();
return new DeleteExpression(unwrappedTableExpression, selectExpression);
}
}
// We can't translate to a simple delete (e.g. the provider doesn't support one of the clauses).
// As a fallback, we place the original query in a Contains subquery, which will get translated via the regular entity equality/
// containment mechanism (InExpression for non-composite keys, Any for composite keys)
var pk = entityType.FindPrimaryKey();
if (pk == null)
{
throw new InvalidOperationException(
RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator(
nameof(EntityFrameworkQueryableExtensions.ExecuteDelete),
entityType.DisplayName()));
}
var clrType = entityType.ClrType;
var entityParameter = Expression.Parameter(clrType);
var predicateBody = Expression.Call(QueryableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter);
var newSource = Expression.Call(
QueryableMethods.Where.MakeGenericMethod(clrType),
new EntityQueryRootExpression(entityType),
Expression.Quote(Expression.Lambda(predicateBody, entityParameter)));
return TranslateExecuteDelete((ShapedQueryExpression)Visit(newSource));
static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBase table)
{View on GitHub (pinned to dbf9771522)
Solutions
- Define a primary key on the entity type so the Contains-subquery fallback can be built.
- Simplify the query so the provider's native ExecuteDelete path applies (single-table predicate only), avoiding the fallback.
- Use raw SQL (Database.ExecuteSqlRaw) for the delete against the keyless entity's table.
- Map the type to a keyed entity if it represents mutable data.
Example fix
// before
modelBuilder.Entity<LogEntry>().HasNoKey().ToTable("Logs");
await db.LogEntries.Where(l => l.Level == "WARN").OrderBy(l => l.Id).ExecuteDeleteAsync();
// after - add a key so the fallback works
modelBuilder.Entity<LogEntry>().HasKey(l => l.Id).ToTable("Logs");
await db.LogEntries.Where(l => l.Level == "WARN").ExecuteDeleteAsync();
// or use raw SQL
await db.Database.ExecuteSqlRawAsync("DELETE FROM Logs WHERE Level = 'WARN'"); Defensive patterns
Strategy: validation
Validate before calling
if (entityType.FindPrimaryKey() is null)
throw new InvalidOperationException($"{entityType.Name} is keyless; ExecuteDelete needs a key for the fallback path.");
// or simplify the query to avoid the fallback path
query = db.LogEntries.Where(l => l.Level == "WARN"); // single-table predicate only Try / catch
try { await db.LogEntries.Where(predicate).ExecuteDeleteAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("keyless entity type"))
{
await db.Database.ExecuteSqlRawAsync($"DELETE FROM Logs WHERE {predicateSql}");
} Prevention
- Define a primary key on entities you bulk-delete.
- Keep ExecuteDelete queries simple (single-table predicate) to avoid the IN-subquery fallback.
- Use raw SQL for keyless/view-backed deletes.
- Reserve HasNoKey for truly read-only types.
When it happens
Trigger: Calling ExecuteDelete on a keyless entity type (HasNoKey) whose query also includes an operator the provider cannot natively translate (forcing the IN-subquery fallback), e.g. ExecuteDelete on a keyless type with a GroupBy/Join/Distinct in the predicate.
Common situations: Using HasNoKey entities (read-only views, report models) and attempting bulk delete; query types migrated to keyless entities; complex filters over keyless sets.
Related errors
- ExecuteDeleteOnNonEntityType
- ExecuteOperationOnOwnedJsonIsNotSupported
- ExecuteOperationOnTPT
- ExecuteOperationOnTPC
- ExecuteUpdateDeleteOnEntityNotMappedToTable
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/3540e6701513732a.
Report an issue: GitHub.