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

时间:2011-05-04 16:04:12

标签: c# reflection .net-3.5 enums

这是一个有点简化的例子(我改变它来隐藏实际的代码)。 我有一个数据库驱动的应用程序和一个单独开发的小工具,可以与应用程序一起使用。有一个表定义了枚举,但它可能会随着时间的推移而发生变化。假设有一些应用程序(医疗?)需要相当精确地跟踪一个人的性别。

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匹配,并将description属性与助记符列匹配。

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

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

6 个答案:

答案 0 :(得分:7)

为什么不将SexType从枚举更改为类(或Struct)并在运行时从数据库中填充List?

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

public struct SexType
{
  public string Type;
  public string Code;
  public int Value;
}

然后您可以从数据库中填充List<SexType>(或者,如果您使用的是EF,则可以下载SexType类型的实体列表,或者您的解决方案允许的任何实体)。

假设您正在使用Linq和EF,请在应用程序启动时进行急切加载,并且您应该可以随意使用。

答案 1 :(得分:7)

查看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文件,其中包含一行&lt;#@ include file =“EnumGenerator.ttinclude”#&gt;`。此文件的名称应与表的名称相同,并且除非在枚举生成器类中更改,否则此表的列必须命名为“ID”和“NAME”。

每当您保存TT文件时,将自动生成枚举。

答案 2 :(得分:5)

var choices = Enumerable.Zip(
    Enum.GetNames(typeof(SexType)),
    Enum.GetValues(typeof(SexType)).Cast<SexType>(),
    (name, value) => Tuple.Create(name, value));

答案 3 :(得分:2)

如果你只是命名枚举值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;
}

答案 4 :(得分:1)

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

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

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

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

答案 5 :(得分:1)

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