PropertyDeleted Event

Summary

The PropertyDeleted event fires during database upgrade when a column that exists in the current database is no longer present in the EF Core model. Use it to migrate data from the column being removed to another column or table before the column is dropped.

When does it fire?

PropertyDeleted fires when the schema comparison detects a column in the live database that does not exist in the new EF Core model. The subscriber runs before the column is dropped, so data can still be read from it.

Schema comparison → Column removed detected → PropertyDeleted subscribers → Column dropped

Syntax

[PropertyDeleted(typeof(EntityType), "PropertyName")]
public void MethodName(PropertyRemovedEventArgs args)
{
    args.UpgradeScript("SQL script here");
}

Attribute

Parameter Type Description
entityType Type The entity type (used as typeof(Entity))
propertyName string The property (column) name being removed

Event Args (PropertyRemovedEventArgs)

Property/Method Description
TableName The database table name
ColumnName The database column 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 column

When an old Price column is replaced by UnitPrice, copy the data before Price is dropped.

public class DataBaseUpgrade
{
    [PropertyDeleted(typeof(Product), "Price")]
    public void MigratePriceToUnitPrice(PropertyRemovedEventArgs args)
    {
        var sql = $@"
            UPDATE ""{args.TableName}""
            SET ""UnitPrice"" = ""{args.ColumnName}""
            WHERE ""UnitPrice"" IS NULL OR ""UnitPrice"" = 0;";
        args.UpgradeScript(sql);
    }
}

2. Archiving data before column removal

When a column is being removed, save the data to an audit table before it is dropped.

public class DataBaseUpgrade
{
    [PropertyDeleted(typeof(Customer), "LegacyCode")]
    public void ArchiveLegacyCode(PropertyRemovedEventArgs args)
    {
        var sql = $@"
            INSERT INTO ""AuditLog"" (""TableName"", ""ColumnName"", ""EntityGuid"", ""OldValue"", ""ArchivedAt"")
            SELECT '{args.TableName}', '{args.ColumnName}', ""Guid"", ""{args.ColumnName}""::text, NOW()
            FROM ""{args.TableName}""
            WHERE ""{args.ColumnName}"" IS NOT NULL;";
        args.UpgradeScript(sql);
    }
}

3. Reading data to inform migration logic

Use ReadFromDatabase to inspect existing data before generating the migration script.

public class DataBaseUpgrade
{
    [PropertyDeleted(typeof(SalesOrder), "OldStatus")]
    public void MigrateOldStatusValues(PropertyRemovedEventArgs args)
    {
        var data = args.ReadFromDatabase(
            $"SELECT DISTINCT \"{args.ColumnName}\" FROM \"{args.TableName}\" WHERE \"{args.ColumnName}\" IS NOT NULL");

        foreach (System.Data.DataRow row in data.Rows)
        {
            var oldValue = row[args.ColumnName]?.ToString();
            var newValue = oldValue == "InProgress" ? "Active" : "Closed";
            args.UpgradeScript(
                $"UPDATE \"{args.TableName}\" SET \"Status\" = '{newValue}' WHERE \"{args.ColumnName}\" = '{oldValue}';");
        }
    }
}

Notes

  • The [PropertyDeleted] attribute takes a Type parameter (not a string name) for the entity, unlike [PropertyAdded] which takes a string name.
  • The subscriber runs before the column is dropped, so you can still read from the column being removed.
  • Use ReadFromDatabase when you need to inspect the existing data to generate appropriate migration scripts.
  • If no subscriber handles the property deletion, the column is simply dropped and its data is lost.