Search code examples
entity-framework-coreef-code-firstef-core-3.1

EF Core Migrations do not take seeding data values


I have a problem with EF Core Migrations in the DataSeeding. I have the following entity configuration:

public void Configure(EntityTypeBuilder<ChartOptionsModel> builder)
        {
            builder.ToTable("charts_options");

            //Columns description
            builder.Property(s => s.Id)
                .UseIdentityColumn()
                .HasColumnName("id")
                .HasColumnType("bigint");

            builder.Property(s => s.GroupId)
                .HasColumnName("group_id")
                .HasColumnType("bigint")
                .HasDefaultValue(1);

            builder.Property(s => s.Type)
                .HasColumnName("type")
                .HasColumnType("varchar(50)")
                .HasDefaultValue("");

            builder.Property(s => s.Title)
                .HasColumnName("title")
                .HasColumnType("text")
                .HasDefaultValue("");

            builder.Property(s => s.LabelFormatter)
                .HasColumnName("label_formatter")
                .HasColumnType("text")
                .HasDefaultValue("");

            builder.Property(s => s.ShowChart)
                .HasColumnName("show_chart")
                .HasColumnType("boolean")
                .HasDefaultValue(true);

            //Keys
            builder.HasKey(s => s.Id)
                .HasName("charts_options_pkey");

            //FK
            builder.HasOne(s => s.Group)
                .WithMany(s => s.ChartsOptions)
                .HasForeignKey(s => s.GroupId)
                .HasConstraintName("charts_options_group_id_fkey");

            //Indices
            builder.HasIndex(s => s.GroupId)
                .HasDatabaseName("charts_options_group_id_idx");

            builder.HasData(ChartsOptionsSeedingData.ChartsOptions);
        }

When I create an migration with Add-Migration [Migration name], I get this in the BuildTargetModel method of the migration:

modelBuilder.Entity("DatabaseManagerService.Database.Models.Charts.ChartOptionsModel", b =>
 {
     
     //other fields

     b.Property<bool>("ShowChart")
         .ValueGeneratedOnAdd()
         .HasColumnType("boolean")
         .HasDefaultValue(true)
         .HasColumnName("show_chart");

     b.Property<string>("Title")
         .ValueGeneratedOnAdd()
         .HasColumnType("text")
         .HasDefaultValue("")
         .HasColumnName("title");

     b.Property<string>("Type")
         .ValueGeneratedOnAdd()
         .HasColumnType("varchar(50)")
         .HasDefaultValue("")
         .HasColumnName("type");

     //other fields

     b.ToTable("charts_options");

     b.HasData(
         new
         {
             Id = 1L,
             GroupId = 1L,
             LabelFormatter = "`${vals[0]} of ${vals[0] + vals[1]}`",
             ShowChart = false,
             Title = "AHU In Local",
             Type = "AHUInLocalChart"
         },
         new
         {
             Id = 2L,
             GroupId = 1L,
             LabelFormatter = "`${vals[0]} of ${vals[0] + vals[1]}`",
             ShowChart = false,
             Title = "FQC In Local",
             Type = "FQCInLocalChart"
         },
    
    // other values here
    
         );
 });

In the Up method the table creation is alright, but the insertion of the data is very wried:

migrationBuilder.InsertData(
table: "charts_options",
columns: new[] { "id", "group_id", "label_formatter", "title", "type" },
values: new object[,]
{
    { 1L, 1L, "`${vals[0]} of ${vals[0] + vals[1]}`", "AHU In Local", "AHUInLocalChart" },
    { 21L, 3L, "`${vals[0]} of ${vals[0] + vals[1]}`", "HТ OK", "HTOkChart" },
    //rest of the values
});

The problem is, that although the default value of the ShowChart property is true, and in the model snapshot I have ShowChart = false for the first two rows of data, the generated insert statement, have no column show_chart, and the corresponding values - false. Am I doing some configuration wrong, or this is expected behaviour?

Best regards,

Julian


Solution

  • After long searching, I found why this is happening and the solution.

    Here is the link to the solution:

    Options Patterns that make this better, using as an example a bool with default true.

    A: Use client-side defaults We would scaffold:

    public class Blog
    {
        public bool IsValid { get; set; } = true;
    }
    

    and

    .HasDefaultValue(true)
    .ValueGeneratedNever();
    

    (If we introduce a scaffolding mode which does not include all database artifacts, then we can skip this.)

    When writing code manually, we would recommend just:

    public class Blog
    {
        public bool IsValid { get; set; } = true;
    }
    

    and no store-generated config unless you need Migrations to create the default in the database.

    B: Use regular properties backed by nullable field This approach would preserve all store-generated behavior at the expense of more code. Also, we need to make changes to EF to make this work, but they should not be complex.

    Code is the same for both scaffolding and writing manually:

    public class Blog
    {
        private bool? _isValid;
    
        public bool IsValid
        {
            get => _isValid ?? false;
            set => _isValid = value;
        }
    }
    

    and

        .HasDefaultValue(true);
    

    (There should be no need to set the property access mode since we read/write fields by default in 3.0.)

    C: Use nullable properties This preserves store generated behavior, but forces the type in the model to be nullable.

    public class Blog
    {
        public bool? IsValid { get; set; };
    }
    

    and

        .HasDefaultValue(true);