如何首先在 EF6 代码中创建与枚举对应的表

本文关键字:枚举 创建 何首先 EF6 代码 | 更新日期: 2023-09-27 18:28:35

我已经按照 MSDN 介绍了如何处理 EF6 代码优先中的枚举。它按预期工作,创建的表中引用枚举器的字段是一个简单的整数

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

public enum Faculty { Eng, Math, Eco }     
public partial class Department 
{ 
  [Key] public Guid ID { get; set; } 
  [Required] public Faculty Name { get; set; } 
}

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

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

是否建议使用"手动"填充表的方法?

如何首先在 EF6 代码中创建与枚举对应的表

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

在配置中添加种子.cs

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

在数据库上下文中添加枚举表

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
}

要记住

如果未在教职员工属性中添加虚拟,则必须使用 DbSet 中的 Include 方法来执行预先加载

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

如果教职员工属性是虚拟的,那么只需使用它

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

根据蒙泰罗@Alberto答案,我创建了泛型类,以防您有多个表。这里要注意的是,Id 是 TEnum 的类型。以这种方式使用它将提供使用枚举来声明属性类型的选项。

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

默认情况下,Enum 使用整数,因此数据库提供程序将创建具有"int"类型的字段。

枚举表.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;
    }

例外助手.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)}");
        }
    }
}

现在你只能继承枚举表

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

另一种可能性是,如果要使模型更简单,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);
}

这将创建包含 2 行的 "Shape" 表,名为 Square 和 Round,并在表 "Foo" 中使用相关的外键约束

优秀的@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));
}

在配置中添加种子.cs

将种子设定添加到数据上下文OnModelCreating

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

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

您的数据库上下文

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

在代码中,我基本上只使用枚举 Color(从不使用 ColorDto(。但是,对于 sql 查询和视图,在"自定义对象"表中拥有带有 FK 的"颜色"表仍然很好。

我可能晚了一点,但我没有在这里找到我要找的答案。

在实体框架文档中查看时,我找到了解决方案,这是价值转换中的第一个示例

有了这个,如果你愿意,你可以做一个很好的扩展方法。

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

然后在您的 OnModelCreation 中使用它:

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

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

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

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

更新:我找到了一种在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 中使用 OnModelCreation 中的 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})");
        });

老路

阿尔贝托·蒙泰罗很好地回答了这个问题。 我必须进行一些调整才能使其与 EF Core 配合使用。

重命名枚举并添加描述修饰器

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

在数据库上下文中添加枚举表

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
}

要记住

如果未在教职员工属性中添加虚拟,则必须使用 DbSet 中的 Include 方法来执行预先加载

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

如果教职员工属性是虚拟的,那么只需使用它

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