Code First Enumerations放入查找表

我曾在很多商店工作,他们运行数据库优先模型,因此总是需要查找表。 您的查找表必须与您的枚举匹配,以便保持数据库的完整性。 我100%同意这个想法,但已经发现,当涉及到Code First Model时,这不是开箱即用的。 我确实在某处读过EF团队可能会添加在EF7中动态地将Enums添加到您的数据库(通过迁移)的function,但他们警告说这不是承诺。

那么你(如果有的话)如何实现这一目标呢? 我将在下面的答案中提供我的解决方案,并期待您的反馈。

我使用的是EF 6.1.3和.NET 4.5.1

所以我不会撒谎,我的解决方案有点深入,但我过去几天一直在使用它,我发现它的工作正如我所需要的那样。

让我们从顶部开始,我创建的基类:

public abstract class LookupTableBase { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } [Required] public string Name { get; set; } } 

这是我的一个查找表实体模型的示例:

 ///  /// Lookup Table for Enumeration AddressTypes /// File Reference: DataAccessLayer/Enumerations/Locators.cs /// DO NOT USE /// SHOULD NOT BE AVAILABLE IN ENTITY MODELS ///  [Table("AddressTypes", Schema = "Lookup")] public class AddressType : LookupTableBase {} 

以下是与此查找表一起使用的枚举:

 public enum AddressTypes { [StringValue("")] Unknown = 0, [StringValue("Home")] Home = 1, [StringValue("Mailing")] Mailing = 2, [StringValue("Business")] Business = 3 } 

StringValue属性是我创建的自定义属性(基于我在网上找到的示例),允许我调用:

 AddressTypes.Home.GetStringValue(); 

这将返回字符串值: Home

我将查找实体模型添加到我的DbSets中,因此将创建表,但我从未在任何其他实体模型中直接引用查找实体模型。 它的唯一目的是在DB中创建查找表,以便我可以针对它们创建外键约束。

 public DbSet AddressTypes { get; set; } 

在我的Context的OnModelCreating方法中,我确实必须添加它,因为Data Annotation似乎没有完全保留:

 modelBuilder.Entity() .Property(x => x.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 

在我的Migration的配置文件中,我将其添加到种子方法中:

 var addressTypeCount = Enum.GetValues(typeof (AddressTypes)).Length; var addressTypes = new List(); for (var i = 1; i < addressTypeCount; i++) { addressTypes.Add(new AddressType { Id = i, Name = ((AddressTypes)i).GetStringValue() }); } context.AddressTypes.AddOrUpdate(c => c.Id, addressTypes.ToArray()); context.SaveChanges(); 

最后,在Migration文件本身中,我将所有查找表创建方法移到列表顶部,现在我可以将外键约束添加到引用该枚举的任何表中。 就我而言,我更进了一步。 由于Migration Class是部分的,我创建了另一个部分类来匹配它。 创建了两种方法:

 public void LookupDataUp() public void LookupDataDown() 

在LookupDataUp方法中,我添加了所有自定义外键和索引,并在LookupDataDown中我删除了所有自定义外键和索引。

当我运行Update-Database时,我以前的所有表都有一些表示某些东西的整数值(在这种情况下是一个AddressType)但没有实际值,现在有一个值可以通过将它链接到它的查找表来看到。

我承认,这似乎只是为了将少量数据存入数据库,但现在每次我删除/更改/添加新项目到我的枚举时,它都会自动推送到数据库。 正如我在上面的问题中所述,这通过在“整数”字段上具有外键约束来创建数据库完整性。

如果您不希望弄乱上下文,但仍希望数据库中的枚举用于故障排除和手动查询。 它也不理想,但您可以在需要时进行一次性迁移。 显然,有一些清理和调整要做,这可能会根据您的使用情况而有所不同。

 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using LookupExample.Data; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace LookupExample.Areas.Admin.Controllers { // [Authorize] [Area("Admin")] public class SetupController : Controller { private ApplicationDbContext _db; public SetupController(ApplicationDbContext dbContext) { _db = dbContext; } public IActionResult Enums() { var enums = Assembly.GetExecutingAssembly().GetTypes() .Where(ttype => ttype.IsEnum && ttype.IsPublic && ttype.Namespace.StartsWith("LookupExample.Models")); var dictionary = enums.ToDictionary(EnumTableName, EnumDictionary); if (dictionary.Count <= 0) return Json(dictionary); #pragma warning disable EF1000 // Possible SQL injection vulnerability. foreach (var kvp in dictionary) { var table = kvp.Key; var tableSql = $"IF OBJECT_ID('{table}', 'U') IS NOT NULL DROP TABLE {table}; CREATE TABLE {table} ( Id int, Val varchar(255));"; _db.Database.ExecuteSqlCommand(tableSql); if (kvp.Value.Count <= 0) continue; var insertSql = $"INSERT INTO {table} (Id, Val) VALUES ( @P0, @P1);"; foreach (var row in kvp.Value) { _db.Database.ExecuteSqlCommand(insertSql, row.Key, row.Value); } } #pragma warning restore EF1000 // Possible SQL injection vulnerability. return Json(dictionary); } private string EnumTableName(Type eenum) { var namespaceModifier = Regex.Replace(Regex.Replace(eenum.Namespace, @"^LookupExample\.Models\.?", ""), @"\.?Enums$", "").Replace(".", "_"); if (namespaceModifier.Length > 0) { namespaceModifier = namespaceModifier + "_"; } return "dbo.Enum_" + namespaceModifier + eenum.Name; // TODO enum schema? } private Dictionary EnumDictionary(Type eenum) { return Enum.GetValues(eenum).Cast().ToDictionary(e => e, e => Enum.GetName(eenum, e)); } } }