PropertyAdded Event

Summary

The PropertyAdded event fires during database upgrade when a new column is detected on an existing table. Use it to populate the new column with appropriate initial values — either static defaults, values derived from related tables, or computed from other columns on the same table.

When does it fire?

PropertyAdded fires when the schema comparison detects a column in the new EF Core model that does not exist in the current database schema. The subscriber runs before any NOT NULL constraint is applied, giving you a chance to populate the column so the constraint does not fail.

Schema comparison → Column added detected → PropertyAdded subscribers → Schema DDL applied → NOT NULL constraints applied

Syntax

[PropertyAdded(nameof(EntityName), nameof(EntityName.PropertyName))]
public void MethodName(PropertyAddedEventArgs args)
{
    args.UpgradeScript("SQL script here");
}

Attribute

Parameter Type Description
entityName string The entity (table) name
propertyName string The property (column) name being added

Event Args (PropertyAddedEventArgs)

Property/Method Description
TableName The database table name
ColumnName The database column name being added
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. Setting a simple default for a new numeric column

When a new MortalityRate property is added to Flock, set all existing rows to 0.

public class DataBaseUpgrade
{
    [PropertyAdded(nameof(Flock), nameof(Flock.MortalityRate))]
    public void AddFlockMortalityRateProperty(PropertyAddedEventArgs args)
    {
        var sql = $"UPDATE \"{args.TableName}\" SET \"MortalityRate\" = 0 WHERE \"MortalityRate\" IS NULL;";
        args.UpgradeScript(sql);
    }
}

2. Setting a default for a new boolean column

When a new Pickable property is added to Product, default all existing products to true.

public class DataBaseUpgrade
{
    [PropertyAdded(nameof(Product), nameof(Product.Pickable))]
    public void AddPickableProperty(PropertyAddedEventArgs args)
    {
        var sql = $"UPDATE \"{args.TableName}\" SET \"Pickable\" = true WHERE \"Pickable\" IS NULL OR \"Pickable\" = false;";
        args.UpgradeScript(sql);
    }
}

3. Setting a default for a new string column

When a new Description property is added, default it to an empty string.

public class DataBaseUpgrade
{
    [PropertyAdded(nameof(Flock), nameof(Flock.Description))]
    public void AddFlockDescriptionProperty(PropertyAddedEventArgs args)
    {
        var sql = $"UPDATE \"{args.TableName}\" SET \"Description\" = '' WHERE \"Description\" IS NULL;";
        args.UpgradeScript(sql);
    }
}

When BeginWeekDate is added to FlockProduction, compute its value from the parent Flock's LayStartDate and the production's Week number.

public class DataBaseUpgrade
{
    [PropertyAdded(nameof(FlockProduction), nameof(FlockProduction.BeginWeekDate))]
    public void AddFlockProductionBeginWeekDateProperty(PropertyAddedEventArgs args)
    {
        var sql = $@"
            -- Treat .NET default DateOnly (1970-01-01) as missing
            UPDATE ""{args.TableName}"" SET ""{args.ColumnName}"" = NULL WHERE ""{args.ColumnName}"" = DATE '1970-01-01';

            -- Set BeginWeekDate based on Flock.LayStartDate and Week
            UPDATE ""{args.TableName}"" AS fp
            SET ""{args.ColumnName}"" = f.""LayStartDate"" + ((CASE WHEN fp.""Week"" > 0 THEN fp.""Week"" - 1 ELSE 0 END) * 7)
            FROM ""Flock"" AS f
            WHERE fp.""FlockGuid"" = f.""Guid""
              AND f.""LayStartDate"" IS NOT NULL
              AND (fp.""{args.ColumnName}"" IS NULL OR EXTRACT(YEAR FROM fp.""{args.ColumnName}"") = 1);
        ";
        args.UpgradeScript(sql);
    }
}

5. Populating a new column from a foreign key relationship with a fallback

When BreedGuid is added to FlockProduction, copy it from the parent Flock.

public class DataBaseUpgrade
{
    [PropertyAdded(nameof(FlockProduction), nameof(FlockProduction.BreedGuid))]
    public void AddFlockProductionBreedAndFarmProperty(PropertyAddedEventArgs args)
    {
        var sql = $@"
            UPDATE ""{args.TableName}"" AS fp
            SET ""BreedGuid"" = f.""BreedGuid""
            FROM ""Flock"" AS f
            WHERE fp.""FlockGuid"" = f.""Guid""
              AND fp.""BreedGuid"" IS NULL
              AND f.""BreedGuid"" IS NOT NULL;

            UPDATE ""{args.TableName}"" AS fp
            SET ""FarmGuid"" = f.""FarmGuid""
            FROM ""Flock"" AS f
            WHERE fp.""FlockGuid"" = f.""Guid""
              AND fp.""FarmGuid"" IS NULL
              AND f.""FarmGuid"" IS NOT NULL;";
        args.UpgradeScript(sql);
    }
}

6. Complex multi-step migration with conditional column checks

When ProjectedHatchRate is added to ManufacturingOrderDetail, populate it from related FlockProduction data and optionally update a companion column if it also exists.

public class DataBaseUpgrade
{
    [PropertyAdded(nameof(ManufacturingOrderDetail), nameof(ManufacturingOrderDetail.ProjectedHatchRate))]
    public void AddManufacturingHatchRate(PropertyAddedEventArgs args)
    {
        var sql = $@"
            UPDATE ""{args.TableName}"" AS mod
            SET ""ProjectedHatchRate"" = COALESCE(fp.""ProjectedHatchRate"", 0)
            FROM ""FlockProduction"" AS fp
            WHERE mod.""FlockProductionGuid"" = fp.""Guid""
              AND mod.""FlockProductionGuid"" IS NOT NULL;
            
            UPDATE ""{args.TableName}""
            SET ""ProjectedHatchRate"" = 0
            WHERE ""FlockProductionGuid"" IS NULL
              AND ""ProjectedHatchRate"" IS NULL;
            
            DO $$
            BEGIN
                IF EXISTS (SELECT 1 FROM information_schema.columns 
                          WHERE table_name = '{args.TableName}' 
                          AND column_name = 'ProjectedHatchCount') THEN
                    UPDATE ""{args.TableName}""
                    SET ""ProjectedHatchCount"" = ROUND((""SetCount"" * ""ProjectedHatchRate"" / 100.0)::numeric, 0)::integer
                    WHERE ""SetCount"" > 0 AND ""ProjectedHatchRate"" > 0;
                    
                    UPDATE ""{args.TableName}""
                    SET ""ProjectedHatchCount"" = 0
                    WHERE ""ProjectedHatchCount"" IS NULL;
                END IF;
            END $$;";
        args.UpgradeScript(sql);
    }
}

Notes

  • The [PropertyAdded] attribute can be applied multiple times to the same method if the same migration logic applies to multiple properties.
  • Use args.TableName and args.ColumnName instead of hardcoding table/column names — the actual database names may differ from the entity/property names.
  • NOT NULL constraints are applied after all subscriber scripts run, so you can safely populate the column first.
  • If a new NOT NULL column is added without a PropertyAdded subscriber to populate it, the upgrade will fail when the constraint is applied.
  • Use WHERE ... IS NULL guards in UPDATE statements to avoid overwriting any values that may already exist.