【问题标题】:Code First Enumerations put into Lookup Tables代码优先枚举放入查找表
【发布时间】:2015-07-12 21:01:26
【问题描述】:

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

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

我正在使用 EF 6.1.3 和 .NET 4.5.1

【问题讨论】:

  • 我知道他们计划添加它(在某个时候),但我厌倦了等待它。我通读了 cmets 并看到了您的 NuGet Package Post ......很好。我会把它添加到我的项目中,谢谢。
  • 我喜欢你的项目,它很好......在一些事情上存在一些问题,例如自定义模式,但总的来说它很好。我把它分叉了,看看我能不能帮忙。
  • 谢谢,很高兴你喜欢它。我在处理捐款方面比我想的要落后一些,但它远未结束。我会称之为更“稳定”
  • 您有拉取请求,可能会解决我遇到的一些相同问题。主要是自定义模式的使用。我还将添加一些配置选项以允许将枚举表放入自己的架构中。

标签: c# entity-framework enums


【解决方案1】:

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

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

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

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

public DbSet<AddressType> AddressTypes { get; set; }

在我的上下文的 OnModelCreating 方法中,我确实必须添加这个,因为数据注释似乎没有一直保持:

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

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

public void LookupDataUp()
public void LookupDataDown()

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

当我运行 Update-Database 时,我的所有表过去都有一些表示某些东西的整数值(在本例中为 AddressType)但没有实际值,现在有一个值可以通过将其链接到其查找来查看表。

我承认,这似乎需要大量工作才能将少量数据放入数据库,但现在每次我删除/更改/添加新项目到我的枚举时,它都会自动推送到数据库。另外,正如我在上述问题中所述,这通过在“整数”字段上设置外键约束来创建数据库完整性。

【讨论】:

  • 我很困惑如何将查找实体模型添加到您的 DbSets 实际上会创建外键约束?您不需要从您想要约束的模型中引用查找实体模型吗?我想您可以使用 Modelbuilder 创建一个伪约束,但看起来您并没有这样做。我遇到了同样的问题,即在我的项目中使用枚举的同时想要查找表,所以我希望这会起作用,但正如我所说,我对如何使用上面的示例创建外键约束感到困惑。
  • @Nicholas 对不起,我很久以前写过这个,但如果我没记错的话,我创建了一个新的类文件,我在迁移文件本身中调用它(分别为 LookupDataUp 和 LookupDataDown)。在此文件中,我为每个相应的查找表创建外键。老实说,这不是我想出的最佳解决方案,但它是第一个。我强烈推荐 Tim Abell 的 NuGet 包 (nuget.org/packages/ef-enum-to-lookup),它最终使用的不仅仅是我的解决方案。
  • 说... for (var i = 1; i
  • 只想指出,随着 EF Core 2.1 的发布,这不再是一个问题,因为您现在可以通过一点 Fluent API 将枚举(作为字符串值)直接推送到数据库中代码。
【解决方案2】:

如果您不想混淆上下文,但仍希望数据库中的枚举用于故障排除和手动查询。它也不理想,但您可以在需要时进行一次迁移。显然,需要进行一些清理和调整,这可能会因您的用例而异。

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

【讨论】:

    猜你喜欢
    • 2012-06-25
    • 1970-01-01
    • 2023-03-14
    • 2012-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多