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


116

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

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

有这样的事吗?


我不想创建一个代码生成的静态枚举(根据The Code Project文章Enum Code Generator-从数据库查找表自动生成枚举代码),并且希望它是完全自动的。


您是否有可能以一种更好的解决方案来尝试使用枚举?

我在@Dan那里,必须有一个更好的方法来做到这一点。
2011年

@mydogisbox有什么更好的方法?
eran otzap'4

@eranotzer实际上,经过一番思考之后,编写一个预构建步骤来查询数据库并从中生成一个枚举将非常简单
N_A 2012年

1
话虽这么说,我不确定他所说的“我不想创建一个代码生成的静态枚举”是什么意思,所以也许这不符合需要。
2012年

Answers:


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

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

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

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

我从这篇msdn文章中获得了以上大部分代码。

希望这可以帮助!


7
对于那些不知道如何在生成后运行生成的可执行文件的人:1)右键单击项目2)单击属性3)单击生成事件4)在“生成后事件命令行”文本框中,键入$(TargetPath)
Miguel

是否可以使用此链接中提到的带有自定义属性定义的Dynamic Enum ?
Balagurunathan Marimuthu

49

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

从Professional C#2008:

C#中枚举的真正功能是在后台将它们实例化为派生自基类System.Enum的结构。这意味着可以针对它们调用方法以执行一些有用的任务。请注意,由于.NET Framework的实现方式,因此在语法上将枚举作为结构对待时不会造成性能损失。实际上,一旦您的代码被编译,枚举将作为基本类型存在,就像int和float一样。

因此,我不确定您是否可以按想要的方式使用枚举。


1
不确定billfredtom的原因是什么,但是我的想法是我可以避免对某些键进行手动字符串查找,而是将其内置到我的代码中。我只希望能够对强类型值而不是弱字符串执行逻辑。需要注意的是,由于我们现在有依赖于动态生成的Enum的代码,因此,如果我们从数据库中删除该值,则下次尝试编译代码时,它将失败。
Pandincus 2010年

14
海报和18个支持者有点遗漏了他的观点。听起来他想要生成的枚举,而不是运行时动态枚举。
马特·米切尔

+1。枚举基本上只是定义整数常量的另一种方法(即使System.Enum具有某些其他功能)。而不是写const int Red=0, Green=1, Blue=3;你写enum { Red, Green, Blue }。根据定义,常量是常量,不是动态的。
Olivier Jacot-Descombes

2
@Oliver如果您想争论语义学,是的,您是正确的。但是我同意Graphain的评论-我相信OP正在寻找生成的枚举。他希望枚举值来自数据库,而不必对其进行硬编码。
Pandincus 2011年

1
或者...说我允许我的web.config文件中的某人为我的电子邮件模板代码定义电子邮件模板的令牌类型。如果我现有的名为EmailTokens的枚举(表示这些字符串类型)是根据我的web.config中定义的那些类型生成的,那将是很好的。因此,如果有人通过我的键值(例如“ Email,FName”)在webconfig中添加新的电子邮件令牌,并且我已经有一个枚举,我将用来表示这些令牌(例如EmailTemplate.Email),那么只要有人可以在web.config的键中添加新的字符串令牌,我的枚举将自动添加const
PositiveGuy

18

它一定是一个实际的枚举吗?改用a怎么样Dictionary<string,int>

例如

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

11
我不会尝试这样做。您失去了编译时间检查,并容易出现键入错误。枚举的所有好处都消失了。您可以引入字符串常量,但随后又回到了开始的地方。
丹尼尔·布鲁克纳(DanielBrückner),2009年

1
我同意。但是请记住,键入错误的字符串将在运行时被捕获。只需添加一个测试用例即可涵盖所有枚举成员。
Autodidact

1
如果您使用常量而不是文字,则不构成错误
Maslow

@Maslow假设您的意思是枚举,而不是字符串常量。
马特·米切尔

4
+1。使用字典或HashSet最接近于动态枚举。完全动态意味着它在运行时发生,因此必须在运行时进行错误检查。
Olivier Jacot-Descombes

13

我已经用T4做到了模板。将.tt文件拖放到您的项目中,并设置Visual Studio以运行T4模板作为预构建步骤是相当简单的。

T4生成一个.cs文件,这意味着您可以让它只查询数据库并根据结果在.cs文件中构建一个枚举。作为预构建任务进行连接,它将在每个构建上重新创建您的枚举,或者您可以根据需要手动运行T4。


12

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

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

现在,您已经编译了类型,可以使用了。
要获取存储在数据库中的枚举值,可以使用:

[Enum].Parse(enumType, value);

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


4
这实际上有什么帮助?没有类型安全性,也没有智能感知。基本上,这只是使用常量的一种更复杂的方法,因为他无论如何都必须提供值。
Runeborg

2
Sani-完美!这正是我所需要的。对于那些质疑这种原因的人,我使用的供应商库要求将属性设置为枚举的名称。枚举限制了同一对象的不同属性的有效值范围。就我而言,我正在加载元数据,包括数据库中的有效值范围;否,供应商代码不支持将任何类型的集合传递给属性。谢谢

10

只是显示 答案带有“货架”代码的Pandincus和一些解释:对于此示例,您需要两种解决方案(我知道也可以通过一种解决方案;),让高级学生介绍它...

因此,这是该表的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)。让进阶的学生介绍将解决方案中的所有内容与动态加载结合起来的解决方案,并检查是否已生成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'2

2
我想那是个错误; I)
Yordan Georgiev 2010年

5

我们不是从错误的方向走过来吗?

如果数据在部署版本的生存期内可能根本没有变化,那么枚举就不合适了,您需要使用字典,哈希或其他动态集合。

如果您知道可能值的集合在已部署发行版的生命周期内是固定的,那么最好使用一个枚举。

如果您的数据库中必须有某些东西可以复制枚举集,那么为什么不添加一个部署步骤来用确定的枚举值集清除并重新填充数据库表呢?


是和否,是,因为您是正确的,整个点都是静态的。您可以避免输入错误,也可以知道可用的内容。与字典和数据库-可能是任何东西。但是有时候当您只允许从一棵树中采摘时,您想要两棵树的果实。

4

我一直喜欢写自己的“自定义枚举”。比起我的一门课要复杂一点,但我可以重用它:

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

我也怀疑这样做是否明智,但是也许有一个我想不到的好用例。

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

SubSonic 非常有效地使用了它们,你可以下载源代码,看看他们如何使用它们,你将不再需要任何的一半复杂,因为他们在做什么。

希望这可以帮助。



0

我认为没有一种做您想要的事情的好方法。如果您考虑一下,我不认为这是您真正想要的。

如果您有一个动态的枚举,那也意味着您在引用它时必须给它提供一个动态值。也许您可以通过许多魔术来实现某种IntelliSense,以解决此问题并在DLL文件中为您生成一个枚举。但是,请考虑所需的工作量,访问数据库以获取IntelliSense信息的效率以及控制生成的DLL文件的版本的噩梦。

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


0

无论采用哪种方式,使用动态枚举都是有害的。您将不得不解决“复制”数据的麻烦,以确保将来易于维护的清晰易懂的代码。

如果您开始引入自动生成的库,那么肯定会给将来必须升级代码的开发人员造成更多的混乱,而不仅仅是在适当的类对象中进行枚举编码。

给出的其他示例听起来不错且令人兴奋,但请考虑一下代码维护的开销以及从中获得的好处。另外,这些价值观会经常改变吗?


0

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

由于大多数枚举均在定义要使用的上下文中使用,并且动态进程将支持“动态枚举”,因此可以区分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();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.