Автоматически создавать Enum на основе значений в таблице поиска в базе данных? - PullRequest
104 голосов
/ 07 апреля 2009

Как автоматически создать перечисление и впоследствии использовать его значения в C # на основе значений в таблице поиска в базе данных (с использованием уровня данных корпоративной библиотеки)?

Например, если я добавлю новое значение поиска в базу данных, мне не нужно будет вручную добавлять в код дополнительное объявление статического значения перечисления - я бы хотел синхронизировать перечисление с базой данных.

Есть ли такая вещь, как эта?


Я не хочу создавать код, сгенерированный статическим перечислением (согласно Код проекта статья Генератор кода перечисления - автоматическое создание кода перечисления из таблиц поиска базы данных ) и предпочел бы, чтобы он был полностью автоматическим.

Ответы [ 14 ]

93 голосов
/ 27 апреля 2009

Я делаю именно эту вещь, но вам нужно , чтобы выполнить какое-то генерирование кода, чтобы это работало.

В моём решении я добавил проект "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 .

Надеюсь, это поможет!

47 голосов
/ 07 апреля 2009

Перечисления должны быть указаны во время компиляции, вы не можете динамически добавлять перечисления во время выполнения - и почему бы вам не использовать / ссылаться на них в коде?

от Professional C # 2008:

Реальная сила перечислений в C # заключается в том, что за кулисами они создаются как структуры, производные от базового класса System.Enum. Это означает, что можно вызывать методы против них для выполнения некоторых полезных задач. Обратите внимание, что из-за способа реализации .NET Framework нет потери производительности, связанной с синтаксической обработкой перечислений как структур. На практике, как только ваш код скомпилирован, перечисления будут существовать как примитивные типы, как int и float.

Итак, я не уверен, что вы можете использовать Enums так, как хотите.

15 голосов
/ 07 апреля 2009

Должно ли это быть фактическим перечислением? Как насчет использования Dictionary<string,int> вместо?

например

Dictionary<string, int> MyEnum = new Dictionary(){{"One", 1}, {"Two", 2}};
Console.WriteLine(MyEnum["One"]);
12 голосов
/ 17 июля 2012

Я сделал это с помощью шаблона T4 . Довольно просто вставить файл .tt в ваш проект и настроить Visual Studio для запуска шаблона T4 в качестве шага перед сборкой.

T4 генерирует файл .cs, что означает, что вы можете просто запросить базу данных и построить перечисление в файле .cs из результата. Смонтированный как задача перед сборкой, он будет пересоздавать ваше перечисление при каждой сборке, или вы можете вместо этого запускать T4 вручную.

11 голосов
/ 07 апреля 2009

Допустим, у вас есть следующее в вашей БД:

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 + ... + "}";

(очевидно, это построено в некотором цикле.)

Затем начинается забавная часть, компилирующая ваш enum и использующая его:

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, хранящегося в БД, вы можете использовать:

[Enum].Parse(enumType, value);

где значением может быть либо целое число (0, 1 и т. Д.), Либо текст / ключ перечисления (Apple, Banana и т.

10 голосов
/ 20 мая 2009

Просто показывает ответ Пандинкуса с кодом "полки" и некоторым объяснением: Вам нужно два решения для этого примера (я знаю, что это можно сделать и через одно;), пусть продвинутые студенты представят его ...

Итак, вот 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
3 голосов
/ 18 апреля 2018

Разве мы не подходим к этому с неправильного направления?

Если данные могут измениться вообще в течение времени жизни развернутого выпуска, тогда перечисление просто не подходит, и вам нужно использовать словарь, хэш или другую динамическую коллекцию.

Если вы знаете, что набор возможных значений фиксирован в течение срока службы развернутого выпуска, тогда предпочтительным является перечисление.

Если у вас должно быть что-то в вашей базе данных, которое реплицирует перечислимый набор, то почему бы не добавить шаг развертывания для очистки и повторного заполнения таблицы базы данных окончательным набором значений перечисления?

2 голосов
/ 12 февраля 2015

Мне всегда нравится писать свой собственный "enum". Чем у меня есть один класс, который немного сложнее, но я могу использовать его повторно:

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 голосов
/ 07 апреля 2009

Требуется System.Web.Compilation.BuildProvider

Я также сомневаюсь в целесообразности этого, но, возможно, есть хороший вариант использования, о котором я не могу вспомнить.

То, что вы ищете, это Поставщики сборки т.е. System.Web.Compilation.BuildProvider

Они используются очень эффективно SubSonic , вы можете скачать исходный код и посмотреть, как они его используют, вам не понадобится ничего настолько сложного, как они делает.

Надеюсь, это поможет.

0 голосов
/ 08 июля 2018

enum builder class

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...