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

C# 在数据库中存储枚举的最佳方法,c#,database,visual-studio,database-design,enums,C#,Database,Visual Studio,Database Design,Enums,使用C#和Visual Studio以及MySQL Data Connector在数据库中存储枚举的最佳方法是什么 我将要创建一个新的项目,有超过100个枚举,其中大部分将必须存储在数据库中。为每个转换器创建转换器将是一个冗长的过程,因此我想知道visual studio或其他人是否有我没有听说过的任何方法。最后,您将需要一种很好的方法来处理重复的编码任务,例如枚举转换器。您可以使用一个代码生成器,例如或其他许多代码生成器,或者可以使用一个代码生成器来为您处理所有事情 至于结构。。。在数百个枚举

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


我将要创建一个新的项目,有超过100个枚举,其中大部分将必须存储在数据库中。为每个转换器创建转换器将是一个冗长的过程,因此我想知道visual studio或其他人是否有我没有听说过的任何方法。

最后,您将需要一种很好的方法来处理重复的编码任务,例如枚举转换器。您可以使用一个代码生成器,例如或其他许多代码生成器,或者可以使用一个代码生成器来为您处理所有事情

至于结构。。。在数百个枚举中,我会首先尝试将数据组织到一个可能看起来像这样的单个表中:(伪SQL)

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

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


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

我们将我们的索引存储为int或long,然后我们可以来回转换它们。可能不是最可靠的解决方案,但这正是我们所做的

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

enum BlockTreatmentType 
{
    All = 0
};

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

有些事情你应该考虑一下

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

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

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

当枚举在代码中是自包含的时,它们是非常优秀的。。。当他们越过边界时,事情往往变得有点混乱


更新:


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

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

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

如果要存储所有枚举值,可以尝试使用下表存储枚举及其成员,并使用代码段添加这些值。不过,我只会在安装时这样做,因为在您重新编译之前,这些值永远不会更改

数据库表:

   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:Enum
{
类型enumToStore=类型(T);
字符串enumName=enumToStore.Name;
int enumKey=DataAccessLayer.CreateEnum(enumName);
foreach(Enum.GetValues(enumToStore)中的int-enumMemberValue)
{
字符串enumMemberName=Enum.GetName(enumToStore,enumMemberValue);
DataAccessLayer.AddEnumMember(enumKey、enumMemberValue、enumMemberName);
}
}

为什么不尝试将枚举与数据库完全分离?我发现这篇文章是一个很好的参考,同时也在做一些类似的工作:

无论您使用什么数据库,其中的思想都应该适用。例如,在MySQL中,您可以使用“enum”数据类型强制遵守编码的enum:

干杯

工作起来很有魅力!无需在代码中转换(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;
        }
    }

若您需要将枚举字段的字符串值存储在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
}

可以通过为Id列名与表名匹配的每个枚举创建一致的表来使用DB first方法。数据库中有可用的枚举值以支持外键约束和视图中的友好列是有利的。我们目前支持分布在众多版本数据库中的大约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()),
);
每个表都可以使用

  • 创建一个“枚举项目”。添加如下所示的.tt文件
  • 为每个数据库架构名称创建一个子文件夹
  • 对于每个枚举类型,创建一个名为SchemaName.TableName.tt的文件。档案 内容始终是同一行:
  • 然后,要创建/更新枚举,右键单击一个或多个文件并 “运行自定义工具”(我们还没有自动更新)。它将向项目中添加/更新.cs文件:
  • 使用System.CodeDom.Compiler; 名称空间companynamespace.Enumerations.Config { [GeneratedCode(“从数据库生成器自动枚举”,“10”)] 公共枚举数据库PushJobState {
    [Required]
        public virtual int PhoneTypeId { get; set; }
        [EnumDataType(typeof(PhoneTypes))]
        public PhoneTypes PhoneType
        {
            get
            {
                return (PhoneTypes)this.PhoneTypeId;
            }
            set
            {
                this.PhoneTypeId = (int)value;
            }
        }
    
    [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
    }
    
    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()),
    );
    
    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 }
    <#@ 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"
                : "";
        }   
    #>
    
    public override void Configure(EntityTypeBuilder<MyEfEntity> b)
    {
        ...
        b.Property(x => x.EnumStatus);
    }
    
    public override void Configure(EntityTypeBuilder<MyEfEntity> b)
    {
        ...
        b.Property(x => x.EnumStatus).HasConversion<EnumToStringConverter>();
    }
    
    public enum EnumStatus
    {
        Active = 0, // Never change this index
        Archived = 1, // Never change this index
    }
    
    public enum EnumStatus
    {
        [Display(Name = "Active")]
        Act,
        [Display(Name = "Archived")]
        Arc,
    }
    
    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>();
    }