Автоматически создавать Enum на основе значений в таблице поиска базы данных?


116

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

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

Есть такая вещь?


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


Возможно ли, что вы пытаетесь использовать перечисление так, чтобы было лучшее решение?
Дэн

Я с @Dan, должен быть лучший способ сделать это.
N_A

@mydogisbox, что лучше?
eran otzap 06

@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

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

Из Professional C # 2008:

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

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


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

15
Плакат и 18 голосов, вроде как, упустили его точку зрения. Похоже, ему нужны сгенерированные перечисления, а не динамические перечисления времени выполнения.
Мэтт Митчелл

+1. Перечисление - это просто еще один способ определения целочисленных констант (даже если он System.Enumимеет некоторые дополнительные функции). Вместо того, чтобы писать const int Red=0, Green=1, Blue=3;Вы пишите enum { Red, Green, Blue }. Константа по определению является постоянной, а не динамической.
Olivier Jacot-Descombes

2
@Oliver Если вы хотите поспорить с семантикой, да, вы правы. Но я согласен с комментарием Graphain - я считаю, что OP ищет сгенерированные перечисления. Он хочет, чтобы значения перечисления поступали из базы данных и не жестко их программировали.
Pandincus

1
Или ... допустим, я разрешаю кому-то в моем web.config определять типы токенов для шаблонов электронной почты для моего кода шаблона электронной почты. Было бы хорошо, если бы мое существующее перечисление с именем EmailTokens, которое представляет эти строковые типы, было бы создано на основе тех типов, которые определены в моем web.config. Итак, если кто-то добавит новый токен электронной почты в webconfig через мое значение ключа, например «Email, FName», и у меня уже есть перечисление, которое я собираюсь использовать для представления этих токенов, например EmailTemplate.Email, было бы неплохо, если бы кто-нибудь мог просто добавить новый строковый токен в этот ключ в web.config, и мое перечисление автоматически добавит константу
PositiveGuy

18

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

например

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

11
Я бы не стал так поступать. Вы теряете проверки времени компиляции и становитесь склонными к опечаткам. Все преимущества перечислений исчезли. Вы можете ввести строковые константы, но тогда вы вернетесь к тому, с чего начали.
Даниэль Брюкнер,

1
Я согласен. Но помните, что неправильно набранные строки будут обнаружены во время выполнения. Просто добавьте тестовый пример, чтобы охватить всех членов перечисления.
Autodidact,

1
опечатка не проблема, если вы используете константы вместо литералов
Маслоу

@Maslow Предположим, вы имеете в виду перечисления, а не строковые константы.
Мэтт Митчелл

4
+1. Использование словаря или HashSet ближе всего к тому, что может быть динамическим перечислением. Полностью динамический означает, что это происходит во время выполнения, и поэтому проверка ошибок должна происходить во время выполнения.
Оливье Жако-Декомб

14

Я сделал это с помощью шаблона Т4 . Достаточно просто добавить файл .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, чтобы получить нужные вам значения:

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

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


4
Каким образом это могло бы помочь? Нет безопасности типов и intellisense. По сути, это всего лишь более сложный способ использования константы, поскольку он в любом случае должен предоставить значение.
Рунеборг

2
Сани - отлично! Это было именно то, что мне было нужно. Для тех, кто сомневается в причинах чего-то вроде этого, я использую библиотеку поставщика, которая требует, чтобы свойство было установлено на имя перечисления. Перечисление ограничивает допустимый диапазон значений для другого свойства того же объекта. В моем случае я загружаю метаданные, включая допустимый диапазон значений из базы данных; и нет, код поставщика не поддерживает передачу в свойство коллекции любого типа. Спасибо

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). Пусть продвинутые студенты представят решение для объединения всего в одном решении с динамической загрузкой и проверкой, есть ли уже build 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

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

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

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

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


Да и нет, да, потому что вы правы, все дело в том, что перечисление статично. Вы можете избежать опечаток, а также знать, что доступно. Со словарем и БД - может быть что угодно. Но иногда вам нужны плоды обоих деревьев, когда вам разрешено собирать плоды только с одного.
Кен

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

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

Поскольку большинство перечислений используются в контексте, в котором они определены для использования, а «динамические перечисления» будут поддерживаться динамическими процессами, вы можете различить 2.

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