{"record":{"id":"3540e6701513732a","repo":"dotnet/efcore","slug":"executeoperationonkeylessentitytypewithunsupported","errorCode":"ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator","errorMessage":"The operation '{operation}' cannot be performed on keyless entity type '{entityType}', since it contains an operator not natively supported by the database provider.","messagePattern":"The operation '(.+?)' cannot be performed on keyless entity type '(.+?)', since it contains an operator not natively supported by the database provider\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs","lineNumber":117,"sourceCode":"                {\n                    throw new InvalidOperationException(\n                        RelationalStrings.ExecuteDeleteOnTableSplitting(unwrappedTableExpression.Table.SchemaQualifiedName));\n                }\n\n                selectExpression.ReplaceProjection([]);\n                selectExpression.ApplyProjection();\n\n                return new DeleteExpression(unwrappedTableExpression, selectExpression);\n            }\n        }\n\n        // We can't translate to a simple delete (e.g. the provider doesn't support one of the clauses).\n        // As a fallback, we place the original query in a Contains subquery, which will get translated via the regular entity equality/\n        // containment mechanism (InExpression for non-composite keys, Any for composite keys)\n        var pk = entityType.FindPrimaryKey();\n        if (pk == null)\n        {\n            throw new InvalidOperationException(\n                RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator(\n                    nameof(EntityFrameworkQueryableExtensions.ExecuteDelete),\n                    entityType.DisplayName()));\n        }\n\n        var clrType = entityType.ClrType;\n        var entityParameter = Expression.Parameter(clrType);\n        var predicateBody = Expression.Call(QueryableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter);\n\n        var newSource = Expression.Call(\n            QueryableMethods.Where.MakeGenericMethod(clrType),\n            new EntityQueryRootExpression(entityType),\n            Expression.Quote(Expression.Lambda(predicateBody, entityParameter)));\n\n        return TranslateExecuteDelete((ShapedQueryExpression)Visit(newSource));\n\n        static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBase table)\n        {","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs#L99-L135","documentation":"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.","triggerScenarios":"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.","commonSituations":"Using HasNoKey entities (read-only views, report models) and attempting bulk delete; query types migrated to keyless entities; complex filters over keyless sets.","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."],"exampleFix":"// before\nmodelBuilder.Entity<LogEntry>().HasNoKey().ToTable(\"Logs\");\nawait db.LogEntries.Where(l => l.Level == \"WARN\").OrderBy(l => l.Id).ExecuteDeleteAsync();\n// after - add a key so the fallback works\nmodelBuilder.Entity<LogEntry>().HasKey(l => l.Id).ToTable(\"Logs\");\nawait db.LogEntries.Where(l => l.Level == \"WARN\").ExecuteDeleteAsync();\n// or use raw SQL\nawait db.Database.ExecuteSqlRawAsync(\"DELETE FROM Logs WHERE Level = 'WARN'\");","handlingStrategy":"validation","validationCode":"if (entityType.FindPrimaryKey() is null)\n    throw new InvalidOperationException($\"{entityType.Name} is keyless; ExecuteDelete needs a key for the fallback path.\");\n\n// or simplify the query to avoid the fallback path\nquery = db.LogEntries.Where(l => l.Level == \"WARN\"); // single-table predicate only","typeGuard":null,"tryCatchPattern":"try { await db.LogEntries.Where(predicate).ExecuteDeleteAsync(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"keyless entity type\"))\n{\n    await db.Database.ExecuteSqlRawAsync($\"DELETE FROM Logs WHERE {predicateSql}\");\n}","preventionTips":["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."],"tags":["executedelete","keyless","bulk-operations","query-translation"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}