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

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

我将用100个以上的Enum创建一个新项目,其中大多数将必须存储在数据库中。 为每个人创建转换器将是一个漫长的过程,因此我想知道Visual Studio或某人是否有任何我从未听说过的方法。

10个解决方案
57 votes
    [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;
        }
    }
Joao Leme answered 2019-11-08T14:04:57Z
10 votes

我们将它们存储为整型或多型,然后就可以来回转换它们。 可能不是最可靠的解决方案,而是我们的工作。

我们正在使用类型化的数据集,例如:

enum BlockTreatmentType 
{
    All = 0
};

// blockTreatmentType is an int property
blockRow.blockTreatmentType = (int)BlockTreatmentType.All;
BlockTreatmentType btt = (BlockTreatmentType)blockRow.blocktreatmenttype;
Muad'Dib answered 2019-11-08T14:05:30Z
3 votes

最后,您将需要一种很好的方法来处理重复的编码任务,例如枚举转换器。 您可以使用诸如MyGeneration或CodeSmith之类的代码生成器,也可以使用诸如nHibernate之类的ORM映射器来为您处理所有事情。

至于结构...有数百个枚举,我首先会考虑尝试将数据组织到一个看起来像这样的单个表中:(pseudo sql)

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

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

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

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

Paul Sasik answered 2019-11-08T14:06:29Z
3 votes

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

数据库表:

   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#代码段:

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);
        }
    }
cortijon answered 2019-11-08T14:07:10Z
3 votes

您应该考虑一些事情。

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

i18n对您的应用程序有哪些需求? 如果只支持一种语言,则可以将枚举另存为文本,并创建一个帮助方法以从描述字符串进行转换。 您可以为此使用Enum.ToObject,可以通过搜索SO找到转换方法。

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

当枚举完全包含在代码中时,它们是极好的。当它们越过边界时,事情就会变得有些混乱。


更新:

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

João Angelo answered 2019-11-08T14:08:16Z
2 votes

如果需要在枚举字段的DB字符串值中存储,最好做如下所示。例如,如果您使用的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
}
trueboroda answered 2019-11-08T14:08:41Z
1 votes

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

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 );
}
Mark Wilkins answered 2019-11-08T14:09:05Z
0 votes

为什么不尝试将枚举与DB完全分开? 在从事类似工作时,我发现这篇文章是很好的参考:

[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]

干杯

Paul Hanssen answered 2019-11-08T14:09:56Z
0 votes

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

对于“代码优先”首选项,可以将以下所示的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模板(* .tt)脚本将每个表导入C#。

  1. 创建一个“枚举项目”。 添加如下所示的.tt文件。
  2. 为每个数据库架构名称创建一个子文件夹。
  3. 对于每种枚举类型,创建一个名称为SchemaName.TableName.tt的文件。 文件内容总是同一行:<#@ includefile =“ .. \ EnumGenerator.ttinclude”#>
  4. 然后要创建/更新枚举,请右键单击1个或多个文件,然后“运行自定义工具”(我们还没有自动更新)。 它将添加/更新.cs文件到项目:
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#中。 右键单击.tt文件时,还有一个“调试T4模板”选项。EnumGenerator.tt包括:

<#@ 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#枚举提供强类型。

crokusek answered 2019-11-08T14:11:33Z
0 votes

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

整数(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/zh-CN/ef/ef6/modeling/code-first/data-types/enums]EFCore:[https://docs.microsoft.com/zh-CN/ef/core/modeling/value-conversions]

ADM-IT answered 2019-11-08T14:12:59Z
translate from https://stackoverflow.com:/questions/2646498/best-method-to-store-enum-in-database