Code First Enumerations放入查找表

时间:2015-07-12 21:01:26

标签: c# entity-framework enums

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

那你怎么做(如果有的话)呢?我将在下面的答案中提供我的解决方案,并期待您的反馈。

我使用的是EF 6.1.3和.NET 4.5.1

2 个答案:

答案 0 :(得分:5)

所以我不会撒谎,我的解决方案有点深入,但我过去几天一直在使用它,而且我发现它完全符合我的需要。

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

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

    [Required]
    public string Name { get; set; }
}

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

/// <summary>
///     Lookup Table for Enumeration AddressTypes
///     File Reference: DataAccessLayer/Enumerations/Locators.cs
///     DO NOT USE
///     SHOULD NOT BE AVAILABLE IN ENTITY MODELS
/// </summary>
[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<AddressType> AddressTypes { get; set; }

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

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

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

var addressTypeCount = Enum.GetValues(typeof (AddressTypes)).Length;
var addressTypes = new List<AddressType>();
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)但是没有实际值,现在有一个值可以通过将它链接到它的查找来看到表

我承认,这似乎只是为了将一些少量数据存入数据库,但现在每次我删除/更改/添加新项目到我的枚举时,它都会被自动推送到D B。正如我在上面的问题中所说的,这通过在&#39;整数&#39;上设置外键来创建数据库完整性。字段。

答案 1 :(得分:0)

如果您不想弄乱上下文,但仍然希望数据库中的枚举可用于故障排除和手动查询。这也不理想,但是您可以在需要时进行一次迁移。显然,需要进行一些清理和调整,具体取决于您的用例。

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<int, string> EnumDictionary(Type eenum)
        {
            return Enum.GetValues(eenum).Cast<int>().ToDictionary(e => e, e => Enum.GetName(eenum, e));
        }
    }
}