在数据库中存储枚举的最佳方法

时间:2010-04-15 15:08:10

标签: c# database visual-studio database-design enums

使用C#和Visual Studio以及MySQL数据连接器在数据库中存储枚举的最佳方法是什么。

我将创建一个包含100多个枚举的新项目,其中大部分都必须存储在数据库中。为每个人创建转换器将是一个漫长的过程,因此我想知道视觉工作室或某人是否有任何我没有听说过的方法。

10 个答案:

答案 0 :(得分:54)

    [Required]
    public virtual int PhoneTypeId
    {
        get
        {
            return (int)this.PhoneType;
        }
        set
        {
            PhoneType = (PhoneTypes)value;
        }
    }
    [EnumDataType(typeof(PhoneTypes))]
    public PhoneTypes PhoneType { get; set; }

public enum PhoneTypes
{
    Mobile = 0,
    Home = 1,
    Work = 2,
    Fax = 3,
    Other = 4
}

像魅力一样!无需在代码中转换(int)Enum或(Enum)int。只需使用enum和ef代码,首先会为你保存int。 p.s。:“[EnumDataType(typeof(PhoneTypes)]]”属性不是必需的,如果你想要额外的功能,这只是额外的。

或者你可以这样做:

[Required]
    public virtual int PhoneTypeId { get; set; }
    [EnumDataType(typeof(PhoneTypes))]
    public PhoneTypes PhoneType
    {
        get
        {
            return (PhoneTypes)this.PhoneTypeId;
        }
        set
        {
            this.PhoneTypeId = (int)value;
        }
    }

答案 1 :(得分:10)

我们将我们存储为整数或长期,然后我们可以来回投射它们。可能不是最强大的解决方案,而是我们所做的。

我们使用的是类型化的DataSet,例如:

enum BlockTreatmentType 
{
    All = 0
};

// blockTreatmentType is an int property
blockRow.blockTreatmentType = (int)BlockTreatmentType.All;
BlockTreatmentType btt = (BlockTreatmentType)blockRow.blocktreatmenttype;

答案 2 :(得分:3)

最后,您将需要一种很好的方式来处理重复编码任务,例如枚举转换器。您可以使用MyGenerationCodeSmith之类的代码生成器,也可以使用ORM mapper like nHibernate来处理所有内容。

至于结构......有数百个枚举我首先考虑尝试将数据组织成一个可能看起来像这样的表:(伪sql)

MyEnumTable(
EnumType as int,
EnumId as int PK,
EnumValue as int )

允许您将枚举信息存储在单个表中。 EnumType也可以是定义不同枚举的表的外键。

您的商业对象将通过EnumId链接到此表。枚举类型仅用于UI中的组织和过滤。当然,利用所有这些取决于您的代码结构和问题域。

顺便说一下,在这种情况下,您可能希望在EnumType上设置聚簇索引,而不是保留在PKey上创建的默认聚类idx。

答案 3 :(得分:3)

你应该考虑的一些事情。

枚举列是否会被其他应用程序直接使用,例如报告。这将限制枚举以整数格式存储的可能性,因为除非报告具有自定义逻辑,否则当报表中存在该值时没有任何意义。

您的应用需要哪些i18n?如果它只支持一种语言,您可以将枚举保存为文本,并创建一个帮助方法以从描述字符串转换。您可以使用[DescriptionAttribute]进行此操作,并且可以通过搜索SO来找到转换方法。

另一方面,如果您需要支持多种语言和外部应用程序访问您的数据,您可以开始考虑枚举是否真的是答案。如果场景更复杂,可以考虑其他选项,如查找表。

当代码自包含在代码中时,枚举非常好......当它们越过边界时,事情往往会变得有些混乱。


<强>更新

您可以使用Enum.ToObject方法从整数转换。这意味着您在转换时知道枚举的类型。如果要使其完全通用,则需要将枚举的类型与数据库中的值一起存储。您可以创建数据字典支持表,以告诉您哪些列是枚举,以及它们是什么类型。

答案 4 :(得分:3)

如果您想要存储所有枚举值,可以尝试使用下表存储枚举及其成员,以及添加这些值的代码段。但是,我只会在安装时执行此操作,因为在重新编译之前这些值永远不会改变!

数据库表:

   create table EnumStore (
    EnumKey int NOT NULL identity primary key,
    EnumName varchar(100)
);
GO

create table EnumMember (
    EnumMemberKey int NOT NULL identity primary key,
    EnumKey int NOT NULL,
    EnumMemberValue int,
    EnumMemberName varchar(100)
);
GO
--add code to create foreign key between tables, and index on EnumName, EnumMemberValue, and EnumMemberName

C#Snippet:

void StoreEnum<T>() where T: Enum
    {
        Type enumToStore = typeof(T);
        string enumName = enumToStore.Name;

        int enumKey = DataAccessLayer.CreateEnum(enumName);
        foreach (int enumMemberValue in Enum.GetValues(enumToStore))
        {
            string enumMemberName = Enum.GetName(enumToStore, enumMemberValue);
            DataAccessLayer.AddEnumMember(enumKey, enumMemberValue, enumMemberName);
        }
    }

答案 5 :(得分:2)

如果您需要在枚举字段的数据库字符串值中存储,最好如下所示。 例如,如果您使用的是不支持枚举字段的SQLite,则可能需要它。

[Required]
public string PhoneTypeAsString
{
    get
    {
        return this.PhoneType.ToString();
    }
    set
    {
        PhoneType = (PhoneTypes)Enum.Parse( typeof(PhoneTypes), value, true);
    }
}

public PhoneTypes PhoneType{get; set;};

public enum PhoneTypes
{
    Mobile = 0,
    Home = 1,
    Work = 2,
    Fax = 3,
    Other = 4
}

答案 6 :(得分:1)

我不确定它是否最灵活,但你可以简单地存储它们的字符串版本。它当然是可读的,但可能难以维护。枚举很容易从字符串转换回来:

public enum TestEnum
{
    MyFirstEnum,
    MySecondEnum
}

static void TestEnums()
{
    string str = TestEnum.MyFirstEnum.ToString();
    Console.WriteLine( "Enum = {0}", str );
    TestEnum e = (TestEnum)Enum.Parse( typeof( TestEnum ), "MySecondEnum", true );
    Console.WriteLine( "Enum = {0}", e );
}

答案 7 :(得分:0)

为什么不尝试将数据库中的枚举完全分开?我发现这篇文章在处理类似的事情时是一个很好的参考:

http://stevesmithblog.com/blog/reducing-sql-lookup-tables-and-function-properties-in-nhibernate/

无论您使用什么数据库,其中的想法都应适用。例如,在MySQL中,您可以使用“枚举”数据类型来强制符合您的编码枚举:

http://dev.mysql.com/doc/refman/5.0/en/enum.html

干杯

答案 8 :(得分:0)

可以通过为Id列名称与表名匹配的每个枚举创建一致表来使用DB第一种方法。在数据库中具有可用的枚举值以支持视图中的外键约束和友好列是有利的。我们目前支持分散在众多版本数据库中的~100个枚举类型。

对于Code-First首选项,下面显示的T4策略可能会被反转以写入数据库。

create table SomeSchema.SomeEnumType (
  SomeEnumTypeId smallint NOT NULL primary key,
  Name varchar(100) not null,
  Description nvarchar(1000),
  ModifiedUtc datetime2(7) default(sysutcdatetime()),
  CreatedUtc datetime2(7) default(sysutcdatetime()),
);

可以使用T4 template (*.tt) script将每个表导入C#。

  1. 创建一个&#34;枚举项目&#34;。添加如下所示的.tt文件。
  2. 为每个数据库架构名称创建一个子文件夹。
  3. 对于每个枚举类型,创建一个名为SchemaName.TableName.tt的文件。文件 内容总是相同的单行:&lt;#@ include 文件=&#34; .. \ EnumGenerator.ttinclude&#34; #&GT;
  4. 然后创建/更新枚举,右键单击1个或多个文件 &#34;运行自定义工具&#34; (我们还没有自动更新)。它会将.cs文件添加/更新到项目中:
  5. using System.CodeDom.Compiler;
    namespace TheCompanyNamespace.Enumerations.Config
    {
        [GeneratedCode("Auto Enum from DB Generator", "10")]
        public enum DatabasePushJobState
        {     
              Undefined = 0,
              Created = 1,        
        } 
        public partial class EnumDescription
        {
           public static string Description(DatabasePushJobState enumeration)
           {
              string description = "Unknown";
              switch (enumeration)
              {                   
                  case DatabasePushJobState.Undefined:
                      description = "Undefined";
                      break;
    
                  case DatabasePushJobState.Created:
                      description = "Created";
                      break;                 
               }
               return description;
           }
        }
        // select DatabasePushJobStateId, Name, coalesce(Description,Name) as Description
        //    from TheDefaultDatabase.[SchName].[DatabasePushJobState]
        //   where 1=1 order by DatabasePushJobStateId 
     }
    

    最后,有点粗糙的T4脚本(从众多变通方法中简化)。它需要根据您的环境进行定制。调试标志可以将消息输出到C#中。还有一个&#34; Debug T4 Template&#34;右键单击.tt文件时的选项。 的 EnumGenerator.ttinclude

    <#@ template debug="true" hostSpecific="true" #>
    <#@ output extension=".generated.cs" #>
    <#@ Assembly Name="EnvDTE" #>
    <#@ Assembly Name="System.Core" #>
    <#@ Assembly Name="System.Data" #>
    <#@ assembly name="$(TargetPath)" #>
    <#@ import namespace="EnvDTE" #>
    <#@ import namespace="System" #>
    <#@ import namespace="System.Collections" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="System.Data" #>
    <#@ import namespace="System.Data.SqlClient" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Text.RegularExpressions" #>
    <#  
        bool doDebug = false;   // include debug statements to appear in generated output    
    
        string schemaTableName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
        string schema = schemaTableName.Split('.')[0];
        string tableName = schemaTableName.Split('.')[1];
    
        string path = Path.GetDirectoryName(Host.TemplateFile);    
        string enumName = tableName;
        string columnId = enumName + "Id";
        string columnName = "Name"; 
        string columnDescription = "Description";
    
        string currentVersion = CompanyNamespace.Enumerations.Constants.Constants.DefaultDatabaseVersionSuffix;
    
        // Determine Database Name using Schema Name
        //
        Dictionary<string, string> schemaToDatabaseNameMap = new Dictionary<string, string> {
            { "Cfg",        "SomeDbName" + currentVersion },
            { "Common",     "SomeOtherDbName" + currentVersion }
            // etc.     
        };
    
        string databaseName;
        if (!schemaToDatabaseNameMap.TryGetValue(schema, out databaseName))
        {
            databaseName = "TheDefaultDatabase"; // default if not in map
        }
    
        string connectionString = @"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=" + databaseName + @";Data Source=Machine\Instance";
    
        schema = "[" + schema + "]";
        tableName = "[" + tableName + "]";
    
        string whereConstraint = "1=1";  // adjust if needed for specific tables
    
      // Get containing project
      IServiceProvider serviceProvider = (IServiceProvider)Host;
      DTE dte = (DTE)serviceProvider.GetService(typeof(DTE));
      Project project = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject;
    #>
    using System;
    using System.CodeDom.Compiler;
    
    namespace <#= project.Properties.Item("DefaultNamespace").Value #><#= Path.GetDirectoryName(Host.TemplateFile).Remove(0, Path.GetDirectoryName(project.FileName).Length).Replace("\\", ".") #>
    {
        /// <summary>
        /// Auto-generated Enumeration from Source Table <#= databaseName + "." + schema + "." + tableName #>.  Refer to end of file for SQL.
        /// Please do not modify, your changes will be lost!
        /// </summary>
        [GeneratedCode("Auto Enum from DB Generator", "10")]
        public enum <#= enumName #>
        {       
    <#
            SqlConnection conn = new SqlConnection(connectionString);
            // Description is optional, uses name if null
            string command = string.Format(
                "select {0}, {1}, coalesce({2},{1}) as {2}" + "\n  from {3}.{4}.{5}\n where {6} order by {0}", 
                    columnId,           // 0
                    columnName,         // 1
                    columnDescription,  // 2
                    databaseName,       // 3
                    schema,             // 4
                    tableName,          // 5
                    whereConstraint);   // 6
            #><#= DebugCommand(databaseName, command, doDebug) #><#
    
            SqlCommand comm = new SqlCommand(command, conn);
    
            conn.Open();
    
            SqlDataReader reader = comm.ExecuteReader();
            bool loop = reader.Read();
    
            while(loop)
            {
    #>      /// <summary>
            /// <#= reader[columnDescription] #>
            /// </summary>
            <#= Pascalize(reader[columnName]) #> = <#= reader[columnId] #><# loop = reader.Read(); #><#= loop ? ",\r\n" : string.Empty #>
    <#
            }
    #>    }
    
    
        /// <summary>
        /// A helper class to return the Description for each enumeration value
        /// </summary>
        public partial class EnumDescription
        {
            public static string Description(<#= enumName #> enumeration)
            {
                string description = "Unknown";
    
                switch (enumeration)
                {<#
        conn.Close();
        conn.Open();
        reader = comm.ExecuteReader();
        loop = reader.Read();
    
        while(loop)
        {#>                 
                        case <#= enumName #>.<#= Pascalize(reader[columnName]) #>:
                            description = "<#= reader[columnDescription].ToString().Replace("\"", "\\\"") #>";
                            break;
                        <# loop = reader.Read(); #>
    <#
          }
          conn.Close();
    #> 
                }
    
                return description;
            }
        }
        /*
            <#= command.Replace("\n", "\r\n        ") #>
        */
    }
    <#+     
        private string Pascalize(object value)
        {
            Regex rxStartsWithKeyWord = new Regex(@"^[0-9]|^abstract$|^as$|^base$|^bool$|^break$|^byte$|^case$|^catch$|^char$|^checked$|^class$|^const$|^continue$|^decimal$|^default$|^delegate$|^do$|^double$|^else$|^enum$|^event$|^explicit$|^extern$|^$false|^finally$|^fixed$|^float$|^for$|^foreach$|^goto$|^if$|^implicit$|^in$|^int$|^interface$|^internal$|^is$|^lock$|^long$|^namespace$|^new$|^null$|^object$|^operator$|^out$|^overrride$|^params$|^private$|^protected$|^public$|^readonly$|^ref$|^return$|^sbyte$|^sealed$|^short$|^sizeof$|^stackalloc$|^static$|^string$|^struct$|^switch$|^this$|^thorw$|^true$|^try$|^typeof$|^uint$|^ulong$|^unchecked$|^unsafe$|^ushort$|^using$|^virtual$|^volatile$|^void$|^while$", RegexOptions.Compiled);
    
            Regex rx = new Regex(@"(?:[^a-zA-Z0-9]*)(?<first>[a-zA-Z0-9])(?<reminder>[a-zA-Z0-9]*)(?:[^a-zA-Z0-9]*)");
            string rawName = rx.Replace(value.ToString(), m => m.Groups["first"].ToString().ToUpper() + m.Groups["reminder"].ToString());
    
            if (rxStartsWithKeyWord.Match(rawName).Success)
                rawName =  "_" + rawName;
    
            return rawName;    
        }
    
        private string DebugCommand(string databaseName, string command, bool doDebug)
        {       
            return doDebug
                ? "        // use " + databaseName + ";  " + command + ";\r\n\r\n"
                : "";
        }   
    #>
    

    希望实体框架有一天会支持这些答案的组合,以便在记录和数据库镜像值中提供C#enum强类型。

答案 9 :(得分:0)

如果要存储整数,则无需执行任何操作。只需在EF中映射您的媒体资源即可。 如果要将它们存储为字符串,请使用转换器。

Int(db类型为smallint):

public override void Configure(EntityTypeBuilder<MyEfEntity> b)
{
    ...
    b.Property(x => x.EnumStatus);
}

字符串(db类型为varchar(50)):

public override void Configure(EntityTypeBuilder<MyEfEntity> b)
{
    ...
    b.Property(x => x.EnumStatus).HasConversion<EnumToStringConverter>();
}

如果要保存数据库数据使用量,请使用smallint作为db中的列。但是数据不会让人可读,因此您应该为每个枚举项设置一个索引,并且永远不要弄乱它们:

public enum EnumStatus
{
    Active = 0, // Never change this index
    Archived = 1, // Never change this index
}

如果要使db中的数据更具可读性,可以将它们另存为字符串(例如varchar(50))。您不必担心索引,更改枚举名称时只需要db中的更新字符串即可。缺点:列大小使数据使用更加昂贵。这意味着如果表中的行数不超过1,000,000行,则可能会影响数据库大小和性能。

作为解决方案,您可以使用简短的枚举名称:

public enum EnumStatus
{
    [Display(Name = "Active")]
    Act,
    [Display(Name = "Archived")]
    Arc,
}

或者使用您自己的转换器使db中的名称更短:

public enum EnumStatus
{
    [Display(Name = "Active", ShortName = "Act")]
    Active,
    [Display(Name = "Archived", ShortName = "Arc")]
    Archived,
}
...
public override void Configure(EntityTypeBuilder<MyEfEntity> b)
{
    ...
    b.Property(x => x.EnumStatus).HasConversion<MyShortEnumsConverter>();
}

更多信息可以在这里找到: EF:https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/data-types/enums EFCore:https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions