根据数据库查找表中的值自动创建枚举?

时间:2009-04-07 10:43:44

标签: c# database dynamic enums

如何根据数据库查找表中的值(使用企业库数据层)自动创建枚举并随后在C#中使用其值?

例如,如果我在数据库中添加一个新的查找值,我不想在代码中手动添加额外的静态枚举值声明 - 我想让枚举与数据库保持同步。

有这样的事吗?


我不想创建代码生成的静态枚举(根据The Code Project文章 Enum Code Generator - Generating enum code automatically from database look up tables ),并希望它完全自动化。

15 个答案:

答案 0 :(得分:93)

我正在做这件事,但你需要做一些代码生成才能实现。

在我的解决方案中,我添加了一个项目“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");

我在解决方案中的其他项目引用了这个生成的程序集。结果,我可以在代码中使用动态枚举,并使用intellisense。

然后,我添加了一个构建后事件,以便在构建此“EnumeratedTypes”项目之后,它会自行运行并生成“MyEnums.dll”文件。

顺便说一句,它有助于更​​改项目的构建顺序,以便首先构建“EnumeratedTypes”。否则,一旦开始使用动态生成的.dll,如果.dll被删除,您将无法进行构建。 (鸡和鸡蛋的问题 - 解决方案中的其他项目需要这个.dll才能正确构建,并且在构建解决方案之前无法创建.dll ...)

我从this msdn article获得了上述大部分代码。

希望这有帮助!

答案 1 :(得分:47)

必须在编译时指定枚举,不能在运行时动态添加枚举 - 为什么你会在代码中没有使用/引用它们?

来自Professional C#2008:

  

C#中枚举的真正威力在于它们被实例化为从基类System.Enum派生的结构。这意味着可以调用针对它们的方法来执行一些有用的任务。请注意,由于.NET Framework的实现方式,在语法上将枚举处​​理为结构时,不存在性能损失。在实践中,一旦编译了代码,枚举将作为基本类型存在,就像int和float。

因此,我不确定您是否可以按照自己的方式使用Enums。

答案 2 :(得分:15)

它必须是一个真正的枚举?如何使用Dictionary<string,int>呢?

例如

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

答案 3 :(得分:12)

我使用T4模板完成了此操作。将.tt文件放入项目并设置Visual Studio以将T4模板作为预构建步骤运行是相当简单的。

T4生成.cs文件,这意味着您可以只查询数据库并从结果中在.cs文件中构建枚举。作为预构建任务接线,它会在每次构建时重新创建枚举,或者您可以根据需要手动运行T4。

答案 4 :(得分:11)

假设您的数据库中包含以下内容:

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

其中value可以是整数值(0,1,等)或枚举文本/键(Apple,Banana等)

答案 5 :(得分:10)

只需用“货架”代码和一些解释显示Pandincus的answer: 这个例子你需要两个解决方案(我知道也可以通过一个解决方案完成;),让高级学生介绍它......

所以这是表的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 

这是Console编程打印输出(记住它必须引用dll)。让高级学生提出解决方案,将一个解决方案中的所有内容与动态加载相结合,并检查是否已经构建了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

答案 6 :(得分:3)

我们是从错误的方向走向这个吗?

如果数据在部署版本的生命周期内可能完全发生变化,那么枚举就不合适了,您需要使用字典,散列或其他动态集合。

如果您知道可用值的集合在已部署版本的生命周期内是固定的,则最好使用枚举。

如果必须在数据库中有复制枚举集的内容,那么为什么不添加部署步骤以使用确定的枚举值集清除和重新填充数据库表?

答案 7 :(得分:2)

您需要System.Web.Compilation.BuildProvider

我也怀疑这样做的智慧,但那时可能有一个我无法想到的好用例。

您正在寻找的是Build Providers,即System.Web.Compilation.BuildProvider

SubSonic有效地使用了非常,您可以下载源代码并查看它们如何使用它们,您不需要像他们正在做的那样复杂一半的东西

希望这有帮助。

答案 8 :(得分:2)

我总是喜欢写自己的“自定义枚举”。比我有一个更复杂的类,但我可以重用它:

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    

答案 9 :(得分:0)

答案 10 :(得分:0)

我认为没有一种好方法可以做你想做的事。如果你仔细想想,我认为这不是你真正想要的。

如果你有一个动态枚举,这也意味着你必须在引用它时用动态值来提供它。也许有很多魔法你可以实现某种IntelliSense来处理这个并在DLL文件中为你生成一个枚举。但要考虑它需要的工作量,访问数据库以获取IntelliSense信息的无效性以及控制生成的DLL文件的版本的噩梦。

如果您真的不想手动添加枚举值(无论如何都必须将它们添加到数据库中),请使用代码生成工具,例如T4模板。右键单击+运行,您在代码中静态定义了枚举,您将获得使用枚举的所有好处。

答案 11 :(得分:0)

无论使用哪种方式,使用动态枚举都很糟糕。您将不得不经历“复制”数据的麻烦,以确保将来可以轻松维护清晰易用的代码。

如果您开始介绍自动生成的库,那么您肯定会让未来的开发人员更加困惑,而不仅仅是让您的枚举在相应的类对象中进行编码。

其他示例听起来很棒且令人兴奋,但请考虑代码维护的开销与您从中获得的开销。此外,这些价值会经常改变吗?

答案 12 :(得分:0)

保留枚举并同时创建动态值列表的一种方法是使用当前对动态创建词典的枚举。

由于大多数枚举用于定义要使用的上下文中,因此动态枚举&#34;将由动态过程支持,你可以区分2。

第一步是创建一个表/集合,其中包含动态条目的ID和引用。在表格中,您将自动增量远大于您最大的枚举值。

现在是动态枚举的部分,我假设您将使用Enums创建一组应用一组规则的条件,其中一些是动态生成的。

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.

答案 13 :(得分: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();

答案 14 :(得分:0)

总结一下,我也厌倦了根据Id / Name数据库表列写枚举,从SSMS中的查询中复制和粘贴内容。

下面是一个超级脏的存储过程,该过程将表名,要用于c#枚举名称的列名称以及要用于c#枚举值的列名称作为输入。

我使用的大多数此类表名a)以“ s”结尾b)有[TABLENAME] Id列,而c)有[TABLENAME] Name列,因此有一些if语句将采用该结构,在这种情况下,不需要列名参数。

这些示例的一些上下文-这里的“ Stonk”并不是真的意味着“股票”,而是,我使用“ stonk”的方式意味着“在一段时间内与它相关联的东西“但这并不重要,它只是具有此Id / Name模式的表的示例。看起来像这样:

CREATE TABLE StonkTypes (
    StonkTypeId TINYINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
    StonkTypeName VARCHAR(200) NOT NULL CONSTRAINT UQ_StonkTypes_StonkTypeName UNIQUE (StonkTypeName)
)

创建proc后,此语句:

EXEC CreateCSharpEnum 'StonkTypes'

选择此字符串:

public enum StonkTypes { Stonk = 1, Bond = 2, Index = 3, Fund = 4, Commodity = 5, 
PutCallRatio = 6, }

我可以复制并粘贴到C#文件中。

我有一个Stonks表,它具有StonkId和StonkName列,因此该执行程序:

EXEC CreateCSharpEnum 'Stonks'

吐出来:

public enum Stonks { SP500 = 1, DowJonesIndustrialAverage = 2, ..... }

但是对于该枚举,我想使用“ Symbol”列作为枚举名称值,因此:

EXEC CreateCSharpEnum 'Stonks', 'Symbol'

完成技巧并呈现:

public enum Stonks { SPY = 1, DIA = 2, ..... }

事不宜迟,这是肮脏的疯狂。是的,非常肮脏,但是我对自己感到很高兴-构造SQL代码的SQL代码构造了C#代码。涉及几层。


CREATE OR ALTER PROCEDURE CreateCSharpEnum
@TableName VARCHAR(MAX),
@EnumNameColumnName VARCHAR(MAX) = NULL,
@EnumValueColumnName VARCHAR(MAX) = NULL
AS

DECLARE @LastCharOfTableName VARCHAR(1)
SELECT @LastCharOfTableName = RIGHT(@TableName, 1)

PRINT 'Last char = [' + @LastCharOfTableName + ']'

DECLARE @TableNameWithoutS VARCHAR(MAX)
IF UPPER(@LastCharOfTableName) = 'S'
    SET @TableNameWithoutS = LEFT(@TableName, LEN(@TableName) - 1)
ELSE
    SET @TableNameWithoutS = @TableName

PRINT 'Table name without trailing s = [' + @TableNameWithoutS + ']'

IF @EnumNameColumnName IS NULL
    BEGIN
        SET @EnumNameColumnName = @TableNameWithoutS + 'Name'
    END

PRINT 'name col name = [' + @EnumNameColumnName + ']'

IF @EnumValueColumnName IS NULL
    SET @EnumValueColumnName = @TableNameWithoutS + 'Id'

PRINT 'value col name = [' + @EnumValueColumnName + ']'

-- replace spaces and punctuation
SET @EnumNameColumnName  = 'REPLACE(' + @EnumNameColumnName + ', '' '', '''')'
SET @EnumNameColumnName  = 'REPLACE(' + @EnumNameColumnName + ', ''&'', '''')'
SET @EnumNameColumnName  = 'REPLACE(' + @EnumNameColumnName + ', ''.'', '''')'
SET @EnumNameColumnName  = 'REPLACE(' + @EnumNameColumnName + ', ''('', '''')'
SET @EnumNameColumnName  = 'REPLACE(' + @EnumNameColumnName + ', '')'', '''')'

PRINT 'name col name with replace sql = [' + @EnumNameColumnName + ']'

DECLARE @SqlStr VARCHAR(MAX) = 'SELECT ' + @EnumNameColumnName  
+ ' + '' = ''' 
+ ' + LTRIM(RTRIM(STR(' + @EnumValueColumnName + '))) + '','' FROM ' + @TableName + ' ORDER BY ' + @EnumValueColumnName

PRINT 'sql that gets rows for enum body = [' + @SqlStr + ']'

CREATE TABLE #EnumRowsTemp (s VARCHAR(MAX))

INSERT 
INTO #EnumRowsTemp
EXEC(@SqlStr)

--SELECT * FROM #EnumRowsTemp

DECLARE @csharpenumbody VARCHAR(MAX) 
SELECT @csharpenumbody = COALESCE(@csharpenumbody + ' ', '') + s FROM #EnumRowsTemp

--PRINT @csharpenumbody

DECLARE @csharpenum VARCHAR(MAX) = 'public enum ' + @TableName + ' { ' + @csharpenumbody + ' }'

PRINT @csharpenum

SELECT @csharpenum

DROP TABLE #EnumRowsTemp

请保持批判。我不了解的一件时髦的事情,我为什么要创建并删除此#EnumRowsTemp表,而不仅仅是“ SELECT INTO #EnumRowsTemp”来动态创建临时表?我不知道答案,我尝试过,但没有用。那可能是这段代码中最少的问题...

可能会很脏...我希望这可以节省一些您的傻瓜时间。