簡體   English   中英

根據數據庫查找表中的值自動創建枚舉?

[英]Automatically create an Enum based on values in a database lookup table?

如何根據數據庫查找表中的值(使用企業庫數據層)自動創建枚舉並隨后在 C# 中使用其值?

例如,如果我在數據庫中添加一個新的查找值,我不想在代碼中手動添加額外的靜態枚舉值聲明 - 我想讓枚舉與數據庫保持同步。

有這樣的事情嗎?


我不想創建代碼生成的靜態枚舉(根據代碼項目文章 枚舉代​​碼生成器 - 從數據庫查找表自動生成枚舉代碼),並且希望它是完全自動的。

我正在做這件事,但你需要做一些代碼生成才能讓它工作。

在我的解決方案中,我添加了一個項目“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 文章中得到了上面的大部分代碼。

希望這可以幫助!

枚舉必須在編譯時指定,您不能在運行時動態添加枚舉 - 為什么在代碼中沒有使用/引用它們?

來自專業 C# 2008:

C# 中枚舉的真正威力在於,它們在幕后被實例化為派生自基類 System.Enum 的結構。 這意味着可以針對它們調用方法來執行一些有用的任務。 請注意,由於 .NET Framework 的實現方式,在語法上將枚舉視為結構不會造成性能損失。 實際上,一旦您的代碼被編譯,枚舉將作為原始類型存在,就像 int 和 float 一樣。

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

它必須是一個實際的枚舉嗎? 改用Dictionary<string,int>怎么樣?

例如

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

我已經用T4模板做到了這一點。 將 .tt 文件放入項目中並設置 Visual Studio 以運行 T4 模板作為預構建步驟相當簡單。

T4 生成一個 .cs 文件,這意味着您可以讓它查詢數據庫並根據結果在 .cs 文件中構建枚舉。 作為預構建任務進行連接,它會在每次構建時重新創建您的枚舉,或者您可以根據需要手動運行 T4。

假設您的數據庫中有以下內容:

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

只是用“現成的”代碼和一些解釋來展示 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

我們不是從錯誤的方向來到這里嗎?

如果數據在部署版本的生命周期內可能會發生變化,那么枚舉就是不合適的,您需要使用字典、哈希或其他動態集合。

如果您知道這組可能值在已部署版本的生命周期內是固定的,則最好使用枚舉。

如果您的數據庫中必須有一些東西來復制枚舉集,那么為什么不添加一個部署步驟來清除並使用一組確定的枚舉值重新填充數據庫表呢?

我總是喜歡編寫自己的“自定義枚舉”。 比我有一個更復雜的類,但我可以重用它:

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    

你想要 System.Web.Compilation.BuildProvider

我也懷疑這樣做是否明智,但是可能有一個我想不到的好用例。

您正在尋找的是構建提供程序,即 System.Web.Compilation.BuildProvider

它們被SubSonic非常有效地使用,您可以下載源代碼並查看它們是如何使用它們的,您不需要任何像它們所做的那樣復雜的東西。

希望這可以幫助。

無論哪種方式,使用動態枚舉都是不好的。 您將不得不經歷“復制”數據的麻煩,以確保將來易於維護的清晰簡單的代碼。

如果你開始引入自動生成的庫,你肯定會給未來的開發人員帶來更多的困惑,他們不得不升級你的代碼,而不是簡單地在適當的類對象中編碼你的枚舉。

給出的其他示例聽起來不錯且令人興奮,但請考慮代碼維護的開銷與從中獲得的收益。 此外,這些值是否會頻繁更改?

我認為沒有什么好的方法可以做你想做的事。 如果你仔細想想,我認為這不是你真正想要的。

如果您有一個動態枚舉,這也意味着您必須在引用它時為其提供動態值。 也許用很多魔法,你可以實現某種智能感知,它可以解決這個問題,並在 DLL 文件中為你生成一個枚舉。 但考慮到它需要的工作量、訪問數據庫以獲取 IntelliSense 信息的效率有多低,以及控制生成的 DLL 文件的版本的噩夢。

如果您真的不想手動添加枚舉值(無論如何您都必須將它們添加到數據庫中),請改用代碼生成工具,例如T4模板。 右鍵單擊+運行,您將在代碼中靜態定義枚舉,並獲得使用枚舉的所有好處。

保留枚舉並同時創建動態值列表的一種方法是將您當前擁有的枚舉與動態創建的字典一起使用。

由於大多數枚舉是在它們被定義為使用的上下文中使用的,並且“動態枚舉”將被動態進程支持,因此您可以區分 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.

枚舉構建器類

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

說起來,我也厭倦了根據 Id / Name db 表列寫出枚舉,從 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, ..... }

但是對於該枚舉,我想對枚舉名稱值使用“符號”列,因此:

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”來動態創建臨時表? 我不知道答案,我試過了,但沒有用。 這可能是這段代碼中最少的問題......

盡管它可能很臟......我希望這可以為你們中的一些笨蛋節省一點時間。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM