データベースルックアップテーブルの値に基づいてEnumを自動的に作成しますか?


116

列挙型を自動的に作成し、データベースルックアップテーブルの値(エンタープライズライブラリデータレイヤーを使用)に基づいてC#でその値を使用するにはどうすればよいですか?

たとえば、データベースに新しいルックアップ値を追加する場合、静的列挙型値宣言を手動でコードに追加する必要はありません。列挙型とデータベースの同期を維持したいのですが。

このようなものはありますか?


コード生成の静的列挙型を作成したくありません(コードプロジェクトの記事「列挙型コードジェネレーター-データベースルックアップテーブルから列挙型コードを自動的に生成する」のように)。完全に自動化したいのですが。


より良い解決策がある方法で列挙を使用しようとしている可能性はありますか?
ダン

私は@Danと一緒です。これを行うにはもっと良い方法が必要です。
N_A

@mydogisboxより良い方法は何ですか?
eran otzap

@eranotzer実際、少し考えた後、DBにクエリを実行してそこから列挙型を生成する事前構築ステップを書くのは非常に簡単です
N_A

1
そうは言っても、「コードで生成された静的列挙型を作成したくない」とは彼の意味がわからないので、おそらくこれはニーズに適合しません。
N_A 2012

回答:


97

私はこれを正確に行っています、これを機能させるには、何らかのコード生成を行う必要があります。

私のソリューションでは、プロジェクト「EnumeratedTypes」を追加しました。これは、データベースからすべての値を取得し、それらから列挙型を構築するコンソールアプリケーションです。次に、すべての列挙型をアセンブリに保存します。

列挙型生成コードは次のとおりです。

// Get the current application domain for the current thread
AppDomain currentDomain = AppDomain.CurrentDomain;

// Create a dynamic assembly in the current application domain,
// and allow it to be executed and saved to disk.
AssemblyName name = new AssemblyName("MyEnums");
AssemblyBuilder assemblyBuilder = currentDomain.DefineDynamicAssembly(name,
                                      AssemblyBuilderAccess.RunAndSave);

// Define a dynamic module in "MyEnums" assembly.
// For a single-module assembly, the module has the same name as the assembly.
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(name.Name,
                                  name.Name + ".dll");

// Define a public enumeration with the name "MyEnum" and an underlying type of Integer.
EnumBuilder myEnum = moduleBuilder.DefineEnum("EnumeratedTypes.MyEnum",
                         TypeAttributes.Public, typeof(int));

// Get data from database
MyDataAdapter someAdapter = new MyDataAdapter();
MyDataSet.MyDataTable myData = myDataAdapter.GetMyData();

foreach (MyDataSet.MyDataRow row in myData.Rows)
{
    myEnum.DefineLiteral(row.Name, row.Key);
}

// Create the enum
myEnum.CreateType();

// Finally, save the assembly
assemblyBuilder.Save(name.Name + ".dll");

ソリューション内の他のプロジェクトは、この生成されたアセンブリを参照します。その結果、コードで動的列挙型を使用して、インテリセンスを完成させることができます。

次に、ビルド後のイベントを追加して、この「EnumeratedTypes」プロジェクトがビルドされた後、それ自体が実行され、「MyEnums.dll」ファイルが生成されるようにしました。

ちなみに、「EnumeratedTypes」が最初にビルドされるようにプロジェクトのビルド順序を変更すると役立ちます。そうしないと、動的に生成された.dllの使用を開始すると、.dllが削除された場合にビルドを実行できなくなります。(鶏肉と卵の種類の問題-ソリューション内の他のプロジェクトでは、この.dllを正しくビルドする必要があり、ソリューションをビルドするまで.dllを作成できません...)

このmsdnの記事から上記のコードのほとんどを入手しました。

お役に立てれば!


7
結果の実行可能ファイルをポストビルドで実行する方法がわからない場合:1)プロジェクトを右クリック2)プロパティをクリックします3)ビルドイベントをクリックします4)「ポストビルドイベントコマンドライン」テキストボックスに入力します$(TargetPath)
Miguel

このリンクで言及されているように、カスタム属性定義で動的列挙を行うことは可能ですか?
バラグルナサンマリムトゥ

49

列挙型はコンパイル時に指定する必要があります。実行時に列挙型を動的に追加することはできません。なぜコードでそれらを使用/参照しないのでしょうか。

Professional C#2008から:

C#の列挙型の本当の力は、背後でそれらが基本クラスSystem.Enumから派生した構造体としてインスタンス化されることです。これは、いくつかの有用なタスクを実行するためにそれらに対してメソッドを呼び出すことが可能であることを意味します。.NET Frameworkの実装方法が原因で、列挙型を構文的に構造体として処理することに関連するパフォーマンスの損失がないことに注意してください。実際には、コードがコンパイルされると、列挙型はintやfloatと同様にプリミティブ型として存在します。

したがって、Enumsを思い通りに使用できるかどうかはわかりません。


1
billfredtomの推論が何であるかはわかりませんが、私のコードでは、特定のキーを手動で文字列検索してコードに組み込むのではなく、弱い文字列ではなく、強く型付けされた値に対してロジックを実行できるようにしたいだけです。動的に生成されたEnumに依存するコードがあるため、データベースから値を削除すると、次回コードをコンパイルしようとすると失敗します。
Pandincus

14
ポスターと18の賛成票はちょっと彼のポイントを逃した。実行時の動的列挙ではなく、生成された列挙が必要なようです。
Matt Mitchell

+1。列挙型は、基本的に整数定数を定義するもう1つの方法です(System.Enumいくつかの追加機能がある場合でも)。書く代わりにconst int Red=0, Green=1, Blue=3;あなたは書くenum { Red, Green, Blue }。定数は定義により定数であり、動的ではありません。
Olivier Jacot-Descombes

2
@Oliverセマンティクスについて議論したいのであれば、そうです。しかし、私はGraphainのコメントに同意します-OPは生成された列挙型を探していると思います。彼は、列挙値をデータベースから取得し、ハードコーディングする必要がないことを望んでいます。
Pandincus

1
または... web.configの誰かが私の電子メールテンプレートコードの電子メールテンプレートのトークンタイプを定義することを許可すると言います。これらの文字列型を表すEmailTokensという既存の列挙型が、web.configで定義されている型に基づいて生成されると便利です。したがって、誰かが私のキー値を介してwebconfigに新しい電子メールトークンを追加した場合(例: "Email、FName")、EmailTemplate.Emailなどのこれらのトークンを表すために使用する列挙が既にある場合、 web.configのそのキーに新しい文字列トークンを追加すると、私の列挙型が自動的にconstを追加します
PositiveGuy

18

実際の列挙型である必要がありますか?Dictionary<string,int>代わりに使用するのはどうですか?

例えば

Dictionary<string, int> MyEnum = new Dictionary(){{"One", 1}, {"Two", 2}};
Console.WriteLine(MyEnum["One"]);

11
私はそれをこのようにしようとはしません。コンパイル時のチェックを失い、タイプミスをしやすくなります。列挙型のすべての利点がなくなりました。文字列定数を導入することもできますが、その後は元の場所に戻ります。
DanielBrückner

1
同意する。ただし、タイプミスした文字列は実行時に捕捉されることを覚えておいてください。すべての列挙型メンバーをカバーするテストケースを追加するだけです。
Autodidact

1
リテラルの代わりに定数を使用する場合、タイプミスは問題ではありません
Maslow

@Maslow文字列定数ではなく列挙型を意味すると想定します。
Matt Mitchell、

4
+1。辞書またはHashSetの使用は、動的な列挙型に最も近いものになります。完全に動的とは、実行時に発生するため、実行時にエラーチェックを実行する必要があることを意味します。
Olivier Jacot-Descombes

13

私はこれで T4テンプレートで。プロジェクトに.ttファイルをドロップし、ビルド前のステップとしてT4テンプレートを実行するようにVisual Studioを設定することは、かなり簡単です。

T4は.csファイルを生成します。つまり、データベースにクエリを実行し、その結果から.csファイルに列挙型を作成できます。ビルド前のタスクとして配線すると、ビルドごとに列挙型が再作成されますが、代わりに必要に応じてT4を手動で実行することもできます。


12

あなたのDBに次のものがあったとしましょう:

table enums
-----------------
| id | name     |
-----------------
| 0  | MyEnum   |
| 1  | YourEnum |
-----------------

table enum_values
----------------------------------
| id | enums_id | value | key    |
----------------------------------
| 0  | 0        | 0     | Apple  |
| 1  | 0        | 1     | Banana |
| 2  | 0        | 2     | Pear   |
| 3  | 0        | 3     | Cherry |
| 4  | 1        | 0     | Red    |
| 5  | 1        | 1     | Green  |
| 6  | 1        | 2     | Yellow |
----------------------------------

必要な値を取得する選択を作成します。

select * from enums e inner join enum_values ev on ev.enums_id=e.id where e.id=0

列挙型のソースコードを作成すると、次のような結果が得られます。

String enumSourceCode = "enum " + enumName + "{" + enumKey1 + "=" enumValue1 + "," + enumKey2 + ... + "}";

(明らかに、これは何らかのループで構築されます。)

次に、楽しい部分、列挙型のコンパイルと使用:

CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cs = new CompilerParameters();
cp.GenerateInMemory = True;

CompilerResult result = provider.CompileAssemblyFromSource(cp, enumSourceCode);

Type enumType = result.CompiledAssembly.GetType(enumName);

これで、型がコンパイルされ、使用できるようになりました。
DBに保存されている列挙値を取得するには、次のように使用できます。

[Enum].Parse(enumType, value);

ここで、値は整数値(0、1など)または列挙テキスト/キー(Apple、バナナなど)のいずれかです。


4
これは実際にどのように役立ちますか?タイプセーフやインテリセンスはありません。基本的に、定数を使用するのは、とにかく値を指定する必要があるため、より複雑な方法にすぎません。
ルネボー

2
サニ-完璧!これはまさに私が必要としたものでした。このような理由を疑う人のために、私はプロパティを列挙型の名前に設定する必要があるベンダーライブラリを使用しています。列挙は、同じオブジェクトの異なるプロパティの有効な値の範囲を制限します。私の場合、データベースからの有効な値の範囲を含むメタデータをロードしています。いいえ、ベンダーコードは、任意のタイプのコレクションをプロパティに渡すことをサポートしていません。ありがとう

10

答えを出すだけ 「既成の」コードといくつかの説明でPandincusをです:この例には2つのソリューションが必要です(私はそれも1つで実行できることを知っています;)、上級の学生にそれを提示させます...

だからここにテーブルのDDL SQLがあります:

USE [ocms_dev]
    GO

CREATE TABLE [dbo].[Role](
    [RoleId] [int] IDENTITY(1,1) NOT NULL,
    [RoleName] [varchar](50) NULL
) ON [PRIMARY]

だからここにdllを生成するコンソールプログラムがあります:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Data.Common;
using System.Data;
using System.Data.SqlClient;

namespace DynamicEnums
{
    class EnumCreator
    {
        // after running for first time rename this method to Main1
        static void Main ()
        {
            string strAssemblyName = "MyEnums";
            bool flagFileExists = System.IO.File.Exists (
                   AppDomain.CurrentDomain.SetupInformation.ApplicationBase + 
                   strAssemblyName + ".dll"
            );

            // Get the current application domain for the current thread
            AppDomain currentDomain = AppDomain.CurrentDomain;

            // Create a dynamic assembly in the current application domain,
            // and allow it to be executed and saved to disk.
            AssemblyName name = new AssemblyName ( strAssemblyName );
            AssemblyBuilder assemblyBuilder = 
                    currentDomain.DefineDynamicAssembly ( name,
                            AssemblyBuilderAccess.RunAndSave );

            // Define a dynamic module in "MyEnums" assembly.
            // For a single-module assembly, the module has the same name as
            // the assembly.
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule (
                    name.Name, name.Name + ".dll" );

            // Define a public enumeration with the name "MyEnum" and
            // an underlying type of Integer.
            EnumBuilder myEnum = moduleBuilder.DefineEnum (
                    "EnumeratedTypes.MyEnum",
                    TypeAttributes.Public,
                    typeof ( int )
            );

            #region GetTheDataFromTheDatabase
            DataTable tableData = new DataTable ( "enumSourceDataTable" );

            string connectionString = "Integrated Security=SSPI;Persist " +
                    "Security Info=False;Initial Catalog=ocms_dev;Data " +
                    "Source=ysg";

            using (SqlConnection connection = 
                    new SqlConnection ( connectionString ))
            {

                SqlCommand command = connection.CreateCommand ();
                command.CommandText = string.Format ( "SELECT [RoleId], " + 
                        "[RoleName] FROM [ocms_dev].[dbo].[Role]" );

                Console.WriteLine ( "command.CommandText is " + 
                        command.CommandText );

                connection.Open ();
                tableData.Load ( command.ExecuteReader ( 
                        CommandBehavior.CloseConnection
                ) );
            } //eof using

            foreach (DataRow dr in tableData.Rows)
            {
                myEnum.DefineLiteral ( dr[1].ToString (),
                        Convert.ToInt32 ( dr[0].ToString () ) );
            }
            #endregion GetTheDataFromTheDatabase

            // Create the enum
            myEnum.CreateType ();

            // Finally, save the assembly
            assemblyBuilder.Save ( name.Name + ".dll" );
        } //eof Main 
    } //eof Program
} //eof namespace 

これは、出力を印刷するコンソールプログラミングです(dllを参照する必要があることに注意してください)。受講生に、すべてを1つのソリューションに組み合わせて動的な読み込みを行い、ビルドDLLがあるかどうかを確認するためのソリューションを提示します。

// add the reference to the newly generated dll
use MyEnums ; 

class Program
{
    static void Main ()
    {
        Array values = Enum.GetValues ( typeof ( EnumeratedTypes.MyEnum ) );

        foreach (EnumeratedTypes.MyEnum val in values)
        {
            Console.WriteLine ( String.Format ( "{0}: {1}",
                    Enum.GetName ( typeof ( EnumeratedTypes.MyEnum ), val ),
                    val ) );
        }

        Console.WriteLine ( "Hit enter to exit " );
        Console.ReadLine ();
    } //eof Main 
} //eof Program

1
@YordanGeorgiev- flagFileExistsアプリケーションで他の場所で使用されていないのになぜ宣言するのですか?
Michael Kniskern 2010

2
私はそれがバグだと思います。I)
ヨルダンゲオルギエフ

5

私たちは間違った方向からこれに来ていませんか?

デプロイされたリリースの存続期間中にデータがまったく変更される可能性がある場合、列挙型は適切ではなく、辞書、ハッシュ、またはその他の動的コレクションを使用する必要があります。

可能な値のセットがデプロイされたリリースの存続期間中に固定されていることがわかっている場合は、列挙型が推奨されます。

列挙セットをレプリケートするものをデータベースに含める必要がある場合は、デプロイメントステップを追加して、データベーステーブルに列挙値の最終的なセットをクリアして再投入してみませんか?


はい、いいえ、はい、あなたが正しいので、全体のポイントは列挙型が静的であるということです。入力ミスを避け、何が利用できるかを知ることができます。辞書とdbを使用すると、何でもかまいません。しかし、1つからのみ選択することが許可されている場合は、両方のツリーの果物が必要になることがあります。
ケン

4

私はいつも自分の「カスタム列挙」を書くのが好きです。もう少し複雑なクラスが1つあるのですが、それを再利用できます。

public abstract class CustomEnum
{
    private readonly string _name;
    private readonly object _id;

    protected CustomEnum( string name, object id )
    {
        _name = name;
        _id = id;
    }

    public string Name
    {
        get { return _name; }
    }

    public object Id
    {
        get { return _id; }
    }

    public override string ToString()
    {
        return _name;
    }
}

public abstract class CustomEnum<TEnumType, TIdType> : CustomEnum
    where TEnumType : CustomEnum<TEnumType, TIdType>
{
    protected CustomEnum( string name, TIdType id )
        : base( name, id )
    { }

    public new TIdType Id
    {
        get { return (TIdType)base.Id; }
    }

    public static TEnumType FromName( string name )
    {
        try
        {
            return FromDelegate( entry => entry.Name.Equals( name ) );
        }
        catch (ArgumentException ae)
        {
            throw new ArgumentException( "Illegal name for custom enum '" + typeof( TEnumType ).Name + "'", ae );
        }
    }

    public static TEnumType FromId( TIdType id )
    {
        try
        {
            return FromDelegate( entry => entry.Id.Equals( id ) );
        }
        catch (ArgumentException ae)
        {
            throw new ArgumentException( "Illegal id for custom enum '" + typeof( TEnumType ).Name + "'", ae );
        }
    }

    public static IEnumerable<TEnumType> GetAll()
    {
        var elements = new Collection<TEnumType>();
        var infoArray = typeof( TEnumType ).GetFields( BindingFlags.Public | BindingFlags.Static );

        foreach (var info in infoArray)
        {
            var type = info.GetValue( null ) as TEnumType;
            elements.Add( type );
        }

        return elements;
    }

    protected static TEnumType FromDelegate( Predicate<TEnumType> predicate )
    {
        if(predicate == null)
            throw new ArgumentNullException( "predicate" );

        foreach (var entry in GetAll())
        {
            if (predicate( entry ))
                return entry;
        }

        throw new ArgumentException( "Element not found while using predicate" );
    }
}

次に、使用したい列挙型を作成するだけです。

 public sealed class SampleEnum : CustomEnum<SampleEnum, int>
    {
        public static readonly SampleEnum Element1 = new SampleEnum( "Element1", 1, "foo" );
        public static readonly SampleEnum Element2 = new SampleEnum( "Element2", 2, "bar" );

        private SampleEnum( string name, int id, string additionalText )
            : base( name, id )
        {
            AdditionalText = additionalText;
        }

        public string AdditionalText { get; private set; }
    }

ついに私は好きなようにそれを使うことができます:

 static void Main( string[] args )
        {
            foreach (var element in SampleEnum.GetAll())
            {
                Console.WriteLine( "{0}: {1}", element, element.AdditionalText );
                Console.WriteLine( "Is 'Element2': {0}", element == SampleEnum.Element2 );
                Console.WriteLine();
            }

            Console.ReadKey();
        }

そして私の出力は:

Element1: foo
Is 'Element2': False

Element2: bar
Is 'Element2': True    

2

System.Web.Compilation.BuildProviderが必要です

これを行うことの賢さも疑いますが、私には考えられない良いユースケースがあるかもしれません。

あなたが探しているのはビルドプロバイダー、つまりSystem.Web.Compilation.BuildProviderです。

それらはSubSonicによって非常に効果的に使用されています、ソースをダウンロードしてそれらがどのように使用されるかを確認できます。彼らがしていることの半分ほど複雑なものは必要ありません。

お役に立てれば。



0

私はあなたが望むことをする良い方法はないと思います。そして、あなたがそれについて考えるならば、私はこれがあなたが本当に望んでいることだとは思いません。

動的な列挙型がある場合、それを参照するときに動的な値をフィードする必要があることも意味します。たぶん多くの魔法を使えば、これを処理してDLLファイルに列挙型を生成するIntelliSenseのようなものを実現できます。しかし、必要な作業量、データベースにアクセスしてIntelliSense情報をフェッチするのがどれほど効果的でないか、生成されたDLLファイルを制御するバージョンの悪夢を考慮する必要があります。

enum値を手動で追加したくない場合(とにかくデータベースに追加する必要がある場合)は、代わりにT4テンプレートなどのコード生成ツールを使用してください。右クリックして実行すると、列挙型がコードで静的に定義され、列挙型を使用することのすべての利点が得られます。


0

動的列挙の使用は、どちらの方法でも問題があります。データを「複製」するという問題を経験して、将来的に保守しやすい明確で簡単なコードを確保する必要があります。

自動生成されたライブラリの導入を開始した場合、将来の開発者が適切なクラスオブジェクト内に列挙型をコード化するだけでなく、コードをアップグレードしなければならない場合に混乱が生じることは確実です。

与えられた他の例は素晴らしくエキサイティングなように聞こえますが、コード保守のオーバーヘッドとそれから得られるものについて考えてください。また、それらの値は頻繁に変更されますか?


0

Enumを保持し、同時に動的な値のリストを作成する1つの方法は、動的に作成されたディクショナリで現在持っているEnumを使用することです。

ほとんどの列挙型は、使用するように定義されているコンテキストで使用され、「動的列挙型」は動的プロセスでサポートされるため、2を区別できます。

最初のステップは、動的エントリのIDと参照を格納するテーブル/コレクションを作成することです。テーブルでは、最大のEnum値よりもはるかに大きい自動インクリメントが行われます。

動的な列挙型の部分になりました。列挙型を使用して、一連のルールを適用する一連の条件を作成することを想定しています。一部は動的に生成されます。

Get integer from database
If Integer is in Enum -> create Enum -> then run Enum parts
If Integer is not a Enum -> create Dictionary from Table -> then run Dictionary parts.

0

列挙型ビルダークラス

public class XEnum
{
    private EnumBuilder enumBuilder;
    private int index;
    private AssemblyBuilder _ab;
    private AssemblyName _name;
    public XEnum(string enumname)
    {
        AppDomain currentDomain = AppDomain.CurrentDomain;
        _name = new AssemblyName("MyAssembly");
        _ab = currentDomain.DefineDynamicAssembly(
            _name, AssemblyBuilderAccess.RunAndSave);

        ModuleBuilder mb = _ab.DefineDynamicModule("MyModule");

        enumBuilder = mb.DefineEnum(enumname, TypeAttributes.Public, typeof(int));


    }
    /// <summary>
    /// adding one string to enum
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public FieldBuilder add(string s)
    {
        FieldBuilder f = enumBuilder.DefineLiteral(s, index);
        index++;
        return f;
    }
    /// <summary>
    /// adding array to enum
    /// </summary>
    /// <param name="s"></param>
    public void addRange(string[] s)
    {
        for (int i = 0; i < s.Length; i++)
        {
            enumBuilder.DefineLiteral(s[i], i);
        }
    }
    /// <summary>
    /// getting index 0
    /// </summary>
    /// <returns></returns>
    public object getEnum()
    {
        Type finished = enumBuilder.CreateType();
        _ab.Save(_name.Name + ".dll");
        Object o1 = Enum.Parse(finished, "0");
        return o1;
    }
    /// <summary>
    /// getting with index
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    public object getEnum(int i)
    {
        Type finished = enumBuilder.CreateType();
        _ab.Save(_name.Name + ".dll");
        Object o1 = Enum.Parse(finished, i.ToString());
        return o1;
    }
}

オブジェクトを作成する

string[] types = { "String", "Boolean", "Int32", "Enum", "Point", "Thickness", "long", "float" };
XEnum xe = new XEnum("Enum");
        xe.addRange(types);
        return xe.getEnum();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.