【问题标题】:How to create a table corresponding to enum in EF6 Code First?如何在EF6 Code First中创建与枚举对应的表?
【发布时间】:2016-04-06 02:00:35
【问题描述】:

我已关注MSDN,了解如何在 EF6 的 Code First 中处理枚举。它起作用了,正如预期的那样但是创建的表中引用枚举数的字段是一个简单的int

我希望生成第二个表,其值将遵循 C# 代码中枚举数的定义。因此,除了在 MSDN 上的示例中仅获取与 Department 对应的表外,我还希望看到由 Faculty 中的项目填充的第二个表。 p>

public enum Faculty { Eng, Math, Eco }     

public partial class Department 
{ 
  [Key] public Guid ID { get; set; } 
  [Required] public Faculty Name { get; set; } 
}

在研究这个问题时,我偶然发现了一个solution,它建议为枚举创建一个表并通过播种显式填充它。

在我看来,这是一种繁琐的方法,而且很多工作都应该自动处理。毕竟,系统知道构成枚举的实际值。从 DB 的角度来看,它仍然是数据行,就像我创建的实体一样,但从 OO 方面来看,它并不是真正的数据——而是一种类型(松散地表达),可以假设有限且预先知道的状态数量。

是否推荐“手动”填充表格的方法?

【问题讨论】:

标签: c# entity-framework enums ef-code-first entity-framework-6


【解决方案1】:

由于 EF 不会自动处理,是的,这是推荐的方式。

我建议对您提供的文章进行一些修改。

重命名你的枚举

public enum FacultyEnum { Eng, Math, Eco }

创建一个代表表格的类

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;
}

您的模型引用了类

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

创建扩展方法以从枚举和种子值中获取描述

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));
}

在 Configuration.cs 中添加种子

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

在 DbContext 中添加枚举表

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

使用它

var example = new ExampleClass();
example.Faculty = FacultyEnum.Eng;

if (example.Faculty == FacultyEnum.Math)
{
    //code
}

要记住

如果你没有在 Faculty 属性中添加 virtual,则必须使用 DbSet 中的 Include 方法进行 Eager Load

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

如果 Faculty 属性是虚拟的,那么就使用它

var exampleFromDb = dbContext.Examples.Find(1);
if (example.Faculty == FacultyEnum.Math)
{
    //code
}

【讨论】:

  • 措辞非常好的答案。 +1 仅用于努力。现在,据我了解,EF 不会通过其设计自动处理枚举,我想知道这是否只是设计人员做出的选择,或者背后是否存在逻辑、技术或语义上的原因。 如果它会按照我们懒人喜欢的方式处理它,这会导致概念上的悖论吗?我看到它可以通过一个值为整数和两个字符串(名称和描述)的表来解决。我是否过于简单化了这个问题?
  • @KonradViltersten 我认为一切都取决于。所以英孚试图满足大多数人最普遍的需求。我从来不需要为枚举创建一个表,只是使用“int”来满足我的需要。恕我直言,我认为 EF 设计选择了这种方式,因为大多数人都以这种方式使用并获得更好的性能和 DB 大小优化。此外,我认为这不会导致概念上的悖论,如果有创建另一个表的有效理由,那就去做吧。我清楚了吗?顺便说一句,新年快乐!
  • 你说的很清楚。 Year year = new Year{ State = States.Happy }; 对你来说也是如此。耶!我今年的第一个书呆子笑话!
  • @KonradViltersten eventStream.ReceiveJoke += joke =&gt; Console.WriteLine($"Pretty nice joke: {joke}, Thanks!!!");
  • @RitwikSen enum 是 C# 中的保留字,要将此字用作变量/方法/类名,必须使用后缀 @。
【解决方案2】:

基于@Alberto Monteiro 的回答,我创建了通用类,以防您有多个表。这里需要注意的是Id是TEnum的类型。以这种方式使用它将提供使用 Enum 来声明属性类型的选项。

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

    public QuestionType QuestionType { get; set; } // navigation property
}

默认情况下 Enum 使用整数,因此 db 提供程序将创建“int”类型的字段。

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)}");
        }
    }
}

现在你可以继承 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!
}

播种价值观

context.QuestionTypes.SeedEnumValues<QuestionType, QuestionTypeEnum>(e => new QuestionType(e));

【讨论】:

  • 这当然会破坏隐含运算符。为了解决这个问题,我将EnumTable 抽象化,并从中删除了隐式运算符。然后我将它们添加到派生类中。如果开发人员忘记添加隐式运算符,那么当他们尝试分配类型的引用或尝试在配置中设置种子时,他们将收到有关缺少隐式转换的编译器错误。我觉得这是可以接受的。
  • 非常感谢,对我帮助很大!很酷!来自巴西 =]
【解决方案3】:

另一种可能性,如果你想保持你的模型更简单,POCO 风格,使用枚举作为一个属性,它将被实体框架存储为一个整数。

然后,如果您希望在数据库中创建和更新“枚举表”,我建议使用 nuget 包 https://github.com/timabell/ef-enum-to-lookup 并在 EF 迁移种子方法中使用它,例如:

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);
}

这将创建一个名为“Square”和“Round”的 2 行的“Shape”表,表“Foo”中具有相关的外键约束

【讨论】:

  • 我喜欢简单的方法。
【解决方案4】:

另一种在 EF Core 中有效(我觉得更简单)的方法:

你的枚举

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

数据库表

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; }
}

你的 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);
        }
    }
}

在代码中我基本上只使用枚举颜色(从不使用 ColorDto)。但是在 'CustomObjects' 表中为 sql 查询和视图提供带有 FK 的 'Colors' 表仍然很好。

【讨论】:

  • 是的,它更简单,但是您丢失了一些不错的功能,例如将枚举与类进行比较,在 if 语句中很有用,而且您的方法不允许带有空格的“自动”名称,仅限于枚举字符串名称
  • 这是 def 更好:) 谢谢!
【解决方案5】:

优秀的@AlbertoMonterio!为了让它与 ASP.NET CORE / EF Core 一起使用,我对 Alberto 的解决方案进行了一些调整。

为简洁起见,以下仅显示修改:

创建扩展方法以从枚举和种子值中获取描述

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));
}

在 Configuration.cs 中添加种子

为DataContext的OnModelCreating添加种子

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.SeedEnumValues<Faculty, EnumEntityRole>(e => e);
}

【讨论】:

  • 不会让种子只在模型改变时发生吗?我发现每次应用启动时让种子重新填充数据库更有可能获得收益。还是我错过了什么?
  • @KonradViltersten 它会在模型​​初始化时播种。如果您想在启动时播种,您可以在 Startup 的 Configure() 方法中添加 context.Database.Migrate();
【解决方案6】:

您应该在enum 声明前添加: byte

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

在数据库中,您应该会看到TINYINT,无需强制转换!

【讨论】:

    【解决方案7】:

    我参加聚会可能有点晚了,但我没有在这里找到我想要的答案。

    在 EntityFramework 文档中查找时,我找到了解决方案,这是Value Conversions 中的第一个示例

    有了这个,你可以根据需要制作一个不错的扩展方法。即

    public static void HasEnum<TEntity, TProperty>(this EntityTypeBuilder<TEntity> entityBuilder, Expression<Func<TEntity, TProperty>> propertyExpression)
            where TEntity : class
            where TProperty : Enum
        {
            entityBuilder.Property(propertyExpression)
                .HasConversion(
                    v => v.ToString(),
                    v => (TProperty)Enum.Parse(typeof(TProperty), v)
                );
        }
    

    然后在你的 OnModelCreating 中使用它:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<YourEntity>()
            .HasEnum(e => e.YourProperty);
    }
    

    【讨论】:

    • 我是这个的原创者。不知道是不是这样,但我记得当我问,4 年零 5 个月前,有像 HasEnum 这样的注释。如果有,我很遗憾错过了该功能。如果没有,我很高兴它被添加了。 +1 的尝试,另外,您能否详细说明在表中创建的内容(即,如果采用这种方法,结果将是什么 DB 架构)?
    • 感谢您的回复! HasEnum 方法是我自己创建的扩展。通过这种方式,您可以以一种对其他人可读的简洁方式将其与模型构建器一起使用。在数据库中,它将作为枚举输入,本质上是一个带有允许值列表的字符串。所以当保存在数据库中时,枚举的值将被转换为字符串。检索时,该值被解析为您的枚举类型。我喜欢使用枚举,因为它清楚地显示了与 int 相对的值,即减少了出错的可能性。希望这能澄清一点..
    • 我可能需要补充一点,我认为这个解决方案只对 EF Core 有效,当这个问题被问到(4 年前)时,我不确定它是否真的存在。
    【解决方案8】:

    更新:我找到了一种在 EntityFrameworkCore 5.0.8 中运行良好的更好方法

    将 JsonConverter 属性添加到您的枚举中

    [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
    public enum FacultyEnum
    {
        [EnumMember(Value = "English Professor")]
        Eng,
        [EnumMember(Value = "Math Professor")]
        Math,
        [EnumMember(Value = "Economics Professor")]
        Eco
    }
    

    创建一个代表表格的类

    public class Faculty
    {
        public int Id { get; set; }
    
        public string Name { get; set; }
    
        public FacultyEnum Description { get; set; }
    }
    

    在 DbContext 中的 OnModelCreating 中使用 Fluent API 以使用枚举字符串并设置检查约束

            var enumToString = new EnumToStringConverter<FacultyEnum>();
            modelBuilder.Entity<Faculty>(entity =>
            {
                entity.ToTable(nameof(FacultyMembers));
                //convert enums to string
                entity.Property(e => e.Description).HasConversion(enumToString); 
                //build check constraint from enum
                var allowedEnumStrings = string.Join(',',
                    typeof(Faculty).GetMembers()
                        .Select(x => x.GetCustomAttribute(typeof(EnumMemberAttribute), false)).Where(x => x != null)
                        .Select(x => $"'{((EnumMemberAttribute)x).Value}'"));
                entity.HasCheckConstraint($"CK_{nameof(FacultyMembers)}_{nameof(Faculty.Description)}", $"{nameof(Faculty.Description)} in ({allowedEnumStrings})");
            });
    

    老路

    Alberto Monteiro 很好地回答了这个问题。我必须进行一些调整才能使其与 EF 核心一起使用。

    重命名您的枚举并添加描述装饰器

    public enum FacultyEnum 
    {
        [Description("English Professor")]
        Eng, 
        [Description("Math Professor")]
        Math, 
        [Description("Economics Professor")]
        Eco 
    }
    

    创建一个代表表格的类

    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;
    }
    

    您的模型引用了类

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

    创建扩展方法以从枚举和种子值中获取描述

    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;
    }
    

    在 YourDbContext.cs 中添加种子

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Faculty>().HasData(FacultyEnum.Eng, FacultyEnum.Math, FacultyEnum.Eco);
        }
    

    在 DbContext 中添加枚举表

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

    使用它

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

    要记住

    如果你没有在 Faculty 属性中添加 virtual,则必须使用 DbSet 中的 Include 方法进行 Eager Load

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

    如果 Faculty 属性是虚拟的,那么就使用它

    var exampleFromDb = dbContext.Examples.Find(1);
    if (example.Faculty == FacultyEnum.Math)
    {
        //code
    }
    

    【讨论】:

    • 你是否也为ExampleClass.Faculty配置了外键约束?
    • 我正在努力为我自己的代码证明这种方法的合理性,而且我不确定你的示例是如何工作的。您的表以 FacultyEnum.Eng 的条目为种子,因此当您设置 example.Faculty = FacultyEnum.Eng 时,您应该创建一个重复条目。在我的情况下,我得到了一个 UNIQUE 约束失败,并且必须使用类似 example.Faculty = dbContext.Facultys.Single(t =&gt; t.Id == FacultyEnum.Eng) 的东西来获取该枚举的实际表条目。隐式运算符显然是在创建一个new 实例,尽管由于隐式而不明显......
    • @theberserker 要获得 ForeignKey 约束,ExampleClass 还需要这样的属性:public string FacultyName { get;set; }
    • @DrEsperanto 我注意到了同样的问题。我希望它确实隐含地工作。在我的示例类中添加了一个新的 FacultyName 属性后,我不得不这样做:dbContext.Examples.First().FacultyName = TruckStatusEnum.Closed.Tostring()
    • @Danwize 一旦对框架有了更好的理解,我决定不使用这种隐式运算符的方法。它只是真的很容易(但很复杂)为数据库中的表做种,我只做一次。我只是通过将枚举转换为我的 EnumTable 对象列表来播种它们(我使用具有 Id 和 Name 参数的通用 EnumTable 对象,并在我的上下文中有一个静态方法将枚举转换为 EnumTables 列表。我然后在我的模型上使用 LINQ 查询以返回具有给定枚举值的 Id 的条目(这样我仍然可以从智能感知中获得帮助)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-13
    • 1970-01-01
    相关资源
    最近更新 更多