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);
}
}
4. Populating a new column from a related table
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.TableNameandargs.ColumnNameinstead 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
PropertyAddedsubscriber to populate it, the upgrade will fail when the constraint is applied. - Use
WHERE ... IS NULLguards in UPDATE statements to avoid overwriting any values that may already exist.