Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net Core 2.1 seems to be ignoring modelBuilder.Entity<TEntity>().HasData

Tags:

I have a .net core project that contains model, service and api projects. My original goal with this project was to make enums work in a more "code-first" way. I wanted to just be able to modify the enum and get the database and the lookup table to be updated accordingly. After spending too much time trying to figure out how to do that, I decided I'd settle for creating a special enum class and manually seed it using "HasData". I have done this before and have not had problems with it. The last project I did this in was .Net Core 2.1. This project is using 2.2. I can't tell if I've discovered a weird bug or if I'm just doing something wrong. My "services" project is where the DbContext lives and most of the pertinent code is.

I have a model that looks like:

public class Employee : IEmployee
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Display(Name = "Id")]
    public int Id { get; set; }

    [StringLength(512, ErrorMessage = "The first name cannot be longer than 512 characters")]
    [Display(Name = "First Name")]
    [Required]
    public string FirstName { get; set; }

    [StringLength(512, ErrorMessage = "The last name cannot be longer than 512 characters")]
    [Display(Name = "Last Name")]
    [Required]
    public string LastName { get; set; }

    public Department Department { get; set; }

}

It has an Enum:

public enum Department{

    [Description("Sales")]
    Sales = 1,

    [Description("Customer Service")]
    CustomerService = 2,

    [Description("Technical Support")]
    TechnicalSupport = 3
}

I created a model that corresponds to the enum that looks like this:

[Table("Department")]
public class DepartmentEnum : EnumBase<Department>
{
    public DepartmentEnum()
    {
    }
}

It has a base class that looks like this:

public class EnumBase<TEnum> where TEnum : struct
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual int Id { get; set; }

    [Required]
    [MaxLength(100)]
    public virtual string Name { get; set; }

    [MaxLength(100)]
    public virtual string Description { get; set; }

    public virtual bool Deleted { get; set; } = false;
}

I created a DbContext that looks like this:

public class ApplicationDbContext : IdentityDbContext<AppUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options){}

    public DbSet<Employee> Employees { get; set; }

    public DbSet<DepartmentEnum> Departments { get; set; }

    protected internal virtual void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<DepartmentEnum>().HasData(
            new DepartmentEnum { Id = 1, Name = "Sales", Description = "Sales", Deleted = false },
            new DepartmentEnum { Id = 2, Name = "Customer Service", Description = "Customer Service", Deleted = false },
            new DepartmentEnum { Id = 3, Name = "TechnicalSupport", Description = "Technical Support", Deleted = false }
        );

    }
}

In the OnModelCreating section I am seeding the data that I want to be in this table. Because the service project can't be launched directly and can't learn it's connection string I created a DbContextFactory that inherits from IDesignTimeDbContextFactory<ApplicationDbContext> it looks like this:

public class DbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
    public ApplicationDbContext CreateDbContext(string[] args)
    {
        //uncomment this line if you need to debug this code
        //then choose yes and create a new instance of visual
        //studio to step through the code
        //Debugger.Launch();

        var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
        builder
            .UseSqlServer(@"Server=(localdb)\MSSQLLocalDB;Database=EnumSeeder;Trusted_Connection=True;MultipleActiveResultSets=true");

        //get the dbContext
        var context = new ApplicationDbContext(builder.Options);

        return context;
    }
}

When I run add-migration InitialCreate I am expecting to see my model data reflected in the migration file. When I look at the department table all I see is:

migrationBuilder.CreateTable(
    name: "Department",
    columns: table => new
    {
        Id = table.Column<int>(nullable: false)
            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
        Name = table.Column<string>(maxLength: 100, nullable: false),
        Description = table.Column<string>(maxLength: 100, nullable: true),
        Deleted = table.Column<bool>(nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Department", x => x.Id);
    });

The "HasData" potion of the creation is never there no matter what I do. I have tried switching the db context to just plain old DbContext. I have tried changing the order of events by adding the employee table first and then going back and adding the department table as a second migration. It seems like nothing I do will get the migration to contain the seeded data.

Can anyone spot my problem?

like image 988
user2033791 Avatar asked Jan 31 '19 19:01

user2033791


1 Answers

You should pay attention to compiler warnings.

Here

protected internal virtual void OnModelCreating(ModelBuilder modelBuilder)

you are shadowing the base OnModelCreating method (there must be a compiler warning telling that). And of course EF Core infrastructure is not calling your method, but the one defined in DbContext class.

Hence whatever you put there has no effect - the Department table is created based on EF Core conventions, and of course no data seed.

Change the method signature to

protected override void OnModelCreating(ModelBuilder modelBuilder)

and make sure to call

base.OnModelCreating(modelBuilder);

at the very beginning. This is especially important when you inherit IdentityContext (or any base context class other than the DbContext which does nothing).

like image 137
Ivan Stoev Avatar answered Oct 22 '22 12:10

Ivan Stoev