Overview
This implementation explains how to physically delete a specific record (row) from a database using Entity Framework Core (EF Core). By retrieving the target data, passing it to the Remove method, and calling SaveChangesAsync, a DELETE SQL statement is executed against the database.
Specifications (Input/Output)
- Input: The ID of the data to be deleted (integer).
- Output: The result of the deletion process (success or error message).
- Prerequisites: .NET 6.0 or higher,
Microsoft.EntityFrameworkCore.InMemory(for testing purposes).
Basic Usage
- Retrieve the data you want to delete.
- Pass the target object to the
DbSet.Removemethod. - Commit the changes using
SaveChangesAsync.
// 1. Retrieve the target to delete
var item = await context.Items.FindAsync(targetId);
if (item != null)
{
// 2. Mark for deletion
context.Items.Remove(item);
// 3. Execute the DELETE statement in the database
await context.SaveChangesAsync();
}
Full Code Example
The following is a console application scenario for “Deleting old system logs by ID.” It uses an in-memory database for demonstration. You will need the Microsoft.EntityFrameworkCore.InMemory package.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
public class Program
{
public static async Task Main()
{
var options = new DbContextOptionsBuilder<SystemLogContext>()
.UseInMemoryDatabase("LogDb")
.Options;
// --- 1. Prepare Initial Data ---
using (var context = new SystemLogContext(options))
{
if (!await context.SystemLogs.AnyAsync())
{
context.SystemLogs.Add(new SystemLog { Id = 1, Message = "Server started", CreatedAt = DateTime.Now.AddHours(-2) });
context.SystemLogs.Add(new SystemLog { Id = 2, Message = "Connection warning", CreatedAt = DateTime.Now.AddHours(-1) });
await context.SaveChangesAsync();
}
}
// --- 2. Execute Deletion ---
using (var context = new SystemLogContext(options))
{
var worker = new LogCleaner(context);
// Delete the log with ID: 2
await worker.DeleteLogAsync(2);
}
// --- 3. Verify Results ---
using (var context = new SystemLogContext(options))
{
var logs = await context.SystemLogs.ToListAsync();
Console.WriteLine($"[Current Log Count]: {logs.Count}");
foreach (var log in logs)
{
Console.WriteLine($" - ID:{log.Id} {log.Message}");
}
}
}
}
// Business Logic Class
public class LogCleaner
{
private readonly SystemLogContext _context;
public LogCleaner(SystemLogContext context)
{
_context = context;
}
public async Task DeleteLogAsync(int logId)
{
// Search for the target to delete
var log = await _context.SystemLogs.FindAsync(logId);
if (log == null)
{
Console.WriteLine($"Error: Log with ID {logId} was not found.");
return;
}
// Set state to deleted (Database is not changed yet)
_context.SystemLogs.Remove(log);
// Commit changes (Executes the DELETE statement)
await _context.SaveChangesAsync();
Console.WriteLine($"Success: Log with ID {logId} has been deleted.");
}
}
// Entity Definition
public class SystemLog
{
public int Id { get; set; }
public string Message { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
}
// DbContext Definition
public class SystemLogContext : DbContext
{
public SystemLogContext(DbContextOptions<SystemLogContext> options)
: base(options) { }
public DbSet<SystemLog> SystemLogs => Set<SystemLog>();
}
Example Output
Success: Log with ID 2 has been deleted.
[Current Log Count]: 1
- ID:1 Server started
Customization Points
- Bulk Deletion: If you want to delete multiple records at once, use
RemoveRange(IEnumerable<TEntity>).C#var oldLogs = context.SystemLogs.Where(x => x.CreatedAt < DateTime.Now.AddDays(-30)); context.SystemLogs.RemoveRange(oldLogs); - Soft Deletion: If you want to keep the data but mark it as deleted, do not use
Remove. Instead, implement an update process that sets a flag likeIsDeleted = true. - Related Data Deletion: Depending on your relationship configuration (e.g.,
OnDelete: Cascade), deleting a parent record might automatically delete child records. Check your foreign key constraints to prevent unintended data loss.
Important Notes
- Retrieval Cost: Generally, you must load the data from the database into memory (SELECT) before calling
Remove. Loading large amounts of data just to delete it can hurt performance (see Advanced Application below). - Null Check: Passing
nullto theRemovemethod (from a failedFindAsyncorFirstOrDefaultAsync) will cause an exception or unexpected behavior. Always verify the data exists first. - Referential Integrity Errors: Attempting to delete data that is referenced by other tables may result in a constraint error from the database. Implement exception handling (
DbUpdateException) or manage dependencies properly.
Advanced Application
In EF Core 7.0 and later, you can use ExecuteDeleteAsync to perform deletions directly on the database without retrieving the data first. This reduces network traffic and is extremely fast.
// Skip the SELECT query and DELETE matching data directly
int deletedCount = await _context.SystemLogs
.Where(x => x.Id == 2)
.ExecuteDeleteAsync();
Console.WriteLine($"{deletedCount} record(s) deleted.");
Conclusion
For bulk deletions where performance is critical, consider using ExecuteDeleteAsync available in EF Core 7 and later.
Standard deletion follows the steps: Retrieve -> Remove -> SaveChangesAsync.
Always perform a null check to ensure the target exists before attempting to delete it.
