Автоматично створити Enum на основі значень у таблиці пошуку бази даних?


116

Як я автоматично створюю enum і згодом використовую його значення в C # на основі значень у таблиці пошуку бази даних (використовуючи рівень даних бібліотеки підприємства)?

Наприклад, якщо я додаю нове значення пошуку в базу даних, мені не хочеться вручну додавати додаткове статичне значення значення перерахунку в код - я б хотів, щоб перерахунок синхронізувався з базою даних.

Чи є таке, як це?


Я не хочу створювати статичний перелік, створений кодом (відповідно до статті проекту Code Code Enum Code Generator - автоматично генерувати код перерахунку з таблиць пошуку бази даних ), і я вважаю за краще, щоб він був повністю автоматичним.


Чи можливо, ви намагаєтесь перерахувати так, щоб було краще рішення?
День

Я з @Dan, там повинен бути кращий спосіб зробити це.
N_A

@mydogisbox, що краще?
eran otzap

@eranotzer Насправді, подумавши про це трохи, було б досить просто написати крок попереднього збирання, який запитує БД і генерує з нього перерахунок
N_A

1
Попри це, я не впевнений, що він означає під "я не хочу створювати статичний перелік, сформований кодом", тому, можливо, це не відповідає потребі.
N_A

Відповіді:


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)
Мігель

Чи можливо зробити Dynamic Enum зі спеціальним визначенням атрибутів, як зазначено в цьому посиланні ?
Балагурутана Марімутху

49

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

З професійного C # 2008:

Справжня сила перерахунків у C # полягає в тому, що за лаштунками вони описуються як структури, похідні від базового класу System.Enum. Це означає, що можна викликати проти них методи для виконання деяких корисних завдань. Зауважте, що через те, як реалізується .NET Framework, втрати продуктивності не пов'язані з синтаксичним трактуванням переліків як структур. На практиці, як тільки ваш код буде складений, перерахунки існуватимуть як примітивні типи, як int та float.

Тож я не впевнений, що ви можете використовувати Enums так, як вам хочеться.


1
не впевнений, що таке міркування Billfredtom, але моє те, що я міг уникнути ручного пошуку рядків для певних ключів, замість того, щоб вони були вбудовані у мій код. Я просто вважаю за краще вміти виконувати логіку на сильно набраних значеннях замість слабких рядків. Застереження було б таким, оскільки тепер у нас є код, який спирається на динамічно генерований Enum, якщо ми видалимо значення з бази даних, наступного разу, коли ми спробуємо скомпілювати наш код, він не вдасться.
Пандінкус

14
Плакат і 18 нагород начебто пропустили його точку. Це здається, що він хоче генерувати перерахунки, а не динамічні перерахунки під час виконання.
Метт Мітчелл

+1. Перерахунок - це лише інший спосіб визначення цілих констант (навіть якщо він System.Enumмає додаткову функціональність). Замість написання const int Red=0, Green=1, Blue=3;Ви пишете enum { Red, Green, Blue }. Константа за визначенням є постійною і не динамічною.
Олів'є Якот-Дескомб

2
@Oliver Якщо ви хочете сперечатися з семантикою, так, ви маєте рацію. Але я погоджуюся з коментарем Графайна - я вважаю, що ОП шукає породжені переліки. Він хоче, щоб значення перерахунків надходили з бази даних, а не потрібно їх жорстко кодувати.
Пандінкус

1
Або ... скажемо, я дозволяю комусь у своєму web.config визначати типи токенів для шаблонів електронної пошти для мого коду шаблону електронної пошти. Було б добре, якби мій існуючий перелік, який називається EmailTokens, який представляє ці типи рядків, буде генерований на основі тих типів, які визначені в моєму web.config. Тож якщо хтось додасть новий веб-маркер електронної пошти у веб-конфіг за допомогою мого ключового значення, наприклад, "Електронна пошта, 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
Я б не намагався це зробити так. Ви втрачаєте перевірку часу компіляції та стаєте схильні до помилок введення тексту. Всі переваги перерахунків пішли. Ви можете ввести струнні константи, але тоді ви знову там, де почали.
Даніель Брюкнер

1
Я згоден. Але пам’ятайте, що помилкові рядки будуть вловлюватися під час виконання. Просто додайте тестовий випадок, щоб охопити всіх членів перерахунків.
Автододаток

1
Помилка не є проблемою, якщо ви використовуєте константи замість літералів
Маслоу

@Maslow Припустимо, ви маєте на увазі переліки, а не рядкові константи.
Метт Мітчелл

4
+1. Використання словника або HashSet наближається до того, що може бути динамічним перерахунком. Повністю динамічний означає, що це відбувається під час виконання, і тому перевірка помилок повинна відбуватися під час виконання.
Олів'є Якот-Дескомб

13

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

T4 генерує .cs-файл, а це означає, що ви можете просто запросити його в базу даних та створити enum у .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

Побудуйте вихідний код для enum, і ви отримаєте щось на зразок:

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

де значенням може бути або ціле значення (0, 1, і т.д.), або текст / ключ enum (Apple, Banana тощо)


4
Яким чином це насправді допомогло б? Немає ніякої безпеки безпеки і не існує конфлікту. По суті, це лише більш складний спосіб використання константи, оскільки він так чи інакше повинен надати значення.
Рунеборг

2
Сані - ідеально! Це саме те, що мені було потрібно. Для тих, хто ставить під сумнів причину чогось подібного, я використовую бібліотеку постачальників, яка вимагає, щоб властивість було встановлено на ім'я перерахування. Перерахування обмежує допустимий діапазон значень для іншого властивості одного і того ж об'єкта. У моєму випадку я завантажую метадані, включаючи допустимий діапазон значень із бази даних; та ні, код постачальника не підтримує передачу колекції будь-якого типу у власність. Дякую

10

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

Отже, ось 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коли він не використовується ніде в додатку?
Майкл Кіскерн

2
Я здогадуюсь, це помилка, ніж; I)
Йордан Георгієв

5

Хіба ми не приходимо до цього з неправильного напрямку?

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

Якщо ви знаєте, що набір можливих значень фіксований протягом життя розгорнутого випуску, то перевага є кращою.

Якщо у вашій базі даних має бути щось, що повторює перелічений набір, то чому б не додати крок розгортання для очищення та перенаселення таблиці бази даних з остаточним набором значень enum?


Так і ні, так, тому що ви правильні, вся справа в перерахунку статична. Ви можете уникнути помилок введення тексту, а також знати, що доступно. Зі словником і db - може бути що завгодно. Але іноді хочеться плодів обох дерев, коли вам дозволено збирати лише одне.
Кен

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

Я також сумніваюся в мудрості робити це, але тоді, можливо, є корисний випадок, про який я не можу придумати.

Що ви шукаєте, це постачальники побудови, тобто System.Web.Compilation.BuildProvider

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

Сподіваюся, це допомагає.



0

Я не думаю, що є хороший спосіб робити те, що ти хочеш. І якщо ви подумаєте про це, я не думаю, що цього ви дійсно хочете.

Якщо у вас буде динамічний перелік, це також означає, що вам потрібно подавати його з динамічним значенням, коли ви посилаєтесь на нього. Можливо, з великою кількістю магії ви могли б досягти якихось IntelliSense, які б подбали про це і створили для вас перерахунок у файлі DLL. Але врахуйте обсяг роботи, який би знадобився, наскільки неефективним було б отримати доступ до бази даних для отримання інформації IntelliSense, а також кошмар версії, що контролює створений файл DLL.

Якщо ви дійсно не хочете вручну додавати значення перерахунків (вам доведеться все-таки додати їх до бази даних), використовуйте натомість інструмент генерації коду, наприклад шаблони T4 . Клацніть правою клавішею + запустити, і ви отримали статизм, визначений у коді, і ви отримаєте всі переваги використання переліків.


0

Використовувати динамічні перерахунки погано, незалежно від того, в який спосіб. Вам доведеться пережити проблеми "дублювання" даних, щоб забезпечити чіткий та простий код, який легко підтримувати в майбутньому.

Якщо ви почнете впроваджувати автоматично створені бібліотеки, ви впевнені, що викликаєте більше плутанини у майбутніх розробників, які потребують оновлення вашого коду, ніж просто зробити ваш перерахунок закодованим у відповідному об'єкті класу.

Інші приклади дають звучання приємно та захоплююче, але подумайте про накладні витрати на обслуговування коду порівняно з тим, що ви отримуєте від нього. Також ці значення часто змінюватимуться?


0

Один із способів збереження переліків і створення динамічного списку значень одночасно - це використання переліків, які у вас є, у динамічно створеному словнику.

Оскільки більшість Enums використовуються в контексті, який вони визначені для використання, а "динамічні перерахунки" будуть підтримуватися динамічними процесами, ви можете виділити 2.

Першим кроком є ​​створення таблиці / колекції, в якій розміщуються ідентифікатори та посилання для динамічних записів. У таблиці ви будете автоматично збільшувати набагато більше, ніж ваше найбільше значення Enum.

Тепер приходить частина для ваших динамічних Enums, я припускаю, що ви будете використовувати 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.

0

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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.