2010-04-15 4 views
46

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

나는 100 개가 넘는 Enum이있는 새로운 프로젝트를 만들 예정이며, 대다수는 데이터베이스에 저장해야합니다. 각 하나에 대한 변환기를 만드는 것은 긴 바람이 끊긴 프로세스 따라서 Visual Studio 또는 누군가가 내가 듣지 못한 이것에 대한 어떤 방법이 있는지 궁금하네요.

+0

+1 누군가가보기를 원한다면 이런 종류의 일이 별도의 테이블과 FK 제약 또는 규칙적인 제약으로 적용되어야하는지에 대한 의견에 관심이 있습니까? –

+2

@Martin 이것은 주관적인 경향이 있으므로 응답을 계획하지 않지만 OLTP/ODS의 경우 FK 제약 조건이있는 별도의 테이블을 사용합니다. DSS보고 솔루션의 경우 엔 enum의 기호 이름 (또는 설명)을 비정규 화하여보고 테이블에 다른 사실과 함께 저장합니다. –

답변

42
[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 
} 

매력처럼 작동합니다! 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; 
     } 
    } 
3

결국에는 열거 형 변환기와 같은 반복 코딩 작업을 처리하는 좋은 방법이 필요합니다. 많은 사람들 중에 MyGeneration 또는 CodeSmith과 같은 코드 생성기를 사용하거나 모든 것을 처리하는 코드 생성기 ORM mapper like nHibernate을 사용할 수 있습니다. (의사 SQL)

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

을 허용 것이라고 : 구조에 관해서는 ... 내가 처음과 같이 보일 수있는 그 하나의 테이블에 데이터를 구성하려고 노력이라고 생각 열거 수백와

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

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

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

+0

열거 형을 저장하는 재미있는 방법. 하지만 Enum (C# 기반)을 사용하려는 주요 방법은 가독성을위한 것입니다. if (something.Type == Enum.EnumType)이 프로젝트는 매우 길고 복잡한 프로젝트가 될 것이고 100을 열거하면 매번 추적하고 데이터베이스로 돌아가는 것이 어려울 것입니다. –

10

우리는 ints 또는 longs로 우리를 저장하고 우리는 그냥 앞뒤로 캐스팅 할 수 있습니다. 아마도 가장 강력한 솔루션이 아닌, 우리가하는 일입니다.

우리는 예를 들어, 입력 된 데이터 세트를 사용하여, 이렇게됩니다

eunum BlockTreatmentType 
{ 
    All = 0 
}; 

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

어떻게 캐스팅합니까? 그리고 무엇을 저장합니까? 서수 값 또는 사용자 지정 특성? –

+0

코드 부분을 –

+2

허용되는 솔루션과 같은 이음새가 추가되었습니다. 사람들이 생각해내는 것을 잠시 보면서 질문을 공개하겠습니다. 나는 순서/추기경 변경을 방지하기 위해 열거 형의 int 값을 수동으로 설정했다고 가정합니다. –

3

어떤 일을 당신이 고려해야한다.

예를 들어 보고서와 같은 다른 응용 프로그램에서 직접 열거 열을 사용하게됩니까? 이렇게하면 열거 형이 정수 형식으로 저장 될 가능성이 제한되므로 보고서에 사용자 지정 논리가 없으면 해당 값은 아무런 의미가 없습니다.

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

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

열거 형은 코드에 자체 포함되어있을 때 우수합니다 ... 테두리를 교차 할 때 상황이 조금 엉망이되는 경향이 있습니다.


업데이트 :

당신은 Enum.ToObject 방법을 사용하여 정수로 변환 할 수 있습니다. 이것은 변환 할 때 열거 형의 유형을 알고 있음을 의미합니다.완전하게 일반화하려면 열거 형의 유형을 데이터베이스의 값과 함께 저장해야합니다. 데이터 딕셔너리 지원 테이블을 생성하여 어떤 열이 열거 형이고 어떤 열거 형인지 알 수 있습니다.

+0

이상적인 보고서는 열거 형을 저장하는 동일한 라이브러리를 사용하여 작성되므로 데이터베이스에 정수를 저장하는 것이 완벽합니다. 유일한 문제는 데이터베이스에서 어떻게 추출하여 원본 열거 형으로 변환합니까? (값이 + 5 이상인 enum 각각 100 개 이상) –

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

당신이 당신의 열거 값의 모든 상점, 당신은 열거하고 그 구성원을 저장하기 위해 아래의 테이블을 시도 할 수 있습니다 원하는 경우, 그리고 코드는 해당 값을 추가 할 수 있습니다. 그러나 설치 시간에이 작업을 수행하는 것은 재 컴파일 할 때까지 변경되지 않습니다.

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

가 왜 DB에서 모두 열거 형을 분리하려고하지? 거기에

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

아이디어에 관계없이 사용 DB를 무엇을 적용해야합니다 : 비슷한 작업을하는 동안 나는 좋은 참조가 될이 문서를 발견했다. 예를 들어, MySQL의에서 당신은 당신의 코드 열거 준수를 적용 할 "열거"데이터 유형을 사용할 수 있습니다

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

건배

2

을하면 아래 표와 같이 할 더 열거 필드의 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 
} 
+1

전환 빈도 (예 : 50k 개체)에 따라 ToString 및 Enum.Parse와 같은 전환 방법으로 전환/확장 방법을 사용하는 것이 좋습니다. 반사는 리플렉션을 사용합니다. 그냥 FYI –

+0

예. 나는 동의한다. 그러나 작은 하중에 대해서는 쉬운 솔루션이 적합합니다. – trueboroda

0

DB 첫 번째 방법은 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 template (*.tt) script을 사용하여 C#으로 가져올 수 있습니다.

  1. "열거 프로젝트"를 만듭니다. 아래에 표시된 .tt 파일을 추가하십시오.
  2. 각 데이터베이스 스키마 이름에 대한 하위 폴더를 만듭니다.
  3. 각 열거 형식에 대해 이름이 SchemaName.TableName.tt 인 파일을 만듭니다. 파일 내용은 항상 같은 한 줄입니다 < #를 @가 파일을 포함 = ".. \ 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 파일을 마우스 오른쪽 버튼으로 클릭 할 때 "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#을 열거 강력한 입력을 제공하기 위해 이러한 답변의 조합을 지원합니다.

관련 문제