web-dev-qa-db-fra.com

Créer automatiquement une énumération basée sur les valeurs d'une table de recherche de base de données

Comment créer automatiquement une énumération et ensuite utiliser ses valeurs en C # en fonction des valeurs d'une table de recherche de base de données (à l'aide de la couche de données de bibliothèque d'entreprise)?

Par exemple, si j'ajoute une nouvelle valeur de recherche dans la base de données, je ne souhaite pas avoir à ajouter manuellement la déclaration de valeur d'enum statique supplémentaire dans le code. J'aimerais que l'énumération soit synchronisée avec la base de données.

Existe-t-il une telle chose? 


Je ne souhaite pas créer d'énumération statique générée par un code (conformément à The Code Project article Enum Code Generator - Génération automatique de code enum à partir de tables de recherche de base de données) être complètement automatique.

104
billfredtom

Je fais exactement cela, mais vous avez besoin de faire une sorte de génération de code pour que cela fonctionne.

Dans ma solution, j'ai ajouté un projet "EnumeratedTypes". Il s'agit d'une application console qui récupère toutes les valeurs de la base de données et construit les énumérations à partir de celles-ci. Ensuite, tous les enums sont sauvegardés dans une assemblée.

Le code de génération enum est comme ceci:

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

Mes autres projets dans la solution font référence à cette Assemblée générée. En conséquence, je peux ensuite utiliser les enums dynamiques dans le code, avec intellisense.

Ensuite, j'ai ajouté un événement post-génération afin qu'après ce projet "EnumeratedTypes" soit généré, il s'exécute et génère le fichier "MyEnums.dll".

En passant, il est utile de modifier l'ordre build de votre projet afin que "EnumeratedTypes" soit créé en premier. Sinon, une fois que vous aurez commencé à utiliser votre fichier .dll généré dynamiquement, vous ne pourrez pas créer de version si le fichier .dll est supprimé. (Problème type poulet et œuf - vos autres projets de la solution ont besoin de ce fichier .dll pour être construit correctement et vous ne pouvez pas créer le fichier .dll tant que vous n'avez pas créé votre solution ...)

J'ai obtenu la plupart du code ci-dessus de cet article msdn .

J'espère que cela t'aides!

93
Pandincus

Les énumérations doivent être spécifiées au moment de la compilation, vous ne pouvez pas ajouter dynamiquement des énumérations pendant l'exécution - et pourquoi voudriez-vous qu'il n'y ait aucune utilisation/référence dans le code?

De Professional C # 2008:

Le véritable pouvoir des énumérations en C # réside dans le fait qu’elles sont instanciées en tant que structures dérivées de la classe de base, System.Enum. Cela signifie qu'il est possible d'appeler des méthodes sur ces tâches pour effectuer certaines tâches utiles. Notez qu'en raison de la manière dont le .NET Framework est implémenté, il n'y a pas de perte de performance associée au traitement syntaxique des enums en tant que structs. En pratique, une fois votre code compilé, les énumérations existent sous forme de types primitifs, tout comme int et float.

Donc, je ne suis pas sûr que vous puissiez utiliser Enums comme vous le souhaitez.

47
Marcus L

Est-ce que cela doit être une énumération réelle? Que diriez-vous d'utiliser un Dictionary<string,int> à la place?

par exemple 

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

Disons que vous avez les éléments suivants dans votre base de données: 

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

Construisez une sélection pour obtenir les valeurs dont vous avez besoin:

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

Construisez le code source pour l'énum et vous obtiendrez quelque chose comme:

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

(évidemment, cela est construit dans une boucle de quelque sorte.)

Vient ensuite la partie amusante, Compiler votre enum et l’utiliser:

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

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

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

Vous avez maintenant le type compilé et prêt à être utilisé.
Pour obtenir une valeur enum stockée dans la base de données, vous pouvez utiliser:

[Enum].Parse(enumType, value);

où valeur peut être la valeur entière (0, 1, etc.) ou le texte/clé enum (Apple, Banana, etc.)

11

Il suffit de montrer la réponse de Pandincus avec le code "du plateau" et quelques explications: ...

Alors voici le SQL DDL pour la table: 

USE [ocms_dev]
    GO

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

Voici donc le programme de la console produisant 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 

Voici la programmation de la console imprimant la sortie (rappelez-vous qu’elle doit référencer la dll). Laissez les étudiants avancés présenter la solution pour combiner le tout en une solution avec le chargement dynamique et vérifier s’il existe déjà des 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
10
Yordan Georgiev

Je l'ai fait avec un T4 template. Il est assez simple de déposer un fichier .tt dans votre projet et de configurer Visual Studio pour exécuter le modèle T4 en tant qu'étape de pré-génération. 

Le T4 génère un fichier .cs, ce qui signifie que vous pouvez le demander simplement à la base de données et créer une énumération dans un fichier .cs à partir du résultat. En tant que tâche de pré-génération, il recréerait votre enum sur chaque génération, ou vous pouvez exécuter le T4 manuellement si nécessaire.

10
CodingWithSpike

Ne venons-nous pas à cela de la mauvaise direction?

Si les données sont susceptibles de changer du tout pendant la durée de vie de la version déployée, une enum n'est pas appropriée, et vous devez utiliser un dictionnaire, un hachage ou une autre collection dynamique.

Si vous savez que l'ensemble des valeurs possibles est fixe pour la durée de vie de la version déployée, une enum est préférable.

Si vous devez avez quelque chose dans votre base de données qui réplique le jeu énuméré, alors pourquoi ne pas ajouter une étape de déploiement pour effacer et repeupler la table de base de données avec le jeu définitif de valeurs enum?

3
Brian Lowe

J'aime toujours écrire mon propre "Custom Enum". J'ai une classe un peu plus complexe, mais je peux la réutiliser:

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

Maintenant, j'ai juste besoin de créer mon enum que je veux utiliser:

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

Enfin, je peux l'utiliser comme je veux:

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

Et ma sortie serait:

Element1: foo
Is 'Element2': False

Element2: bar
Is 'Element2': True    
2
Traummaennlein

Vous voulez System.Web.Compilation.BuildProvider

Je doute aussi de la sagesse de faire cela, mais alors il y a peut-être un bon cas d'utilisation auquel je ne peux pas penser.

Ce que vous recherchez sont Construire des fournisseurs i.e. System.Web.Compilation.BuildProvider

Ils sont utilisés très efficacement par SubSonic , vous pouvez télécharger le code source et voir comment ils les utilisent. Vous n'aurez besoin de rien d'aussi complexe que ce qu'ils font.

J'espère que cela t'aides.

2
Binary Worrier

Vous pouvez utiliser CodeSmith pour générer quelque chose comme ceci:

http://www.csharping.com/PermaLink,guid,cef1b637-7d37-4691-8e49-138cbf1d51e9.aspx

1
Rick Boarman

Utiliser des énumérations dynamiques est mauvais, peu importe la méthode. Vous devrez passer par la «duplication» des données pour vous assurer que le code soit clair, facile et facile à gérer à l’avenir.

Si vous commencez à introduire des bibliothèques générées automatiquement, vous causerez certainement plus de confusion aux futurs développeurs devant mettre à jour votre code plutôt que de simplement coder votre enum dans l'objet de classe approprié.

Les autres exemples donnés semblent intéressants et passionnants, mais réfléchissez aux frais généraux liés à la maintenance du code par rapport à ce que vous obtenez. De plus, ces valeurs vont-elles changer aussi souvent?

0
Martin

Une façon de conserver les énumérations et de créer simultanément une liste de valeurs dynamique consiste à utiliser les énumérations que vous avez actuellement avec un dictionnaire créé dynamiquement.

Comme la plupart des énumérations sont utilisées dans le contexte dans lequel elles ont été définies, et que les "énumérations dynamiques" sont prises en charge par des processus dynamiques, vous pouvez distinguer le 2.

La première étape consiste à créer une table/collection contenant les ID et les références pour les entrées dynamiques. Dans le tableau, vous auto-incrémentez beaucoup plus que votre plus grande valeur Enum.

Vient maintenant la partie relative à vos énumérations dynamiques. Je suppose que vous utiliserez les énumérations pour créer un ensemble de conditions qui appliquent un ensemble de règles. Certaines sont générées de manière dynamique.

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
Jackstine

Je ne pense pas qu'il existe un bon moyen de faire ce que vous voulez. Et si vous y réfléchissez, je ne pense pas que ce soit ce que vous voulez vraiment.

Si vous avez une énumération dynamique, cela signifie également que vous devez l'alimenter avec une valeur dynamique lorsque vous la référencez. Peut-être qu'avec beaucoup de magie, vous pourriez réaliser une sorte de IntelliSense qui s'en chargerait et générerait une énumération pour vous dans un fichier DLL. Mais considérons la quantité de travail que cela prendrait, le manque d'efficacité de l'accès à la base de données pour récupérer les informations IntelliSense, ainsi que le cauchemar de la version contrôlant le fichier DLL généré.

Si vous ne voulez vraiment pas ajouter manuellement les valeurs enum (vous devrez quand même les ajouter à la base de données), utilisez plutôt un outil de génération de code, par exemple T4 templates. Cliquez avec le bouton droit de la souris sur run et vous obtenez votre enum défini statiquement dans le code et vous bénéficiez de tous les avantages liés à l'utilisation d'énums.

0
Runeborg

classe constructeur enum 

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

créer un objet

string[] types = { "String", "Boolean", "Int32", "Enum", "Point", "Thickness", "long", "float" };
XEnum xe = new XEnum("Enum");
        xe.addRange(types);
        return xe.getEnum();
0
Mahdi Khalili