Development Tip

Enum을 데이터베이스에 저장하는 가장 좋은 방법

yourdevel 2020. 12. 25. 10:35
반응형

Enum을 데이터베이스에 저장하는 가장 좋은 방법


C # 및 Visual Studio 및 MySQL 데이터 커넥터를 사용하여 데이터베이스에 Enum을 저장하는 가장 좋은 방법은 무엇입니까?

100 개가 넘는 Enum으로 새 프로젝트를 만들 예정이며 대부분은 데이터베이스에 저장되어야합니다. 각각에 대한 변환기를 만드는 것은 긴 과정이 될 것이므로 Visual Studio 또는 누군가가 내가 들어 본 적이없는 방법이 있는지 궁금합니다.


    [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가 저장됩니다. 추신 : "[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;
        }
    }

우리는 int 또는 long으로 저장 한 다음 앞뒤로 캐스팅 할 수 있습니다. 아마도 가장 강력한 솔루션은 아니지만 우리가하는 일입니다.

형식화 된 DataSet을 사용하고 있으므로 예를 들면 다음과 같습니다.

enum BlockTreatmentType 
{
    All = 0
};

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

결국 enum 변환기와 같은 반복적 인 코딩 작업을 처리 할 수있는 좋은 방법이 필요합니다. 당신은 같은 코드 생성기 사용할 수 있습니다 MyGeneration 또는 CodeSmith을 많은 다른 사람 또는 아마도 중 nHibernate 수는 같은 ORM 매퍼 당신을 위해 모든 것을 처리 할 수 있습니다.

구조에 관해서는 ... 수백 개의 열거 형을 사용하여 먼저 데이터를 다음과 같은 단일 테이블로 구성하는 것을 고려할 것입니다. (의사 SQL)

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

이를 통해 열거 형 정보를 단일 테이블에 저장할 수 있습니다. EnumType은 다른 열거 형을 정의하는 테이블에 대한 외래 키일 수도 있습니다.

biz 개체는 EnumId를 통해이 테이블에 연결됩니다. 열거 형 유형은 UI의 구성 및 필터링에만 있습니다. 물론이 모든 것을 활용하는 것은 코드 구조와 문제 영역에 달려 있습니다.

Btw,이 시나리오에서는 PKey에 생성 된 기본 클러스터 idx를 그대로 두지 않고 EnumType에 클러스터형 인덱스를 설정하려고합니다.


모든 열거 형 값을 저장하려면 아래 표를 사용하여 열거 형과 해당 멤버를 저장하고 코드 조각을 사용하여 해당 값을 추가 할 수 있습니다. 그러나이 값은 다시 컴파일 할 때까지 변경되지 않으므로 설치시에만이 작업을 수행합니다!

DB 테이블 :

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

고려해야 할 몇 가지 사항.

예를 들어 보고서와 같은 다른 응용 프로그램에서 직접 사용할 열거 형 열입니다. 보고서에 사용자 지정 논리가없는 한 보고서에 표시되는 값은 의미가 없으므로 정수 형식으로 열거 형이 저장 될 가능성이 제한됩니다.

애플리케이션에 필요한 i18n은 무엇입니까? 하나의 언어 만 지원하는 경우 열거 형을 텍스트로 저장하고 설명 문자열에서 변환 할 도우미 메서드를 만들 수 있습니다. [DescriptionAttribute]이를 위해 사용할 수 있으며 변환 방법은 SO를 검색하여 찾을 수 있습니다.

반면에 데이터에 대한 다중 언어 및 외부 애플리케이션 액세스를 지원해야하는 경우 열거가 실제로 해답인지 고려할 수 있습니다. 시나리오가 더 복잡한 경우 조회 테이블과 같은 다른 옵션을 고려할 수 있습니다.

열거 형은 코드에 자체 포함되어있을 때 훌륭합니다 ... 그 경계를 넘으면 상황이 약간 지저분 해지는 경향이 있습니다.


최신 정보:

Enum.ToObject메서드를 사용하여 정수에서 변환 할 수 있습니다 . 이는 변환 할 때 열거 유형을 알고 있음을 의미합니다. 완전히 제네릭으로 만들고 싶다면 데이터베이스에 열거 형 값과 함께 열거 형을 저장해야합니다. 데이터 딕셔너리 지원 테이블을 만들어 어떤 열이 열거이고 어떤 유형인지 알 수 있습니다.


enum 필드의 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
}

가장 유연한 지 확실하지 않지만 단순히 문자열 버전을 저장할 수 있습니다. 확실히 읽을 수 있지만 유지하기 어려울 수 있습니다. 열거 형은 문자열에서 쉽게 변환됩니다.

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

DB에서 열거 형을 모두 분리 해 보는 것은 어떨까요? 비슷한 작업을하는 동안이 기사가 훌륭한 참고 자료라는 것을 알았습니다.

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

그 아이디어는 어떤 DB를 사용하든 상관없이 적용되어야합니다. 예를 들어 MySQL에서 "enum"데이터 유형을 사용하여 코딩 된 열거 형을 준수하도록 할 수 있습니다.

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

건배


DB 우선 접근 방식은 Id 열 이름이 테이블 이름과 일치하는 각 열거 형에 대해 일관된 테이블을 생성하여 사용할 수 있습니다. 데이터베이스 내에서 enum 값을 사용하여 뷰에서 외래 키 제약 조건과 친숙한 열을 지원하는 것이 유리합니다. 현재 다양한 버전의 데이터베이스에 흩어져있는 ~ 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 file = ".. \ EnumGenerator.ttinclude"#>
  4. 그런 다음 열거 형을 생성 / 업데이트하려면 하나 이상의 파일을 마우스 오른쪽 버튼으로 클릭하고 "사용자 정의 도구 실행"(아직 자동 업데이트가 없습니다). 프로젝트에 .cs 파일을 추가 / 업데이트합니다.
System.CodeDom.Compiler 사용;
네임 스페이스 TheCompanyNamespace.Enumerations.Config
{
    [GeneratedCode ( "DB 생성기에서 자동 열거", "10")]
    공용 열거 형 DatabasePushJobState
    {     
          정의되지 않음 = 0,
          생성됨 = 1,        
    } 
    공용 부분 클래스 EnumDescription
    {
       공용 정적 문자열 설명 (DatabasePushJobState 열거 형)
       {
          문자열 설명 = "알 수 없음";
          스위치 (열거 형)
          {                   
              case DatabasePushJobState.Undefined :
                  설명 = "정의되지 않음";
                  단절;

              case DatabasePushJobState.Created :
                  설명 = "생성됨";
                  단절;                 
           }
           반환 설명;
       }
    }
    // 설명으로 DatabasePushJobStateId, Name, coalesce (Description, Name)을 선택합니다.
    // TheDefaultDatabase. [SchName]. [DatabasePushJobState]에서
    // 여기서 1 = 1 DatabasePushJobStateId 별 주문 
 }

마지막으로, 다소 형편없는 T4 스크립트 (많은 해결 방법으로 단순화 됨). 사용자 환경에 맞게 사용자 정의해야합니다. 디버그 플래그는 메시지를 C #으로 출력 할 수 있습니다. .tt 파일을 마우스 오른쪽 버튼으로 클릭하면 "Debug T4 Template"옵션도 있습니다. 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 # 열거 형 강력한 형식 지정 및 값의 데이터베이스 미러링을 제공 할 것입니다.


int를 저장하려면 아무것도 할 필요가 없습니다. 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>();
}

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: 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

ReferenceURL : https://stackoverflow.com/questions/2646498/best-method-to-store-enum-in-database

반응형