web-dev-qa-db-fra.com

Meilleure méthode pour stocker Enum dans la base de données

Quelle est la meilleure méthode pour stocker un Enum dans une base de données en utilisant C # And Visual Studio et le connecteur de données MySQL.

Je vais créer un nouveau projet avec plus de 100 Enums, et la majorité d'entre eux devront être stockés dans la base de données. Créer des convertisseurs pour chacun serait un processus fastidieux, donc je me demande si Visual Studio ou quelqu'un a des méthodes pour cela que je n'ai pas entendues.

54
Michal Ciechan
    [Required]
    public virtual int PhoneTypeId
    {
        get
        {
            return (int)this.PhoneType;
        }
        set
        {
            PhoneType = (PhoneTypes)value;
        }
    }
    [EnumDataType(typeof(PhoneTypes))]
    public PhoneTypes PhoneType { get; set; }

public enum PhoneTypes
{
    Mobile = 0,
    Home = 1,
    Work = 2,
    Fax = 3,
    Other = 4
}

Fonctionne comme un charme! Pas besoin de convertir (int) Enum ou (Enum) int en code. Il suffit d’utiliser le code enum et ef en premier pour enregistrer l’int pour vous. p.s .: L'attribut "[EnumDataType (typeof (PhoneTypes))]]" n'est pas obligatoire, c'est juste un extra si vous voulez des fonctionnalités supplémentaires.

Sinon, vous pouvez faire:

[Required]
    public virtual int PhoneTypeId { get; set; }
    [EnumDataType(typeof(PhoneTypes))]
    public PhoneTypes PhoneType
    {
        get
        {
            return (PhoneTypes)this.PhoneTypeId;
        }
        set
        {
            this.PhoneTypeId = (int)value;
        }
    }
52
Joao Leme

Nous stockons les nôtres comme des objets ou des longs et ensuite nous pouvons simplement les jeter d'avant en arrière. Probablement pas la solution la plus robuste, mais c'est ce que nous faisons.

nous utilisons des DataSets typés, par exemple:

enum BlockTreatmentType 
{
    All = 0
};

// blockTreatmentType is an int property
blockRow.blockTreatmentType = (int)BlockTreatmentType.All;
BlockTreatmentType btt = (BlockTreatmentType)blockRow.blocktreatmenttype;
10
Muad'Dib

Si vous voulez stocker toutes vos valeurs enums, vous pouvez essayer les tables ci-dessous pour stocker les enums et leurs membres, et l'extrait de code pour ajouter ces valeurs. Cependant, je ne le ferais qu'au moment de l'installation, car ces valeurs ne changeront jamais jusqu'à ce que vous recompiliez!

Table DB:

   create table EnumStore (
    EnumKey int NOT NULL identity primary key,
    EnumName varchar(100)
);
GO

create table EnumMember (
    EnumMemberKey int NOT NULL identity primary key,
    EnumKey int NOT NULL,
    EnumMemberValue int,
    EnumMemberName varchar(100)
);
GO
--add code to create foreign key between tables, and index on EnumName, EnumMemberValue, and EnumMemberName

C # Snippet:

void StoreEnum<T>() where T: Enum
    {
        Type enumToStore = typeof(T);
        string enumName = enumToStore.Name;

        int enumKey = DataAccessLayer.CreateEnum(enumName);
        foreach (int enumMemberValue in Enum.GetValues(enumToStore))
        {
            string enumMemberName = Enum.GetName(enumToStore, enumMemberValue);
            DataAccessLayer.AddEnumMember(enumKey, enumMemberValue, enumMemberName);
        }
    }
3
cortijon

Certaines choses que vous devriez prendre en considération. 

La colonne d’énumération va-t-elle être utilisée directement par d’autres applications, comme par exemple des rapports? Cela limitera la possibilité que l'énumération soit stockée dans son format entier, car cette valeur n'aura aucune signification si elle est présente dans un rapport, à moins que les rapports ne comportent une logique personnalisée.

Quels sont les besoins d'i18n pour votre application? S'il ne prend en charge qu'une langue, vous pouvez enregistrer l'énumération sous forme de texte et créer une méthode d'assistance à convertir à partir d'une chaîne de description. Vous pouvez utiliser [DescriptionAttribute] pour cela et trouver des méthodes de conversion en cherchant SO.

Si, par contre, vous avez besoin de prendre en charge plusieurs langues et un accès d’application externe à vos données, vous pouvez commencer à vous demander si l’énumération est vraiment la solution. Une autre option, telle que les tables de consultation, peut être envisagée si le scénario est plus complexe.

Les énumérations sont excellentes lorsqu'elles sont contenues dans du code ... lorsqu'elles franchissent cette frontière, les choses ont tendance à devenir un peu compliquées.


Mettre à jour:

Vous pouvez convertir un entier en utilisant la méthode Enum.ToObject. Cela implique que vous connaissiez le type de l'énumération lors de la conversion. Si vous voulez le rendre complètement générique, vous devez stocker le type de l'énumération avec sa valeur dans la base de données. Vous pouvez créer des tables de prise en charge du dictionnaire de données pour vous indiquer les colonnes et les types d'énumérations.

3
João Angelo

À la fin, vous aurez besoin d’un excellent moyen de traiter des tâches de codage répétitives telles que les convertisseurs d’énumération. Vous pouvez utiliser un générateur de code tel que MyGeneration ou CodeSmith parmi beaucoup d'autres, ou peut-être un mappeur ORM comme nHibernate pour tout gérer à votre place.

Pour ce qui est de la structure ... avec des centaines d’énums, je commencerais par envisager d’organiser les données dans une seule table qui pourrait ressembler à ceci: (pseudo sql)

MyEnumTable(
EnumType as int,
EnumId as int PK,
EnumValue as int )

cela vous permettrait de stocker vos informations enum dans une seule table. EnumType peut également être une clé étrangère vers une table qui définit les différents énumérations.

Vos objets biz seraient liés à cette table via EnumId. Le type enum n'est là que pour l'organisation et le filtrage dans l'interface utilisateur. Bien entendu, l'utilisation de tout cela dépend de la structure de votre code et du domaine qui vous pose problème.

Au fait, dans ce scénario, vous voudriez définir un index clusterisé sur EnumType plutôt que de laisser l'idx de cluster par défaut créé sur la clé PKey.

3
Paul Sasik

Si vous avez besoin de stocker dans les valeurs de chaîne de la base de données du champ enum, procédez comme suit: ____, par exemple, si vous utilisez SQLite, qui ne prend pas en charge les champs enum.

[Required]
public string PhoneTypeAsString
{
    get
    {
        return this.PhoneType.ToString();
    }
    set
    {
        PhoneType = (PhoneTypes)Enum.Parse( typeof(PhoneTypes), value, true);
    }
}

public PhoneTypes PhoneType{get; set;};

public enum PhoneTypes
{
    Mobile = 0,
    Home = 1,
    Work = 2,
    Fax = 3,
    Other = 4
}
2
trueboroda

Je ne suis pas sûr que ce soit le plus flexible, mais vous pouvez simplement en stocker les versions en chaîne. Il est certainement lisible, mais peut-être difficile à maintenir. Les enums sont facilement convertis à partir de chaînes et de dos:

public enum TestEnum
{
    MyFirstEnum,
    MySecondEnum
}

static void TestEnums()
{
    string str = TestEnum.MyFirstEnum.ToString();
    Console.WriteLine( "Enum = {0}", str );
    TestEnum e = (TestEnum)Enum.Parse( typeof( TestEnum ), "MySecondEnum", true );
    Console.WriteLine( "Enum = {0}", e );
}
1
Mark Wilkins

Vous n'avez rien à faire si vous voulez stocker des données. Il suffit de mapper votre propriété dans EF . Si vous souhaitez les stocker sous forme de chaînes, utilisez le convertisseur.

Int (type de base est smallint):

public override void Configure(EntityTypeBuilder<MyEfEntity> b)
{
    ...
    b.Property(x => x.EnumStatus);
}

String (type de base est varchar (50)):

public override void Configure(EntityTypeBuilder<MyEfEntity> b)
{
    ...
    b.Property(x => x.EnumStatus).HasConversion<EnumToStringConverter>();
}

Si vous souhaitez enregistrer votre utilisation de données de base de données, utilisez smallint en tant que colonne dans db. Mais les données ne seront pas lisibles par l'homme et vous devriez définir un index pour chaque élément enum et ne jamais jouer avec:

public enum EnumStatus
{
    Active = 0, // Never change this index
    Archived = 1, // Never change this index
}

Si vous voulez rendre les données de la base de données plus lisibles, vous pouvez les enregistrer sous forme de chaînes (par exemple, varchar (50)). Vous n'avez pas à vous soucier des index et vous avez simplement besoin de mettre à jour les chaînes dans la base de données lorsque vous modifiez les noms enum. Inconvénients: la taille des colonnes augmente l’utilisation des données. Cela signifie que si vous avez une table sur 1 000 000 de lignes, cela peut avoir un impact sur la taille et les performances de la base de données.

Également comme solution, vous pouvez utiliser des noms courts enum:

public enum EnumStatus
{
    [Display(Name = "Active")]
    Act,
    [Display(Name = "Archived")]
    Arc,
}

Ou utilisez votre propre convertisseur pour raccourcir les noms en db:

public enum EnumStatus
{
    [Display(Name = "Active", ShortName = "Act")]
    Active,
    [Display(Name = "Archived", ShortName = "Arc")]
    Archived,
}
...
public override void Configure(EntityTypeBuilder<MyEfEntity> b)
{
    ...
    b.Property(x => x.EnumStatus).HasConversion<MyShortEnumsConverter>();
}

Plus d'informations peuvent être trouvées ici: EF: https://docs.Microsoft.com/en-us/ef/ef6/modeling/code-first/data-types/enums EFCore: https://docs.Microsoft.com/en-us/ef/core/modeling/value-conversions

0
ADM-IT

Pourquoi ne pas essayer de séparer les enums de la base de données? J'ai trouvé cet article très utile en travaillant sur quelque chose de similaire:

http://stevesmithblog.com/blog/reducing-sql-lookup-tables-and-function-properties-in-nhibernate/

Les idées qu’il contient devraient s’appliquer quelle que soit la base de données que vous utilisez. Par exemple, dans MySQL, vous pouvez utiliser le type de données "enum" pour appliquer la conformité à vos énumérations codées:

http://dev.mysql.com/doc/refman/5.0/en/enum.html

À votre santé

0
Paul Hanssen

Une première approche de base de données peut être utilisée en créant une table cohérente pour chaque énumération où le nom de la colonne Id correspond au nom de la table. Il est avantageux de disposer de valeurs énumérées dans la base de données pour prendre en charge les contraintes de clé étrangère et les colonnes conviviales dans les vues. Nous prenons actuellement en charge environ 100 types d’énumération répartis dans de nombreuses bases de données versionnées. 

Pour une préférence Code-First, la stratégie T4 présentée ci-dessous pourrait probablement être inversée pour écrire dans la base de données.

create table SomeSchema.SomeEnumType (
  SomeEnumTypeId smallint NOT NULL primary key,
  Name varchar(100) not null,
  Description nvarchar(1000),
  ModifiedUtc datetime2(7) default(sysutcdatetime()),
  CreatedUtc datetime2(7) default(sysutcdatetime()),
);

Chaque table peut être importée en C # à l’aide d’un script modèle T4 (* .tt)

  1. Créez un "projet d'énumération". Ajoutez le fichier .tt indiqué ci-dessous.
  2. Créez un sous-dossier pour chaque nom de schéma de base de données. 
  3. Pour chaque type enum, créez un fichier dont le nom est SchemaName.TableName.tt. Le fichier Contenu est toujours la même ligne unique: <# @ include File = "..\EnumGenerator.ttinclude" #>
  4. Ensuite, pour créer/mettre à jour les enums, cliquez avec le bouton droit de la souris sur 1 ou plusieurs fichiers et sur "Exécuter l'outil personnalisé" (nous n'avons pas encore de mise à jour automatique). Il ajoutera/mettra à jour un fichier .cs dans le projet:
using System.CodeDom.Compiler; 
 namespace TheCompanyNamespace.Enumerations.Config 
 {
 [GeneratedCode ("Énumération automatique à partir de DB Generator", "10")] 
 énumération publique DatabasePushJobState 
 {
 Non défini = 0, 
 Créé = 1, 
 } 
 classe partielle publique EnumDescription 
 {
 chaîne publique statique Description (énumération DatabasePushJobState) 
 {
 string description = "Unknown"; 
 commutateur (énumération) 
 {
 case DatabasePushJobState.Undefined: 
 description = "Undefined"; 
 Pause;

 case DatabasePushJobState.Created: 
 description = "Créé"; 
 Pause; 
 } 
 renvoyer la description; 
 } 
 } 
 // sélectionnez DatabasePushJobStateId, Name, coalesce (Description, Name) en tant que Description 
 // de TheDefaultDatabase. [SchName]. [DatabasePushJobState] 
 // où 1 = 1 commande par DatabasePushJobStateId 
 } 

Et enfin, le script T4 quelque peu effrayant (simplifié à partir de nombreuses solutions de contournement). Il devra être adapté à votre environnement. Un indicateur de débogage peut générer des messages dans le C #. Il existe également une option "Modèle T4 de débogage" lorsque vous cliquez avec le bouton droit sur le fichier .tt. EnumGenerator.ttinclude:

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".generated.cs" #>
<#@ Assembly Name="EnvDTE" #>
<#@ Assembly Name="System.Core" #>
<#@ Assembly Name="System.Data" #>
<#@ Assembly name="$(TargetPath)" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#  
    bool doDebug = false;   // include debug statements to appear in generated output    

    string schemaTableName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
    string schema = schemaTableName.Split('.')[0];
    string tableName = schemaTableName.Split('.')[1];

    string path = Path.GetDirectoryName(Host.TemplateFile);    
    string enumName = tableName;
    string columnId = enumName + "Id";
    string columnName = "Name"; 
    string columnDescription = "Description";

    string currentVersion = CompanyNamespace.Enumerations.Constants.Constants.DefaultDatabaseVersionSuffix;

    // Determine Database Name using Schema Name
    //
    Dictionary<string, string> schemaToDatabaseNameMap = new Dictionary<string, string> {
        { "Cfg",        "SomeDbName" + currentVersion },
        { "Common",     "SomeOtherDbName" + currentVersion }
        // etc.     
    };

    string databaseName;
    if (!schemaToDatabaseNameMap.TryGetValue(schema, out databaseName))
    {
        databaseName = "TheDefaultDatabase"; // default if not in map
    }

    string connectionString = @"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=" + databaseName + @";Data Source=Machine\Instance";

    schema = "[" + schema + "]";
    tableName = "[" + tableName + "]";

    string whereConstraint = "1=1";  // adjust if needed for specific tables

  // Get containing project
  IServiceProvider serviceProvider = (IServiceProvider)Host;
  DTE dte = (DTE)serviceProvider.GetService(typeof(DTE));
  Project project = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject;
#>
using System;
using System.CodeDom.Compiler;

namespace <#= project.Properties.Item("DefaultNamespace").Value #><#= Path.GetDirectoryName(Host.TemplateFile).Remove(0, Path.GetDirectoryName(project.FileName).Length).Replace("\\", ".") #>
{
    /// <summary>
    /// Auto-generated Enumeration from Source Table <#= databaseName + "." + schema + "." + tableName #>.  Refer to end of file for SQL.
    /// Please do not modify, your changes will be lost!
    /// </summary>
    [GeneratedCode("Auto Enum from DB Generator", "10")]
    public enum <#= enumName #>
    {       
<#
        SqlConnection conn = new SqlConnection(connectionString);
        // Description is optional, uses name if null
        string command = string.Format(
            "select {0}, {1}, coalesce({2},{1}) as {2}" + "\n  from {3}.{4}.{5}\n where {6} order by {0}", 
                columnId,           // 0
                columnName,         // 1
                columnDescription,  // 2
                databaseName,       // 3
                schema,             // 4
                tableName,          // 5
                whereConstraint);   // 6
        #><#= DebugCommand(databaseName, command, doDebug) #><#

        SqlCommand comm = new SqlCommand(command, conn);

        conn.Open();

        SqlDataReader reader = comm.ExecuteReader();
        bool loop = reader.Read();

        while(loop)
        {
#>      /// <summary>
        /// <#= reader[columnDescription] #>
        /// </summary>
        <#= Pascalize(reader[columnName]) #> = <#= reader[columnId] #><# loop = reader.Read(); #><#= loop ? ",\r\n" : string.Empty #>
<#
        }
#>    }


    /// <summary>
    /// A helper class to return the Description for each enumeration value
    /// </summary>
    public partial class EnumDescription
    {
        public static string Description(<#= enumName #> enumeration)
        {
            string description = "Unknown";

            switch (enumeration)
            {<#
    conn.Close();
    conn.Open();
    reader = comm.ExecuteReader();
    loop = reader.Read();

    while(loop)
    {#>                 
                    case <#= enumName #>.<#= Pascalize(reader[columnName]) #>:
                        description = "<#= reader[columnDescription].ToString().Replace("\"", "\\\"") #>";
                        break;
                    <# loop = reader.Read(); #>
<#
      }
      conn.Close();
#> 
            }

            return description;
        }
    }
    /*
        <#= command.Replace("\n", "\r\n        ") #>
    */
}
<#+     
    private string Pascalize(object value)
    {
        Regex rxStartsWithKeyWord = new Regex(@"^[0-9]|^abstract$|^as$|^base$|^bool$|^break$|^byte$|^case$|^catch$|^char$|^checked$|^class$|^const$|^continue$|^decimal$|^default$|^delegate$|^do$|^double$|^else$|^enum$|^event$|^explicit$|^extern$|^$false|^finally$|^fixed$|^float$|^for$|^foreach$|^goto$|^if$|^implicit$|^in$|^int$|^interface$|^internal$|^is$|^lock$|^long$|^namespace$|^new$|^null$|^object$|^operator$|^out$|^overrride$|^params$|^private$|^protected$|^public$|^readonly$|^ref$|^return$|^sbyte$|^sealed$|^short$|^sizeof$|^stackalloc$|^static$|^string$|^struct$|^switch$|^this$|^thorw$|^true$|^try$|^typeof$|^uint$|^ulong$|^unchecked$|^unsafe$|^ushort$|^using$|^virtual$|^volatile$|^void$|^while$", RegexOptions.Compiled);

        Regex rx = new Regex(@"(?:[^a-zA-Z0-9]*)(?<first>[a-zA-Z0-9])(?<reminder>[a-zA-Z0-9]*)(?:[^a-zA-Z0-9]*)");
        string rawName = rx.Replace(value.ToString(), m => m.Groups["first"].ToString().ToUpper() + m.Groups["reminder"].ToString());

        if (rxStartsWithKeyWord.Match(rawName).Success)
            rawName =  "_" + rawName;

        return rawName;    
    }

    private string DebugCommand(string databaseName, string command, bool doDebug)
    {       
        return doDebug
            ? "        // use " + databaseName + ";  " + command + ";\r\n\r\n"
            : "";
    }   
#>

Espérons que le framework d'entités supportera un jour une combinaison de ces réponses pour offrir au typage Cen enum fort dans les enregistrements et la mise en miroir des valeurs de la base de données. 

0
crokusek