Creare automaticamente un Enum in base ai valori in una tabella di ricerca nel database?


116

Come posso creare automaticamente un'enumerazione e successivamente utilizzare i suoi valori in C # in base ai valori in una tabella di ricerca nel database (utilizzando il livello dati della libreria aziendale)?

Ad esempio, se aggiungo un nuovo valore di ricerca nel database, non voglio dover aggiungere manualmente la dichiarazione del valore enum statico extra nel codice: vorrei mantenere l'enumerazione sincronizzata con il database.

Esiste una cosa come questa?


Non voglio creare un codice generato dall'enumerazione statica (come da articolo di The Code Project Enum Code Generator - Generazione automatica del codice enum dalle tabelle di ricerca del database ) e preferirei che fosse completamente automatico.


È possibile che tu stia cercando di utilizzare un'enumerazione in un modo in cui esiste una soluzione migliore?
Dan

Sono con @Dan, deve esserci un modo migliore per farlo.
N_A

@mydogisbox qual è il modo migliore?
eran otzap

@eranotzer In realtà, dopo averci pensato un po ', sarebbe piuttosto semplice scrivere un passaggio di pre-compilazione che interroghi il DB e ne generi un enum
N_A

1
Detto questo, non sono sicuro di cosa intenda per "non voglio creare un codice generato dall'enumerazione statica", quindi forse questo non si adatta alle necessità.
N_A

Risposte:


97

Sto facendo esattamente questa cosa, ma è necessario eseguire un qualche tipo di generazione di codice affinché funzioni.

Nella mia soluzione, ho aggiunto un progetto "EnumeratedTypes". Questa è un'applicazione console che ottiene tutti i valori dal database e da essi costruisce le enumerazioni. Quindi salva tutte le enumerazioni in un assembly.

Il codice di generazione di enum è così:

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

Gli altri miei progetti nella soluzione fanno riferimento a questo assembly generato. Di conseguenza, posso quindi utilizzare le enumerazioni dinamiche nel codice, complete di intellisense.

Quindi, ho aggiunto un evento di post-compilazione in modo che dopo la creazione del progetto "EnumeratedTypes", venga eseguito da solo e generi il file "MyEnums.dll".

A proposito, aiuta a cambiare l' ordine di generazione del progetto in modo che "EnumeratedTypes" venga creato per primo. Altrimenti, una volta che inizi a utilizzare il tuo .dll generato dinamicamente, non sarai in grado di creare una build se il .dll viene mai eliminato. (Tipo di problema con pollo e uova: gli altri tuoi progetti nella soluzione hanno bisogno di questo .dll per essere compilato correttamente e non puoi creare il .dll finché non crei la tua soluzione ...)

Ho ottenuto la maggior parte del codice sopra da questo articolo di msdn .

Spero che questo ti aiuti!


7
Per coloro che non sanno come eseguire l'eseguibile risultante dopo la compilazione: 1) Fare clic con il pulsante destro del mouse sul progetto 2) Fare clic su proprietà 3) Fare clic su Eventi di compilazione 4) Sul tipo di casella di testo "Riga di comando dell'evento post-compilazione" $ (TargetPath)
Miguel

È possibile eseguire l'enumerazione dinamica con la definizione di attributo personalizzato come indicato in questo collegamento ?
Balagurunathan Marimuthu

49

Gli enum devono essere specificati in fase di compilazione, non è possibile aggiungere dinamicamente enumerazioni durante il runtime - e perché non ci sarebbe alcun uso / riferimento ad essi nel codice?

Da Professional C # 2008:

Il vero potere delle enumerazioni in C # è che dietro le quinte vengono istanziate come strutture derivate dalla classe base, System.Enum. Ciò significa che è possibile chiamare metodi contro di loro per eseguire alcune attività utili. Si noti che a causa del modo in cui è implementato .NET Framework non si verifica alcuna perdita di prestazioni associata al trattamento sintattico delle enumerazioni come strutture. In pratica, una volta compilato il codice, le enumerazioni esisteranno come tipi primitivi, proprio come int e float.

Quindi, non sono sicuro che tu possa usare Enums nel modo in cui vuoi.


1
non sono sicuro di quale sia il ragionamento di billfredtom, ma il mio era che potevo evitare di fare ricerche manuali di stringhe per certe chiavi, invece di averle incorporate nel mio codice. Preferisco solo essere in grado di eseguire la logica su valori fortemente tipizzati anziché stringhe deboli. Un avvertimento sarebbe che, poiché ora abbiamo codice che si basa su un Enum generato dinamicamente, se cancelliamo il valore dal database, la prossima volta che proviamo a compilare il nostro codice fallirà.
Pandincus

14
Poster e 18 voti positivi hanno mancato il suo punto. Sembra che voglia enumerazioni generate , non enumerazioni dinamiche di runtime.
Matt Mitchell

+1. Un enum è fondamentalmente solo un altro modo per definire costanti intere (anche se System.Enumha alcune funzionalità aggiuntive). Invece di scrivere const int Red=0, Green=1, Blue=3;scrivi tu enum { Red, Green, Blue }. Una costante è per definizione costante e non dinamica.
Olivier Jacot-Descombes

2
@Oliver Se vuoi discutere la semantica, sì, hai ragione. Ma sono d'accordo con il commento di Graphain: credo che l'OP stia cercando enumerazioni generate . Vuole che i valori enum provengano dal database e non debbano codificarli come hardcoded.
Pandincus

1
Oppure ... diciamo che permetto a qualcuno nel mio web.config di definire i tipi di token per i modelli di email per il mio codice di modelli di email. Sarebbe bello se il mio enum esistente chiamato EmailTokens che rappresenta quei tipi di stringa venisse generato in base a quei tipi definiti nel mio web.config. Quindi, se qualcuno aggiunge un nuovo token e-mail nel webconfig tramite il mio valore chiave, ad esempio "Email, FName" e ho già un enum che userò per rappresentare questi token come EmailTemplate.Email sarebbe carino se qualcuno potesse semplicemente aggiungere un nuovo token di stringa in quella chiave nel web.config e il mio enum aggiungerebbe automaticamente const
PositiveGuy

18

Deve essere un vero enum? Che ne dici di usare Dictionary<string,int>invece un ?

per esempio

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

11
Non proverei a farlo in questo modo. Perdi i controlli del tempo di compilazione e diventi incline a errori di battitura. Tutti i vantaggi degli enum sono andati. Potresti introdurre costanti stringa, ma poi sei tornato dove hai iniziato.
Daniel Brückner

1
Sono d'accordo. Ma ricorda che le stringhe digitate in modo errato verranno rilevate in fase di esecuzione. Basta aggiungere un test case per coprire tutti i membri enum.
Autodidatta

1
l'errata digitazione non è un problema se si utilizzano costanti anziché letterali
Maslow

@ Maslow Supponiamo che tu intenda enumerazioni, non costanti stringa.
Matt Mitchell

4
+1. L'uso di un dizionario o di un HashSet si avvicina di più a quella che potrebbe essere un'enumerazione dinamica. Completamente dinamico significa che avviene in fase di esecuzione e quindi il controllo degli errori dovrà essere eseguito in fase di esecuzione.
Olivier Jacot-Descombes

13

L'ho fatto con un modello T4 . È abbastanza banale rilasciare un file .tt nel progetto e configurare Visual Studio per eseguire il modello T4 come passaggio di pre-compilazione.

Il T4 genera un file .cs, il che significa che puoi semplicemente interrogare il database e creare un enum in un file .cs dal risultato. Collegato come attività di pre-compilazione, ricrea la tua enum su ogni build, oppure puoi eseguire T4 manualmente se necessario.


12

Supponiamo che tu abbia quanto segue nel tuo DB:

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

Costruisci una selezione per ottenere i valori di cui hai bisogno:

select * from enums e inner join enum_values ev on ev.enums_id=e.id where e.id=0

Costruisci il codice sorgente per l'enum e otterrai qualcosa del tipo:

String enumSourceCode = "enum " + enumName + "{" + enumKey1 + "=" enumValue1 + "," + enumKey2 + ... + "}";

(ovviamente questo è costruito in un ciclo di qualche tipo.)

Poi arriva la parte divertente, compilare la tua enum e usarla:

CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cs = new CompilerParameters();
cp.GenerateInMemory = True;

CompilerResult result = provider.CompileAssemblyFromSource(cp, enumSourceCode);

Type enumType = result.CompiledAssembly.GetType(enumName);

Ora hai il tipo compilato e pronto per l'uso.
Per ottenere un valore enum memorizzato nel DB puoi utilizzare:

[Enum].Parse(enumType, value);

dove valore può essere il valore intero (0, 1, ecc.) o il testo / chiave enum (Apple, Banana, ecc.)


4
In che modo questo aiuterebbe effettivamente? Non c'è protezione dai tipi e nessun intellisense. Fondamentalmente è solo un modo più complicato di usare una costante poiché deve fornire comunque il valore.
Runeborg

2
Sani - perfetto! Questo era esattamente ciò di cui avevo bisogno. Per coloro che mettono in dubbio il motivo di qualcosa di simile, sto usando una libreria di fornitori che richiede che una proprietà sia impostata sul nome di un'enumerazione. L'enumerazione limita l'intervallo di valori valido per una proprietà diversa dello stesso oggetto. Nel mio caso sto caricando i metadati, compreso l'intervallo di valori valido da un database; e no, il codice del fornitore non supporta il passaggio di una raccolta di qualsiasi tipo alla proprietà. Grazie

10

Sto solo mostrando la risposta di Pandincus con il codice "dello scaffale" e qualche spiegazione: hai bisogno di due soluzioni per questo esempio (so che potrebbe essere fatto anche tramite una;), lascia che gli studenti avanzati lo presentino ...

Quindi ecco l'SQL DDL per la tabella:

USE [ocms_dev]
    GO

CREATE TABLE [dbo].[Role](
    [RoleId] [int] IDENTITY(1,1) NOT NULL,
    [RoleName] [varchar](50) NULL
) ON [PRIMARY]

Quindi ecco il programma della console che produce la 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 

Ecco la programmazione della Console che stampa l'output (ricorda che deve fare riferimento alla dll). Lascia che gli studenti avanzati presentino la soluzione per combinare tutto in un'unica soluzione con il caricamento dinamico e il controllo se è già presente 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 -Perché dichiari flagFileExistsquando non viene utilizzato da nessun'altra parte nell'applicazione?
Michael Kniskern,

2
Immagino sia un bug di; I)
Yordan Georgiev

5

Non stiamo arrivando a questo dalla direzione sbagliata?

Se è probabile che i dati cambino durante la durata del rilascio distribuito, un'enumerazione non è appropriata ed è necessario utilizzare un dizionario, un hash o un'altra raccolta dinamica.

Se si sa che il set di valori possibili è fisso per la durata della versione distribuita, è preferibile un'enumerazione.

Se è necessario disporre di qualcosa nel database che replica il set enumerato, perché non aggiungere un passaggio di distribuzione per cancellare e ripopolare la tabella del database con il set definitivo di valori enum?


Sì e no, Sì perché hai ragione, l'intero punto è che l'enumerazione è statica. Puoi evitare errori di battitura e sapere anche cosa è disponibile. Con dizionario e db - potrebbe essere qualsiasi cosa. Ma a volte vuoi il frutto di entrambi gli alberi quando puoi sceglierne solo uno.
Ken il

4

Mi piace sempre scrivere la mia "enumerazione personalizzata". Ho una classe un po 'più complessa, ma posso riutilizzarla:

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

Ora ho solo bisogno di creare il mio enum che voglio usare:

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

Finalmente posso usarlo come voglio:

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

E il mio output sarebbe:

Element1: foo
Is 'Element2': False

Element2: bar
Is 'Element2': True    

2

Vuoi System.Web.Compilation.BuildProvider

Dubito anche della saggezza di farlo, ma forse c'è un buon caso d'uso a cui non riesco a pensare.

Quello che stai cercando sono Build Provider cioè System.Web.Compilation.BuildProvider

Sono usati in modo molto efficace da SubSonic , puoi scaricare il sorgente e vedere come li usano, non avrai bisogno di nulla di intricato la metà di quello che stanno facendo.

Spero che questo ti aiuti.



0

Non credo che ci sia un buon modo per fare quello che vuoi. E se ci pensi non credo che questo sia quello che vuoi veramente.

Se avessi un'enumerazione dinamica, significa anche che devi alimentarla con un valore dinamico quando fai riferimento ad essa. Forse con molta magia potresti ottenere una sorta di IntelliSense che si occupi di questo e generi un enum per te in un file DLL. Ma considera la quantità di lavoro necessaria, quanto sarebbe inefficace accedere al database per recuperare le informazioni di IntelliSense, nonché l'incubo della versione che controlla il file DLL generato.

Se davvero non vuoi aggiungere manualmente i valori enum (dovrai comunque aggiungerli al database) usa invece uno strumento di generazione del codice, ad esempio i modelli T4 . Fai clic con il pulsante destro del mouse + esegui e hai la tua enum definita staticamente nel codice e ottieni tutti i vantaggi dell'utilizzo delle enumerazioni.


0

L'uso di enumerazioni dinamiche non è corretto, indipendentemente dal modo. Dovrai affrontare la difficoltà di "duplicare" i dati per garantire un codice chiaro e facile da mantenere in futuro.

Se inizi a introdurre librerie generate automaticamente, stai sicuramente causando più confusione ai futuri sviluppatori che devono aggiornare il tuo codice piuttosto che fare semplicemente il tuo enum codificato all'interno dell'oggetto di classe appropriato.

Gli altri esempi forniti sembrano piacevoli ed eccitanti, ma pensa al sovraccarico della manutenzione del codice rispetto a ciò che ottieni da esso. Inoltre, quei valori cambieranno così frequentemente?


0

Un modo per mantenere gli enum e creare un elenco dinamico di valori allo stesso tempo consiste nell'usare gli enum attualmente disponibili con un dizionario creato dinamicamente.

Poiché la maggior parte delle enumerazioni viene utilizzata nel contesto in cui è definita per essere utilizzata e le "enumerazioni dinamiche" saranno supportate da processi dinamici, è possibile distinguere le 2.

Il primo passaggio è creare una tabella / raccolta che contenga ID e riferimenti per le voci dinamiche. Nella tabella si autoincrementerà molto più grande del valore Enum più grande.

Ora arriva la parte per i tuoi Enum dinamici, presumo che utilizzerai gli Enum per creare un insieme di condizioni che applicano un insieme di regole, alcune sono generate dinamicamente.

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

creare un oggetto

string[] types = { "String", "Boolean", "Int32", "Enum", "Point", "Thickness", "long", "float" };
XEnum xe = new XEnum("Enum");
        xe.addRange(types);
        return xe.getEnum();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.