EntityDeleted Event
Summary
The EntityDeleted event fires during database upgrade when a table exists in the current database but is no longer present in the EF Core model. Use it to migrate data from the table being removed to other tables before it is dropped.
When does it fire?
EntityDeleted fires when the schema comparison detects a table in the live database that does not exist in the new EF Core model. The subscriber runs before the table is dropped, so data can still be read from it.
Schema comparison → Table removed detected → EntityDeleted subscribers → Table dropped
Syntax
[EntityDeleted("EntityName")]
public void MethodName(EntityDeletedEventArgs args)
{
args.UpgradeScript("SQL script here");
}
Attribute
| Parameter | Type | Description |
|---|---|---|
entityName |
string |
The entity (table) name being removed |
Event Args (EntityDeletedEventArgs)
| Property/Method | Description |
|---|---|
TableName |
The database table name being removed |
UpgradeScript(sql) |
Adds a SQL script to execute during the upgrade |
ReadFromDatabase(sql) |
Reads data from the database to help generate migration scripts |
Scenarios
1. Migrating data to a replacement table
When the Feature table is removed and replaced by __FeatureVersion, read the existing data and insert it into the new table.
public class DataUpgrade
{
[EntityDeleted("Feature")]
public void OnFeaturesTableDeleted(EntityDeletedEventArgs args)
{
var features = args.ReadFromDatabase(
"""SELECT "Id", "Name", "DemoDataVersion", "BlankDataVersion" FROM "Feature" """);
foreach (DataRow row in features.Rows)
{
var id = row["Id"];
var name = row["Name"];
var demoDataVersion = row["DemoDataVersion"];
var blankDataVersion = row["BlankDataVersion"];
args.UpgradeScript($"""
INSERT INTO "__FeatureVersion" ("Id", "Name", "Version", "DemoDataVersion", "BlankDataVersion", "UpdatedAt", "UpdatedByMethod")
SELECT '{id}', '{name}', 0, {demoDataVersion}, {blankDataVersion}, NOW(), 'MigratedFromOldFeaturesTable'
WHERE NOT EXISTS (SELECT 1 FROM "__FeatureVersion" WHERE "Name" = '{name}')
""");
}
}
}
2. Archiving all data before table removal
When a table is being retired, copy its data to an archive table.
public class DataBaseUpgrade
{
[EntityDeleted("LegacyOrder")]
public void ArchiveLegacyOrders(EntityDeletedEventArgs args)
{
args.UpgradeScript($@"
INSERT INTO ""ArchivedOrders"" (""OriginalGuid"", ""Data"", ""ArchivedAt"")
SELECT ""Guid"", row_to_json(""{args.TableName}"")::text, NOW()
FROM ""{args.TableName}"";");
}
}
3. Moving child records to a new parent before deletion
When a junction table is being removed, reassign the relationships.
public class DataBaseUpgrade
{
[EntityDeleted("ProductCategoryLink")]
public void MigrateCategoryLinks(EntityDeletedEventArgs args)
{
args.UpgradeScript($@"
UPDATE ""Product""
SET ""CategoryGuid"" = pcl.""CategoryGuid""
FROM ""{args.TableName}"" AS pcl
WHERE ""Product"".""Guid"" = pcl.""ProductGuid""
AND ""Product"".""CategoryGuid"" IS NULL;");
}
}
Notes
- The
[EntityDeleted]attribute takes a string entity name (not a Type), since the entity class may no longer exist in the codebase. - The subscriber runs before the table is dropped, so you can still query data from it using
ReadFromDatabase. - Use
ReadFromDatabasefor row-by-row data migration andUpgradeScriptfor bulk SQL operations. - If no subscriber handles the entity deletion, the table is simply dropped and all data is lost.
- EntityDeleted is typically used together with EntityAdded when replacing one table with another.