4

I am new to EF Core, and am trying to seed an enum.

According to Data Seeding, this feature is new to EF Core 2.1.

I reviewed several solutions including this SO solution by Blake Mumford, but this doesn't work for me since an enum is not a reference type.

My goal (with the help from migrations) is to have the Category enum to be populated in a new SQL table called Category, and have my Payment table contain a column that references the Category table as a foreign key.

Any help would be greatly appreciated. Thanks.

public partial class Payment
{
    public int PaymentId { get; set; }
    public DateTime PostedDate { get; set; }
    public string Vendor { get; set; }
    public decimal Amount { get; set; }

    public virtual Category Category { get; set; }
}

public enum Category
{
    Restaurants,
    Groceries,
    [Display(Name = "Home Goods")]
    HomeGoods,
    Entertainment
}

public partial class MyDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Error: The type Category must be a reference type in order to use it as parameter TEntity in the 
        // genric type or method ModelBuilder.Entity<TEntity>()

        modelBuilder.Entity<Category>().HasData(Category.Restaurants, 
                                                Category.Groceries, 
                                                Category.HomeGoods, 
                                                Category.Entertainment);
    }
}
Pavel
  • 704
  • 11
  • 25

3 Answers3

4

This is what I did hopefully it will work with your environment.

Environment

Project Framework

  • .NetCore 3.0

Nugets:

  1. EFCore 3.1.2
  2. EFCore.Design 3.1.2
  3. EFCore.Tools 3.1.2
  4. EFCore.Relational 3.1.2
  5. EFCore.SqlServer 3.1.2

Implementation

  1. Add OnModelCreating

    public partial class MyDbContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Error: The type Category must be a reference type in order to use it as parameter TEntity in the 
            // genric type or method ModelBuilder.Entity<TEntity>()
    
            modelBuilder.Entity<UserRole>().HasData(EnumFunctions.GetModelsFromEnum<UserRole, UserRoleEnum>());
        }
    }
    
  2. Create a Generic Enum Function

    public static class EnumFunctions
    {
        public static IEnumerable<TModel> GetModelsFromEnum<TModel, TEnum>() where TModel : IEnumModel<TModel, TEnum>, new()
        {
            var enums = new List<TModel>();
            foreach (var enumVar in (TEnum[])Enum.GetValues(typeof(TEnum)))
            {
                enums.Add(new TModel
                {
                    Id = enumVar,
                    Name = enumVar.ToString()
                });
            }
    
            return enums;
        }
    }
    
  3. Create an Interface

    public interface IEnumModel<TModel, TModelIdType>
    {
        TModelIdType Id { get; set; }
        string Name { get; set; }
    }
    
  4. Apply the interface to the model

    [Table("UserRoles")]
    public class UserRole : IEnumModel<UserRole, UserRoleEnum>
    {
        [Key]
        public UserRoleEnum Id { get; set; }
        public string Name { get; set; }       
    }
    
  5. Add your migration

    PM> add-migration SeedUserRoleTable
    

You should see the migration added

  1. Update the database with the seeded info

    PM> update-database
    
Demodave
  • 6,242
  • 6
  • 43
  • 58
2

Under my Entities folder I have added Enumerations folder, I have this:

public class UserStatus : Enumeration
    {

        public static readonly UserStatus New = new UserStatus(1, "New");
        public static readonly UserStatus UnderReview = new UserStatus(2, "UnderReview");
        public static readonly UserStatus Customer = new UserStatus(3, "Customer");
        public static readonly UserStatus Approved = new UserStatus(4, "Approved");
        public static readonly UserStatus Declined = new UserStatus(5, "Declined");

        public UserStatus(int id, string name)
                    : base(id, name)
        {
        }
    }

In my DataContext.cs I just use this to seed data:

 modelBuilder.Entity<UserStatus>().HasData(Enumeration.GetAll<UserStatus>());

UserStatus is not declared as DbSet in DataContext.cs

.NET Core 3.1

Red
  • 634
  • 6
  • 15
1

You cannot seed values for an enum type to the database because enums are not entity types and thus do not go into the database in the first place. In other words, there will be no Categories table, so it makes no sense to seed values into that non-existent table.

If you want to actually have your categories be persisted in your database, then you need to create a class like:

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • I think I get what you're saying, but it doesn't feel right from a database point of view to have a Category enum in the code, which would be stored as a string in my Payment table. From a database point of view, this should be an Id field referencing a Category table via foreign key. I found a solution (https://stackoverflow.com/a/52498981/4522506) where in OnModelCreating, you use Value Conversions which would allow you to have the category enum be mapped to either an ID or String in my payment table, but this still doesn't take care of seeding. – Pavel Dec 04 '18 at 20:48
  • I agree. It should likely be a foreign key, and that's why it should be a *class* and not an *enum*. Value converters won't help you because you cannot project a value into a different table. If you insist on keeping it an enum, you have no choice but for it to be a value column. – Chris Pratt Dec 04 '18 at 20:54
  • But if I make it a class and not an enum, how would I do comparisons such as: .Where(p => p.Category == Category.Entertainment). Also how would C# be aware of all these categories that are stored in the DB. This seems like a workaround, and does not feel natural for something that should naturally be an enum be represented as a class. Having said all that, what is the most common EF solution, since I'm new to EF. Do people mostly ignore how the DB is constructed using migrations, and just settle with the fact that enums are simply created in the DB as a value column? – Pavel Dec 04 '18 at 21:06
  • You're wanting two contradictory things. The whole point of an enum is that it's an enumeration of defined, unchanging values. As such, it makes no sense to persist that to something like a database, where the entire point is to allow mutability of data. If you want it in the database, then you no longer have clearly defined scope of values, and thus can't statically reference them. Pick an approach, and stop trying to create some unholy union of the two. – Chris Pratt Dec 04 '18 at 21:21