2010-04-15 19 views
46

¿Cuál es el mejor método para almacenar un Enum en una base de datos usando C# y Visual Studio y MySQL Data Connector?Mejor método para almacenar Enum en la base de datos

Voy a crear un nuevo proyecto con más de 100 entradas, y la mayoría de ellas deberá almacenarse en la base de datos. Crear conversores para cada uno sería un proceso largo, por lo que me pregunto si Visual Studio o alguien tiene algún método para esto que no haya escuchado.

+0

+1 Me interesan las opiniones sobre si este tipo de cosas se deben aplicar con tablas separadas y una restricción FK o solo una restricción regular si alguien tiene alguna vista. –

+2

@Martin No tengo pensado responder, ya que esto tenderá a ser bastante subjetivo, pero para OLTP/ODS utilizaría tablas separadas con una restricción FK. Para una solución de informes DSS, desnormalizaría y almacenaría el nombre simbólico (o descripción) de la enumeración en la tabla de informes con los demás hechos. –

Respuesta

42
[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 
} 

funciona como un encanto! No es necesario convertir (int) Enum o (Enum) int en el código. Solo use el código enum y ef primero guardará el int por usted. p.s .: "No se requiere el atributo [EnumDataType (typeof (PhoneTypes))], solo un extra si desea una funcionalidad adicional.

alternativa que puede hacer:

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

Al final, necesitará una excelente manera de lidiar con tareas de codificación repetitivas, como los convertidores enum. Puede usar un generador de código como MyGeneration o CodeSmith, entre muchos otros o quizás un ORM mapper like nHibernate para manejar todo por usted.

En cuanto a la estructura ... con cientos de enumeraciones que consideraría en primer lugar tratar de organizar los datos en una sola tabla que podría ser algo como esto: (pseudo sql)

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

que le permitiría para almacenar su información enum en una sola tabla. EnumType también podría ser una clave externa para una tabla que define las diferentes enumeraciones.

Sus objetos de negocio se vincularán a esta tabla a través de EnumId. El tipo de enumeración está allí solo para la organización y el filtrado en la interfaz de usuario. Utilizar todo esto, por supuesto, depende de la estructura de tu código y del dominio del problema.

Por cierto, en este escenario, querrá establecer un índice agrupado en EnumType en lugar de dejar el clúster predeterminado idx que se crea en el PKey.

+0

Forma interesante de almacenar enums. Pero la principal forma por la que quiero usar Enums (basado en C#) es para la legibilidad, por ej. if (something.Type == Enum.EnumType) Este proyecto será muy largo y complejo, y con cientos de enum será difícil hacer un seguimiento y volver a la base de datos cada vez. –

10

Almacenamos los nuestros como ints o longs y luego podemos simplemente lanzarlos hacia adelante y hacia atrás. Probablemente no sea la solución más robusta, pero eso es lo que hacemos.

estamos utilizando conjuntos de datos con tipo, así, por ejemplo:

eunum BlockTreatmentType 
{ 
    All = 0 
}; 

// blockTreatmentType is an int property 
blockRow.blockTreatmentType = (int)BlockTreatmentType.All; 
BlockTreatmentType btt = (BlockTreatmentType)blockRow.blocktreatmenttype; 
+0

¿Cómo los echas? ¿Y qué almacena? El valor ordinal, o un atributo personalizado? –

+0

agregó un fragmento de código –

+2

esta costura como una solución aceptable. Dejaré la pregunta abierta por un tiempo para ver qué se le ocurre a la gente. Supongo que usted establece manualmente el valor int de la enumeración para evitar el cambio de orden/cardinal? –

3

Algunas cosas que usted debe tomar en consideración.

La columna de enumeración va a ser utilizada directamente por otras aplicaciones como, por ejemplo, informes. Esto limitará la posibilidad de que la enumeración se almacene en su formato entero porque ese valor no tendrá significado cuando esté presente en un informe a menos que los informes tengan una lógica personalizada.

¿Cuáles son las necesidades de i18n para su aplicación? Si solo admite un idioma, puede guardar la enumeración como texto y crear un método auxiliar para convertir desde una cadena de descripción. Puede usar [DescriptionAttribute] para esto y probablemente encuentre los métodos para la conversión buscando SO.

Si, por otro lado, tiene la necesidad de admitir el acceso de múltiples idiomas y aplicaciones externas a sus datos, puede empezar a considerar si la enumeración es realmente la respuesta. Se pueden considerar otras opciones, como tablas de búsqueda, si el escenario es más complejo.

Las enumeraciones son excelentes cuando están contenidas en el código ... cuando cruzan ese límite, las cosas tienden a ser un poco complicadas.


Actualización:

Puede convertir de un entero usando Enum.ToObject método. Esto implica que conoce el tipo de enumeración al convertir.Si desea que sea completamente genérico, debe almacenar el tipo de enumeración junto con su valor en la base de datos. Puede crear tablas de soporte de diccionario de datos para decirle qué columnas son enumeraciones y qué tipo son.

+0

Idealmente, los informes se construirán utilizando la misma biblioteca que contiene las enumeraciones, por lo que almacenar enteros en la base de datos está perfectamente bien. El único problema es que, al extraer de la base de datos, ¿cómo se convierte a la enumeración original? (Más de 100 de enumeraciones cada una con más de 5 valores) –

1

No estoy seguro de si es el más flexible, pero simplemente podría almacenar las versiones de cadena de ellos. Es ciertamente legible, pero tal vez sea difícil de mantener. Enumeraciones convertir de cadenas y de vuelta con bastante facilidad:

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

Si quieres una tienda de todos los valores de enumeraciones, puede probar las siguientes tablas para almacenar las enumeraciones y sus miembros, y el fragmento de código para agregar esos valores. Sin embargo, solo haría esto en el momento de la instalación, ¡ya que esos valores nunca cambiarán hasta que vuelva a compilar!

DB Tabla:

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# Fragmento:

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

por qué no intentar separar las enumeraciones por completo de la base de datos? He encontrado este artículo a ser una gran referencia mientras se trabaja en algo similar:

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

Las ideas en que deben aplicarse con independencia de lo que se utiliza DB. Por ejemplo, en MySQL se puede utilizar el tipo de datos "enumeración" para hacer cumplir sus enumeraciones codificados:

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

Saludos

2

Si necesita almacenar en los valores de cadena de base de datos de campo de enumeración, mejor hacer como se muestra a continuación. Por ejemplo, puede ser necesario si usa SQLite, que no admite campos 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 
} 
+1

Dependiendo de la frecuencia con la que conviertas estos objetos (por ejemplo, 50k), es mejor tener un convertidor/métodos de extensión con un método de cambio como ToString y Enum.Parse utiliza la reflexión. Solo FYI –

+0

Sí. Estoy de acuerdo. Pero para una carga pequeña, la solución fácil es adecuada. – trueboroda

0

A DB primer enfoque se puede utilizar mediante la creación de una tabla consistente para cada enum donde el nombre de la columna Id coincide con nombre de tabla. Es ventajoso tener valores enum disponibles dentro de la base de datos para admitir restricciones de clave externa y columnas amistosas en las vistas. Actualmente estamos dando soporte a ~ 100 tipos de enum distribuidos en numerosas bases de datos versionadas.

Para una preferencia de Código Primero, la estrategia T4 que se muestra a continuación probablemente podría revertirse para escribir en la base de datos.

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

Cada tabla se puede importar a C# utilizando un T4 template (*.tt) script.

  1. Crear un "Proyecto de enumeración". Agregue el archivo .tt que se muestra a continuación.
  2. Cree una subcarpeta para cada nombre de esquema de base de datos.
  3. Para cada tipo de enumeración, cree un archivo cuyo nombre sea SchemaName.TableName.tt. El contenido del archivo son siempre los mismos de una sola línea: < # @ incluye file = ".. \ EnumGenerator.ttinclude" #>
  4. continuación para crear/actualizar las enumeraciones, haga clic derecho en 1 o más archivos y "Ejecutar Herramienta personalizada "(todavía no tenemos la actualización automática). Se añadirá/actualizar un archivo .cs al proyecto:
using System.CodeDom.Compiler; 
namespace TheCompanyNamespace.Enumerations.Config 
{ 
    [GeneratedCode("Auto Enum from DB Generator", "10")] 
    public enum DatabasePushJobState 
    {  
      Undefined = 0, 
      Created = 1,   
    } 
    public partial class EnumDescription 
    { 
     public static string Description(DatabasePushJobState enumeration) 
     { 
      string description = "Unknown"; 
      switch (enumeration) 
      {     
       case DatabasePushJobState.Undefined: 
        description = "Undefined"; 
        break; 

       case DatabasePushJobState.Created: 
        description = "Created"; 
        break;     
      } 
      return description; 
     } 
    } 
    // select DatabasePushJobStateId, Name, coalesce(Description,Name) as Description 
    // from TheDefaultDatabase.[SchName].[DatabasePushJobState] 
    // where 1=1 order by DatabasePushJobStateId 
} 

Y, por último, el guión un tanto retorcido T4 (simplificado de numerosas soluciones alternativas). Necesitará ser personalizado a su ambiente. Un indicador de depuración puede enviar mensajes a C#. También hay una opción "Depurar plantilla T4" al hacer clic derecho en el archivo .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" 
      : ""; 
    } 
#> 

Con suerte el marco entidad apoyará algún día una combinación de estas respuestas para ofrecer el tipado fuerte C# enumeración dentro de los registros y el reflejo de base de los valores.

Cuestiones relacionadas