.net-coreentity-framework-coreentity-framework-core-3.1

Why does EF Core update seeded boolean fields on every migration, despite no changes being made?


This is EF Core 3.1. I have several models with a boolean field, IsActive, defined like so:

public class Job
{
    public bool? IsActive { get; set; }
}

My seed data looks like this:

modelBuilder.Entity<Job>()
    .Property(e => e.IsActive)
    .HasDefaultValue(true);

modelBuilder.Entity<Job>().HasData(
    new Job() { IsActive = true });

Every time I create a migration (including if I run a migration with no changes made, which should generate an empty migration), it has a UpdateData call for the field above, like so:

migrationBuilder.UpdateData(
    table: "Jobs",
    keyColumn: "id",
    keyValue: 1L,
    column: "IsActive",
    value: true);

I've also replicated this behavior in the TodoApi app and setup a GitHub repo for anyone interested in more details.

I understand that happening when the seed data is being generated by a function, or the result of something like DateTime.Now. I don't understand it happening here, when the column is assigned a raw boolean value during seeding.

This behavior seems very similar to issue #13047, "Incorrect UpdateData operations generated when using BoolToStringConverter with HasData", but that issue was fixed in EF Core 2.2.


Solution

  • Well the reason is the HasDefaultValue(true) call in the following model configuration code -

    modelBuilder.Entity<Job>()
        .Property(e => e.IsActive)
        .HasDefaultValue(true);
    

    Whenever you set a default value for a property with the HasDefaultValue() method, that is the value to be set for the respective column in the database table.

    With HasDefaultValue(true), you have set true to be the default value for IsActive property. Therefore, in the seed data -

    modelBuilder.Entity<Job>().HasData(
        new Job() { IsActive = true });
    

    setting a value to IsActive (doesn't matter whatever the value is) is a change from migration's perspective, even though the data didn't change between migrations.

    EDIT - What is the solution:
    The requirement to set default value for a column is always a valid requirement. So the other thing you might consider to change is the data seeding approach.

    The seeding approach you are using now is completely managed by migrations itself, and it has some limitations - Limitations of model seed data

    You can use a Custom initialization logic to seed data which migrations has nothing to do with.