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

发布于 2024-08-29 04:45:59 字数 167 浏览 4 评论 0原文

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

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

What is the best method of storing an Enum in a Database using C# And Visual Studio and MySQL Data Connector.

I am going to be creating a new project with over 100 Enums, and majority of them will have to be stored in the database. Creating converters for each one would be a long winded process therefore I'm wondering if visual studio or someone has any methods for this that I haven't heard off.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

倾听心声的旋律 2024-09-05 04:45:59
    [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 。 ps:“[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;
        }
    }
    [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
}

Works like a charm! No need to convert (int)Enum or (Enum)int in code. Just use the enum and ef code first will save the int for you. p.s.: "[EnumDataType(typeof(PhoneTypes))]" attribute is not required, just an extra if you want additional functionality.

Alternatively you can do:

[Required]
    public virtual int PhoneTypeId { get; set; }
    [EnumDataType(typeof(PhoneTypes))]
    public PhoneTypes PhoneType
    {
        get
        {
            return (PhoneTypes)this.PhoneTypeId;
        }
        set
        {
            this.PhoneTypeId = (int)value;
        }
    }
戏舞 2024-09-05 04:45:59

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

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

enum BlockTreatmentType 
{
    All = 0
};

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

We store ours as ints or longs and then we can just cast 'em back and forth. Probably not the most robust solution, but that what we do.

we are using typed DataSets, so, for example:

enum BlockTreatmentType 
{
    All = 0
};

// blockTreatmentType is an int property
blockRow.blockTreatmentType = (int)BlockTreatmentType.All;
BlockTreatmentType btt = (BlockTreatmentType)blockRow.blocktreatmenttype;
哭泣的笑容 2024-09-05 04:45:59

如果您需要将枚举字段的字符串值存储在数据库中,最好如下所示。
例如,如果您使用不支持枚举字段的 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
}

If you need store in DB string values of enum field, better do like show below.
For example, it can be needed if you are using SQLite, which don`t support enum fields.

[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
}
茶色山野 2024-09-05 04:45:59

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

数据库表:

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

If you want a store of all of your enums values, you can try the below tables to store enums and their members, and the code snippet to add those values. I'd only do this at install time, however, since those values will never change until you recompile!

DB Table:

   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);
        }
    }
一影成城 2024-09-05 04:45:59

最后,您将需要一种很好的方法来处理重复的编码任务,例如枚举转换器。您可以使用代码生成器,例如 MyGenerationCodeSmith 等等,或者可能是一个 ORM 映射器,例如 nHibernate< /a> 为您处理一切。

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

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

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

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

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

In the end you will need a great way to deal with repetitious coding tasks such as enum converters. You can use a code generator such as MyGeneration or CodeSmith among many others or perhaps an ORM mapper like nHibernate to handle everything for you.

As for the structure... with hundreds of enums I would first consider trying to organize the data into a single table that might look something like this: (pseudo sql)

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

that would allow you to store your enum info in a single table. EnumType could also be a foreign key to a table that defines the different enums.

Your biz objects would be linked to this table via EnumId. The enum type is there only for organization and filtering in the UI. Utilizing all of this of course depends on your code structure and problem domain.

Btw, in this scenario you would want to set a clustered index on EnumType rather than leaving the default cluster idx that is created on the PKey.

稍尽春風 2024-09-05 04:45:59

有些事情你应该考虑。

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

您的应用程序的 i18n 需求是什么?如果它仅支持一种语言,您可以将枚举保存为文本并创建一个辅助方法来从描述字符串进行转换。您可以使用 [DescriptionAttribute] 来实现此目的,并且可以通过搜索 SO 找到转换方法。

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

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


更新:

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

Some things you should take in consideration.

Is the enumeration column going to be used directly by other applications like for example reports. This will limit the possibility of the enumeration being stored in it's integer format because that value will have no meaning when present in a report unless the reports have custom logic.

What are the i18n needs for your application? If it only supports one language you can save the enumeration as text and create a helper method to convert from a description string. You can use [DescriptionAttribute] for this and methods for the conversion can probably be found by searching SO.

If on the other hand you have the need to support multiple language and external application access to your data you can start considering if enumeration are really the answer. Other option like lookup tables can be considered if the scenario is more complex.

Enumerations are excellent when they are self contained in code... when they cross that border, things tend to get a bit messy.


Update:

You can convert from an integer using Enum.ToObject method. This implies that you know the type of the enumeration when converting. If you want to make it completely generic you need to store the type of the enumeration alongside it's value in the database. You could create data dictionary support tables to tell you which columns are enumerations and what type are them.

月光色 2024-09-05 04:45:59

如果你想存储整数,你不需要做任何事情。只需在 EF 中映射您的财产即可。
如果你想将它们存储为字符串,请使用转换器。

Int(db类型为smallint):

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

String(db类型为varchar(50)):

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

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

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

如果您想让数据库中的数据更具可读性,您可以将它们保存为字符串(例如 varchar(50))。您不必担心索引,只需在更改枚举名称时更新数据库中的字符串即可。缺点:列大小会使数据使用成本更高。这意味着如果您的表的行数在 1,000,000 以内,则可能会对数据库大小和性能产生影响。

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

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

可以在此处找到更多信息:

EF5:https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/data-类型/枚举

EF6:https://learn .microsoft.com/en-us/ef/core/modeling/value-conversions

You don't need to do anything if you want to store ints. Just map your property in EF.
If you want to store them as strings use converter.

Int (db type is smallint):

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

String (db type is varchar(50)):

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

If you want to save your db data usage use smallint as a column in db. But data won't be human readable and you should set an index against every enum item and never mess with them:

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

If you want to make data in db more readable you can save them as strings (e.g. varchar(50)). You don't have to worry about indexes and you just need update strings in db when you change enum names. Cons: column size gets data usage more expensive. It means if you got a table within 1,000,000 rows it might have an impact on db size and performance.

Also as a solution you can use short enum names:

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

Or use your own converter to make names in db shorter:

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

More info can be found here:

EF5: https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/data-types/enums

EF6: https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions

烟凡古楼 2024-09-05 04:45:59

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

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

I'm not sure if it is the most flexible, but you could simply store the string versions of them. It is certainly readable, but maybe difficult to maintain. Enums convert from strings and back pretty easily:

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 );
}
撩人痒 2024-09-05 04:45:59

可以通过为每个枚举创建一致的表来使用数据库优先方法,其中 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 的文件。该文件
    内容始终是同一行:<#@ include
    文件=“..\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.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# 枚举强类型。

A DB first approach can be used by creating a consistent table for each enum where the Id column name matches table name. It's advantageous to have enum values available within the database to support foreign key constraints and friendly columns in views. We are currently supporting ~100 enum types scattered throughout numerous versioned databases.

For a Code-First preference, the T4 strategy shown below could probably be reversed to write to the database.

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()),
);

Each table can be imported into C# using a T4 template (*.tt) script.

  1. Create an "Enumeration Project". Add the .tt file shown below.
  2. Create a sub-folder for each Database Schema name.
  3. For each enum type, create a file whose name is SchemaName.TableName.tt. The file
    contents are always the same single line: <#@ include
    file="..\EnumGenerator.ttinclude" #>
  4. Then to create/update the enums, right click on 1 or more files and
    "Run Custom Tool" (we don't have auto update yet). It will add/update a .cs file to the project:
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 
 }

And finally, the somewhat gnarly T4 script (simplified from numerous workarounds). It will need to be customized to your environment. A debug flag can output messages into the C#. There is also a "Debug T4 Template" option when right clicking the .tt file.
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"
            : "";
    }   
#>

Hopefully the entity framework will someday support a combination of these answers to offer the C# enum strong typing within records and database mirroring of values.

谁人与我共长歌 2024-09-05 04:45:59

如果您使用的是 EntityFrameworkCore。 (我的版本是5.0.10),而不是像这样直接使用Fluent API:(这将在数据库中保存Home/Other。在数据库中创建Type列作为varchar/nvarchar)

在您的上下文文件中,您继承 DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
            modelBuilder
                .Entity<StudentAddress>()
                .Property(address => address.Type)
                .HasConversion(
                    value => value.ToString(),
                    value => (AddressType)Enum.Parse(typeof(AddressType), value));
}    

我的实体:

[Table("Student", Schema = "Student")]
public class Student 
{
    //...
    public AddressType Type { get; set; }
}

我的枚举:

    public enum AddressType
    {
        Home,
        Other
    }

额外注释,不是强制性的,与上面的示例无关: 在我的这个场景中,这不是必需的。但只要需要,您始终可以在数据库级别创建约束。

    ALTER TABLE [Document].[Document] WITH CHECK ADD  CONSTRAINT [CHK_DocumentType] CHECK  (([DocumentType]="DOC" OR [DocumentType]="PDF" OR [DocumentType]="IMAGE" OR [DocumentType]="OTHER"))

    ALTER TABLE [Document].[Document] CHECK CONSTRAINT [CHK_DocumentType]

If you are using EntityFrameworkCore. (mine version is 5.0.10), than directly use Fluent API like this: (This will save Home/Other in DB. create Type columns as varchar/nvarchar in your database)

In your Context file, where you are inheriting the DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
            modelBuilder
                .Entity<StudentAddress>()
                .Property(address => address.Type)
                .HasConversion(
                    value => value.ToString(),
                    value => (AddressType)Enum.Parse(typeof(AddressType), value));
}    

My Entity:

[Table("Student", Schema = "Student")]
public class Student 
{
    //...
    public AddressType Type { get; set; }
}

My Enum:

    public enum AddressType
    {
        Home,
        Other
    }

Extra Notes, not mandatory and not related to above sample: This is not required in my this scenario. But you can always create a constraint as well at DB level, wherever required.

    ALTER TABLE [Document].[Document] WITH CHECK ADD  CONSTRAINT [CHK_DocumentType] CHECK  (([DocumentType]="DOC" OR [DocumentType]="PDF" OR [DocumentType]="IMAGE" OR [DocumentType]="OTHER"))

    ALTER TABLE [Document].[Document] CHECK CONSTRAINT [CHK_DocumentType]
浮华 2024-09-05 04:45:59

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

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

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

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

Why not try separating the enums altogether from the DB? I found this article to be a great reference while working on something similar:

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

The ideas in it should apply regardless of what DB you use. For example, in MySQL you can use the "enum" data type to enforce compliance with your coded enums:

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文