How to create a table corresponding to enum in EF6 Code First?

前端 未结 8 1551
失恋的感觉
失恋的感觉 2020-11-29 17:10

I\'ve followed MSDN on how to handle enumerations in Code First for EF6. It worked, as supposed to but the field in the created table that refers to the enu

相关标签:
8条回答
  • 2020-11-29 17:50

    Excellent @AlbertoMonterio! To get this to work with ASP.NET CORE / EF Core I made a few adjustments to Alberto's solution.

    For brevity, only the modifications are shown below:

    Create a extension method to get description from enum and seed values

    using System;
    using System.ComponentModel;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    using Microsoft.EntityFrameworkCore; //added
    using Microsoft.EntityFrameworkCore.Metadata.Builders; //added
    
    public static class Extensions
    {
        //unchanged from alberto answer
        public static string GetEnumDescription<TEnum>(this TEnum item)
            => item.GetType()
                   .GetField(item.ToString())
                   .GetCustomAttributes(typeof(DescriptionAttribute), false)
                   .Cast<DescriptionAttribute>()
                   .FirstOrDefault()?.Description ?? string.Empty;
    
        //changed
        public static void SeedEnumValues<T, TEnum>(this ModelBuilder mb, Func<TEnum, T> converter)
        where T : class => Enum.GetValues(typeof(TEnum))
                               .Cast<object>()
                               .Select(value => converter((TEnum)value))
                               .ToList()
                                .ForEach(instance => mb.Entity<T>().HasData(instance));
    }
    

    Add the seed in Configuration.cs

    Add Seeding to OnModelCreating of DataContext

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.SeedEnumValues<Faculty, EnumEntityRole>(e => e);
    }
    
    0 讨论(0)
  • 2020-11-29 17:51

    Another possibility, if you want to keep your model simpler, POCO style, use the enum as a property that will be stored as an integer by entity framework.

    Then, if you want the "enum tables" to be created and updated in your DB, I recommend using the nuget package https://github.com/timabell/ef-enum-to-lookup and use it in a EF Migration seed method for example:

    public enum Shape
    {
        Square,
        Round
    }
    
    public class Foo
    {
        public int Id { get; set; }
        public Shape Shape { get; set; }
    }
    
    public class MyDbContext : DbContext
    {
        public DbSet<Foo> Foos { get; set; }
    }
    
    using(var context = new MyDbContext())
    {
        var enumToLookup = new EnumToLookup
        {
            TableNamePrefix = string.Empty,
            NameFieldLength = 50,
            UseTransaction = true
        };
        enumToLookup.Apply(context);
    }
    

    This will create the "Shape" table with 2 rows named Square and Round, with the relevant foreign key constraint in the table "Foo"

    0 讨论(0)
  • 2020-11-29 17:52

    You should add : byte in front of enum declaration :

    enum MyFieldEnum : byte{
        one = 1,
        two = 2,
        three = 4
    }
    

    In database, you should see TINYINT and no need to casting !

    0 讨论(0)
  • 2020-11-29 17:56

    Based on @Alberto Monteiro answer i've created generic class in case when you have several tables. The notice here is that Id is the type of TEnum. Using it in such way will provide option to use Enum for declaring property type.

    public class Question
    {
        public QuestionTypeEnum QuestionTypeId { get; set; } // field property
    
        public QuestionType QuestionType { get; set; } // navigation property
    }
    

    By default Enum using integers, so the db provider will create field with "int" type.

    EnumTable.cs

        public class EnumTable<TEnum>
            where TEnum : struct
        {
            public TEnum Id { get; set; }
            public string Name { get; set; }
    
            protected EnumTable() { }
    
            public EnumTable(TEnum enumType)
            {
                ExceptionHelpers.ThrowIfNotEnum<TEnum>();
    
                Id = enumType;
                Name = enumType.ToString();
            }
    
            public static implicit operator EnumTable<TEnum>(TEnum enumType) => new EnumTable<TEnum>(enumType);
            public static implicit operator TEnum(EnumTable<TEnum> status) => status.Id;
        }
    

    ExceptionHelpers.cs

    static class ExceptionHelpers
    {
        public static void ThrowIfNotEnum<TEnum>()
            where TEnum : struct
        {
            if (!typeof(TEnum).IsEnum)
            {
                throw new Exception($"Invalid generic method argument of type {typeof(TEnum)}");
            }
        }
    }
    

    Now you just can inherit the EnumTable

    public enum QuestionTypeEnum
    {
        Closed = 0,
        Open = 1
    }
    
    public class QuestionType : EnumTable<QuestionTypeEnum>
    {
        public QuestionType(QuestionTypeEnum enumType) : base(enumType)
        {
        }
    
        public QuestionType() : base() { } // should excplicitly define for EF!
    }
    

    Seed the values

    context.QuestionTypes.SeedEnumValues<QuestionType, QuestionTypeEnum>(e => new QuestionType(e));
    
    0 讨论(0)
  • 2020-11-29 17:56

    Another approach that works (and feels simpler to me) in EF Core:

    Your Enum

    public enum Color
    {
        Red = 1,
        Blue = 2,
        Green = 3,
    }
    

    Db Tables

    public class CustomObjectDto
    {
        public int ID { get; set; }
    
        // ... other props
    
        public Color ColorID { get; set; }
        public ColorDto ColorDto { get; set; }
    }
    
    public class ColorDto
    {
        public Color ID { get; set; }
        public string Name { get; set; }
    }
    

    Your DbContext

    public class Db : DbContext
    {
        public Db(DbContextOptions<Db> options) : base(options) { }
    
        public DbSet<CustomObjectDto> CustomObjects { get; set; }
        public DbSet<ColorDto> Colors { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Seed database with all Colors
            foreach (Color color in Enum.GetValues(typeof(Color)).Cast<Color>())
            {
                ColorDto colorDto = new ColorDto
                {
                    ID = color,
                    Name = color.ToString(),
                };
    
                modelBuilder.Entity<ColorDto>().HasData(colorDto);
            }
        }
    }
    

    In code I basically only use the enum Color (never ColorDto). But it's still nice to have the 'Colors' table with an FK in the 'CustomObjects' table for sql queries and views.

    0 讨论(0)
  • 2020-11-29 17:58

    Since EF doesn't handle it automatically, yes, this is the recommend way.

    I suggest some modifications in article that you provided.

    Rename your enum

    public enum FacultyEnum { Eng, Math, Eco }
    

    Create a class that represent the table

    public class Faculty
    {
        private Faculty(FacultyEnum @enum)
        {
            Id = (int)@enum;
            Name = @enum.ToString();
            Description = @enum.GetEnumDescription();
        }
    
        protected Faculty() { } //For EF
    
        [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int Id { get; set; }
    
        [Required, MaxLength(100)]
        public string Name { get; set; }
    
        [MaxLength(100)]
        public string Description { get; set; }
    
        public static implicit operator Faculty(FacultyEnum @enum) => new Faculty(@enum);
    
        public static implicit operator FacultyEnum(Faculty faculty) => (FacultyEnum)faculty.Id;
    }
    

    Your model reference the class

    public class ExampleClass
    {
        public virtual Faculty Faculty { get; set; }
    }
    

    Create a extension method to get description from enum and seed values

    using System;
    using System.ComponentModel;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    
    public static class Extensions
    {
        public static string GetEnumDescription<TEnum>(this TEnum item)
            => item.GetType()
                   .GetField(item.ToString())
                   .GetCustomAttributes(typeof(DescriptionAttribute), false)
                   .Cast<DescriptionAttribute>()
                   .FirstOrDefault()?.Description ?? string.Empty;
    
        public static void SeedEnumValues<T, TEnum>(this IDbSet<T> dbSet, Func<TEnum, T> converter)
            where T : class => Enum.GetValues(typeof(TEnum))
                                   .Cast<object>()
                                   .Select(value => converter((TEnum)value))
                                   .ToList()
                                   .ForEach(instance => dbSet.AddOrUpdate(instance));
    }
    

    Add the seed in Configuration.cs

    protected override void Seed(Temp.MyClass context)
    {
        context.Facultys.SeedEnumValues<Faculty, FacultyEnum>(@enum => @enum);
        context.SaveChanges();
    }
    

    Add the enum table in your DbContext

    public class MyClass : DbContext
    {
        public DbSet<ExampleClass> Examples { get; set; }
        public DbSet<Faculty> Facultys { get; set; }
    }
    

    Use it

    var example = new ExampleClass();
    example.Faculty = FacultyEnum.Eng;
    
    if (example.Faculty == FacultyEnum.Math)
    {
        //code
    }
    

    To remember

    If you don't add virtual in Faculty property, you must use Include method from DbSet to do Eager Load

    var exampleFromDb = dbContext.Examples.Include(x => x.Faculty).SingleOrDefault(e => e.Id == 1);
    if (example.Faculty == FacultyEnum.Math)
    {
        //code
    }
    

    If Faculty property is virtual, then just use it

    var exampleFromDb = dbContext.Examples.Find(1);
    if (example.Faculty == FacultyEnum.Math)
    {
        //code
    }
    
    0 讨论(0)
提交回复
热议问题