web-dev-qa-db-ja.com

データベースに列挙型を保存する最良の方法

C#およびVisual StudioとMySQL Data Connectorを使用してデータベースにEnumを保存する最良の方法は何ですか。

100を超えるEnumを持つ新しいプロジェクトを作成する予定であり、それらの大部分はデータベースに保存する必要があります。それぞれにコンバーターを作成するのは時間がかかるので、ビジュアルスタジオや他の誰かが私が聞いたことのない方法を持っているのではないかと思っています。

56
Michal Ciechan
    [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;
        }
    }
54
Joao Leme

Intまたはlongとして保存し、それらを前後にキャストできます。おそらく最も堅牢なソリューションではありませんが、それは私たちが行うことです。

型指定されたデータセットを使用しているため、たとえば次のようになります。

enum BlockTreatmentType 
{
    All = 0
};

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

すべての列挙値のストアが必要な場合は、以下の表を使用して列挙とそのメンバーを保存し、コードスニペットを使用してそれらの値を追加できます。ただし、これらの値は再コンパイルするまで変更されないため、インストール時にのみこれを行います。

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

考慮すべきいくつかのこと。

列挙列は、レポートなどの他のアプリケーションで直接使用されますか?これにより、レポートにカスタムロジックがない限り、その値はレポートに存在しても意味を持たないため、列挙が整数形式で格納される可能性が制限されます。

アプリケーションに必要なi18nは何ですか? 1つの言語のみをサポートしている場合、列挙をテキストとして保存し、説明文字列から変換するヘルパーメソッドを作成できます。これには[DescriptionAttribute]を使用でき、SOを検索することで変換の方法を見つけることができます。

一方、データへの複数の言語および外部アプリケーションのアクセスをサポートする必要がある場合、列挙が本当に答えであるかどうかを検討することができます。シナリオがより複雑な場合は、ルックアップテーブルなどの他のオプションを検討できます。

列挙型は、コードに自己完結している場合に優れています。境界線を越えると、物事が少し面倒になりがちです。


更新:

Enum.ToObjectメソッドを使用して整数から変換できます。これは、変換時に列挙型を知っていることを意味します。完全に汎用的にしたい場合は、列挙型を値とともにデータベースに保存する必要があります。データディクショナリサポートテーブルを作成して、どの列が列挙型であり、どの型であるかを示すことができます。

3
João Angelo

最終的には、enumコンバーターなどの反復的なコーディングタスクに対処するための優れた方法が必要になります。 MyGenerationCodeSmith などのコードジェネレーター、またはおそらく nHibernateのようなORMマッパー を使用して、すべてを処理できます。

構造については...数百の列挙型を使用して、最初にデータを次のような単一のテーブルに整理することを検討します:(擬似SQL)

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

これにより、列挙情報を単一のテーブルに保存できます。 EnumTypeは、さまざまな列挙型を定義するテーブルへの外部キーにすることもできます。

BizIdオブジェクトは、EnumIdを介してこのテーブルにリンクされます。列挙型は、UIでの組織化とフィルタリングのためだけにあります。もちろん、これをすべて利用するかどうかは、コード構造と問題の領域に依存します。

ところで、このシナリオでは、PKeyで作成された既定のクラスターidxを残すのではなく、EnumTypeにクラスター化インデックスを設定する必要があります。

3
Paul Sasik

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
}
2
trueboroda

それが最も柔軟かどうかはわかりませんが、単純に文字列バージョンを保存できます。確かに読みやすいですが、保守が難しいかもしれません。列挙型は文字列から非常に簡単に変換します:

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 );
}
1
Mark Wilkins

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

データベースのデータ使用量を保存する場合は、dbの列としてsmallintを使用します。しかし、データは人間が読めるものではないため、すべての列挙項目に対してインデックスを設定する必要があります。

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

Dbのデータを読みやすくしたい場合は、文字列として保存できます(例:varchar(50))。インデックスについて心配する必要はなく、列挙名を変更するときにdbの更新文字列だけが必要です。短所:列サイズにより、データ使用量が高くなります。つまり、1,000,000行以内のテーブルを取得した場合、dbのサイズとパフォーマンスに影響を与える可能性があります。

また、解決策として、短い列挙名を使用できます。

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/en-us/ef/ef6/modeling/code-first/data-types/enums EFCore: https://docs.Microsoft.com/en-us/ef/core/modeling/value-conversions

0
ADM-IT

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

乾杯

0
Paul Hanssen

ID列名がテーブル名と一致する列挙ごとに一貫したテーブルを作成することにより、DBファーストアプローチを使用できます。ビューで外部キー制約とわかりやすい列をサポートするために、データベース内で列挙値を使用できると便利です。現在、多数のバージョン管理されたデータベースに散在する〜100の列挙型をサポートしています。

Code-Firstの設定では、以下に示す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のファイルを作成します。ファイルの内容は常に同じ1行です:<#@ include file = "..\EnumGenerator.ttinclude"#>
  4. 次に、enumを作成/更新するには、1つ以上のファイルを右クリックして、「カスタムツールを実行」を実行します(まだ自動更新はありません)。プロジェクトに.csファイルを追加/更新します。
using System.CodeDom.Compiler; 
 namespace TheCompanyNamespace.Enumerations.Config 
 {
 [GeneratedCode( "DBジェネレーターからの自動列挙"、 "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; 
} 
} 
 // Description [.____。としてDatabasePushJobStateId、Name、coalesce(Description、Name)を選択します。 ] // TheDefaultDatabase。[SchName]。[DatabasePushJobState] 
から// // DatabasePushJobStateId 
} 
による1 = 1の順序

そして最後に、幾分厄介な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#列挙型の強い型付けと値のデータベースミラーリングを提供することを願っています。

0
crokusek