如何使 C# 枚举与数据库中的表保持同步

Posted

技术标签:

【中文标题】如何使 C# 枚举与数据库中的表保持同步【英文标题】:How to keep a C# Enum in sync with a table in database 【发布时间】:2011-08-18 16:30:17 【问题描述】:

这是一个稍微简化的示例(我对其进行了更改以隐藏实际代码)。 我有一个数据库驱动的应用程序和一个正在单独开发的小工具,旨在与该应用程序一起使用。有一个表定义了一个枚举,但它可能会随着时间而改变。假设某个应用程序(医疗?)需要相当精确地跟踪一个人的性别。

select * from sex order by id;

id | mnemonic | description
0  | U        | Unknown sex
1  | M        | Male
2  | F        | Female
3  | T        | Trans-gender

还有我的C# enum

public enum SexType

    /// <summary>Unknown sex.</summary>
    [Description("U")]
    Unknown = 0,

    /// <summary>Male sex.</summary>
    [Description("M")]
    Male = 1,

    /// <summary>Female sex.</summary>
    [Description("F")]
    Female = 2

    /// <summary>Trans-gender sex.</summary>
    [Description("T")]
    TransGender = 3,

这里我不应该假设 id 是一个连续的序列;这些枚举标志也不是可以组合的,即使看起来可能是这样。

有些逻辑是用 SQL 完成的;有些是在C# 代码中完成的。例如,我可能有一个函数:

// Here we get id for some record from the database.
public static void IsMaleOrFemale(int sexId)

    if (!Enum.IsDefined(typeof(SexType), sexId))
    
        string message = String.Format("There is no sex type with id 0.", 
            sexId);
        throw new ArgumentException(message, "sexId");
    

    var sexType = (SexType) sexId;
    return sexType == SexType.Male || sexType == SexType.Female;

只要表和枚举定义都没有改变,这可以很好地工作。但桌子可能。我不能依赖 id 列或助记符列来维护它们的值。我认为我能做的最好的事情就是进行单元测试,以确保表与我对枚举的定义保持同步。我正在尝试将值与 id 匹配,并将描述属性与助记符列匹配。

那么,如何通过查看enum SexType 获得所有对的列表(以编程方式,在C# 中):(0, "U"), (1, "M"), (2, "F"), (3, "T")

我们的想法是将此有序列表与select id, mnemonic from sex order by is asc; 进行严格比较。

【问题讨论】:

哇,我从没想过要对它进行单元测试。我喜欢这个主意! 【参考方案1】:
var choices = Enumerable.Zip(
    Enum.GetNames(typeof(SexType)),
    Enum.GetValues(typeof(SexType)).Cast<SexType>(),
    (name, value) => Tuple.Create(name, value));

【讨论】:

谢谢,差不多了!我需要与众不同的一件事是,我想获得一组描述属性而不是枚举类型的名称。 致编辑:请不要那样做。 GetNamesGetValues 的文档非常清楚地表明它们已经按照枚举值的顺序返回了值和名称,因此没有必要在这里排序。【参考方案2】:

查看Tangible T4Editor。

我用它来做到这一点。

安装它,然后将此文件添加到您的项目中 (more information on this blog post):

EnumGenerator.ttinclude

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".generated.cs" #>
<#@ Assembly Name="System.Data" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#  
    string tableName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
    string path = Path.GetDirectoryName(Host.TemplateFile);
    string columnId = "ID";
    string columnName = "NAME";
    string connectionString = "[your connection string]";
#>
using System; 
using System.CodeDom.Compiler;  

namespace Your.NameSpace.Enums
     
    /// <summary>
    /// <#= tableName #> auto generated enumeration
    /// </summary>
    [GeneratedCode("TextTemplatingFileGenerator", "10")]
    public enum <#= tableName #>
     
<#
    SqlConnection conn = new SqlConnection(connectionString);
    string command = string.Format("select 0, 1 from 2 order by 0", columnId, columnName, tableName);
    SqlCommand comm = new SqlCommand(command, conn);

    conn.Open();

    SqlDataReader reader = comm.ExecuteReader();
    bool loop = reader.Read(); 

    while(loop)
     
#>
        /// <summary>
        /// <#= reader[columnName] #> configuration setting.
        /// </summary>
        <#= reader[columnName] #> = <#= reader[columnId] #><# loop = reader.Read(); #><#= loop ? ",\r\n" : string.Empty #> 
<#   
#>  
 
<#+     private string Pascalize(object value)
        
            Regex rx = new Regex(@"(?:^|[^a-zA-Z]+)(?<first>[a-zA-Z])(?<reminder>[a-zA-Z0-9]+)");
            return rx.Replace(value.ToString(), m => m.Groups["first"].ToString().ToUpper() + m.Groups["reminder"].ToString().ToLower());
              

        private string GetSubNamespace()
        
            Regex rx = new Regex(@"(?:.+Services\s)");
            string path = Path.GetDirectoryName(Host.TemplateFile);
            return rx.Replace(path, string.Empty).Replace("\\", ".");
        
#>

(填写你的类的命名空间和连接字符串)

然后,您可以只添加一个带有一行的空白 TT 文件 `。此文件的名称应与表的名称相同,并且此表的列必须命名为“ID”和“NAME”,除非您在枚举生成器类中更改它。

每当您保存 TT 文件时,Enum 都会自动生成。

【讨论】:

嗯……这看起来很有趣。那么,免费版能处理这个问题吗,还是我需要购买 PRO 许可证?【参考方案3】:
List<Tuple<int, string>> pairs = new List<Tuple<int,string>>();
     Type enumType = typeof(SexType);
     foreach (SexType enumValue in Enum.GetValues(enumType))
     
        var customAttributes = enumType.GetField(Enum.GetName(enumType, enumValue)).
           GetCustomAttributes(typeof(DescriptionAttribute), false);

        DescriptionAttribute descriptionAttribute = 
           (DescriptionAttribute)customAttributes.FirstOrDefault(attr => 
              attr is DescriptionAttribute);

        if (descriptionAttribute != null)
           pairs.Add(new Tuple<int, string>((int)enumValue, descriptionAttribute.Description));
     

【讨论】:

【参考方案4】:

如果您只是将枚举值命名为 U、M、F 和 T,会更容易。如果您这样做,Enum 类的静态方法会为您完成所有工作。

除此之外,您需要使用一些反射来挖掘描述属性

public IEnumerable<Tuple<string, int>> GetEnumValuePairs(Type enumType)

    if(!enumType.IsEnum)
    
        throw new ArgumentException();
    

    List<Tuple<string, int>> result = new List<Tuple<string, int>>();

    foreach (var value in Enum.GetValues(enumType))
    
        string fieldName = Enum.GetName(enumType, value);

        FieldInfo fieldInfo = enumType.GetField(fieldName);
        var descAttribute = fieldInfo.GetCustomAttributes(false).Where(a => a is DescriptionAttribute).Cast<DescriptionAttribute>().FirstOrDefault();

        // ideally check if descAttribute is null here
        result.Add(Tuple.Create(descAttribute.Description, (int)value));
    

    return result;

【讨论】:

谢谢,在实际表格中大约有十几个,并且具有描述性名称确实有帮助。 是的,我的代码中的那一点反映是从描述属性中提取值,所以结果是“男性”、“女性”等。【参考方案5】:

我会使用T4 Text Template,它会在处理模板时(在保存和/或构建时)为您动态生成枚举。

然后,此模板(包括 C# 代码)将根据您的数据库内容为您生成枚举定义到 .cs 文件中。

当然,这意味着您必须在保存/编译时访问数据库,但这是保证这两者在编译时一致的唯一方法。

在运行时,您可能希望对数据库执行一次检查,以确保枚举和数据库中的值一致。

【讨论】:

【参考方案6】:

为什么不将 SexType 从枚举更改为类(或结构)并在运行时从您的数据库中填充一个列表?

你的结构(或类)看起来像这样:

public struct SexType

  public string Type;
  public string Code;
  public int Value;

然后您可以从您的数据库中填充 List&lt;SexType&gt;(或者,如果您使用的是 EF,您可以下拉一个 SexType 类型的实体列表,或者您的解决方案允许的任何内容)。

假设您使用的是 Linq 和 EF,请在应用程序启动时进行预加载,您应该可以开始使用了。

【讨论】:

这种方法的难点在于基于该列表实现IsMaleOrFemale 函数。我不知道 ids 是否会保持不变,如果“M”/“F”将重命名为“MA”/“FE”等。使用静态枚举 + 单元测试,我可以做出假设,但也可以测试它。 你是说表中的数据实际上可能会变成MA/FE?据推测,无论您做什么都会导致问题 - 除非您真的不需要 Enum 来匹配表,它只需要具有大致等效的值......或者我错过了什么? 是的,实际表格(与性别无关)有可能实际上更改为“MA”/“FE”或“MALE”/“FEMALE”。我可能仍然使用该结构,但我需要存储所有记录的额外数组,以及代码 + 数据 st。它会记住映射,然后进行单元测试,以确保一切都同步。如果测试通过我很好。如果测试失败 - 那么我将需要重写一些代码。 显然是 YMMV-但在我工作过的每个组织都需要类似的东西(我猜你以某种形式从事医疗保健),我们自己设置了 Sex 的 ANSI 值表,任何引用性别的东西要么使用对该表的引用 (Sex = 1),而不是 (Sex = "MALE"),或者直接从该表中获取值。不过,不确定这是否适合您。

以上是关于如何使 C# 枚举与数据库中的表保持同步的主要内容,如果未能解决你的问题,请参考以下文章

Spring:如何使路由与页面上的 URL 保持同步?

如何使 Postman 集合和测试与 CI 流中的 swagger/open api 规范和 git 保持同步

使数据网格与 mysql 数据库保持同步

使多个上下文中的 NSManagedObjects 保持同步

使 RID 与 Strophe js 连接的多个选项卡保持同步

如何在EF6 Code First中创建与枚举对应的表?