2010-04-15 6 views
46

Каков наилучший способ хранения Enum в базе данных с использованием C# и Visual Studio и MySQL Data Connector.Лучший способ для хранения Enum в базе данных

Я собираюсь создать новый проект с более чем 100 Enums, и большинство из них необходимо будет сохранить в базе данных. Создание конвертеров для каждого из них будет длительным процессом, поэтому мне интересно, есть ли у visual studio или кто-то есть какие-то методы для этого, о которых я не слышал.

+0

+1 Меня интересуют мнения о том, следует ли применять этот тип с отдельными таблицами и ограничением FK или просто постоянным ограничением, если у кого есть какие-либо взгляды? –

+2

@Martin Не планируйте отвечать, поскольку это будет иметь тенденцию быть весьма субъективным, но для OLTP/ODS я бы использовал отдельные таблицы с ограничением FK. Для решения отчетности DSS я бы денормализовал и сохранил символическое имя перечисления (или описание) в таблице отчетов с другими фактами. –

ответ

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 
} 

работает как шарм! Нет необходимости конвертировать (int) Enum или (Enum) int в код. Просто используйте enum и ef-код, сначала сохраните int для вас. p.s .: "[EnumDataType (typeof (PhoneTypes))]" атрибут не требуется, просто дополнительный, если вы хотите получить дополнительную функциональность.

В качестве альтернативы вы можете сделать:

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

В конце концов вам понадобится отличный способ справиться с повторяющимися задачами кодирования, такими как конвертеры enum. Вы можете использовать генератор кода, такой как MyGeneration или CodeSmith среди многих других, или, возможно, ORM mapper like nHibernate, чтобы обрабатывать все для вас.

Что касается структуры ... с сотнями перечислений я бы сначала рассмотреть пытаюсь организовать данные в одну таблицу, которая может выглядеть следующим образом: (псевдо SQL)

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

, что позволят вам для хранения информации об enum в одной таблице. EnumType также может быть внешним ключом к таблице, которая определяет различные перечисления.

Ваши объекты biz будут связаны с этой таблицей через EnumId. Тип перечисления существует только для организации и фильтрации в пользовательском интерфейсе. Использование всего этого, конечно, зависит от структуры кода и проблемного домена.

Кстати, в этом случае вы хотели бы установить кластерный индекс в EnumType, а не оставлять кластер idx по умолчанию, созданный на PKey.

+0

Интересный способ хранения перечислений. Но основным способом, почему я хочу использовать Enums (C# based), является читаемость, например. if (something.Type == Enum.EnumType) Этот проект будет очень длинным и сложным проектом и с 100-кратным перечислением будет сложно отслеживать и возвращаться в базу данных каждый раз. –

10

Мы храним наши как ints или longs, а затем мы можем просто бросить их туда и обратно. Наверное, это не самое надежное решение, а то, что мы делаем.

мы используем типизированных DataSets, так, например:

eunum BlockTreatmentType 
{ 
    All = 0 
}; 

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

Как вы набрасываете em? И что вы храните? Ординальное значение или пользовательский атрибут? –

+0

добавил фрагмент кода –

+2

, этот шов, как приемлемое решение. Я оставлю вопрос открытым на некоторое время посмотреть, что люди придумали. Я предполагаю, что вы вручную задали значение int для перечисления, чтобы предотвратить изменение порядка/кардинала? –

3

Некоторые вещи, которые вы должны принять во внимание.

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

Каковы потребности i18n в вашем приложении? Если он поддерживает только один язык, вы можете сохранить перечисление как текст и создать вспомогательный метод для преобразования из строки описания. Вы можете использовать [DescriptionAttribute] для этого, и методы для преобразования, вероятно, можно найти, выполнив поиск SO.

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

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


Update:

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

+0

Идеально отчеты будут построены с использованием той же библиотеки, которая содержит перечисления, поэтому сохранение целых чисел в базе данных отлично. Единственная проблема заключается в том, когда вы извлекаете из базы данных, как вы конвертируете в исходное перечисление? (100+ из перечислений, каждый из которых имеет 5+ значений). –

1

Я не уверен, является ли он наиболее гибким, но вы можете просто сохранить его строковые версии. Это, конечно, читаемо, но, возможно, сложно поддерживать. Перечисления преобразование из строк и обратно довольно легко:

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

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

таблицы БД:

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# Отрывок:

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

Почему бы не попробовать разделения перечислений вообще из БД? Я нашел эту статью, чтобы быть хорошим справочником при работе на что-то подобное:

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

Идеи в ней должны применяться независимо от того, что БД вы используете. Например, в MySQL вы можете использовать «перечисление» тип данных для обеспечения соответствия с кодированными перечислениями:

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

Приветствие

2

Если вам нужно хранить в строковых значений БД полевых перечислений, лучше сделать, как показано на рисунке ниже. Например, это может потребоваться, если вы используете SQLite, которые не поддерживают поля 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

В зависимости от того, как часто вы их конвертируете (например, 50k объектов), вам лучше иметь методы конвертации/расширения с помощью метода switch как ToString и Enum.Parse использует отражение. Просто FYI –

+0

Да. Согласен. Но для небольшой нагрузки подходит простое решение. – trueboroda

0

Первый подход DB можно использовать, создав согласованную таблицу для каждого перечисления, где имя столбца Id совпадает с именем таблицы. Преимущественно иметь значения перечисления, доступные в базе данных, для поддержки ограничений внешнего ключа и дружественных столбцов в представлениях. В настоящее время мы поддерживаем ~ 100 типов перечислений, разбросанных по многочисленным версиям баз данных.

Для предпочтений с кодовым приоритетом стратегия Т4, показанная ниже, вероятно, может быть отменена для записи в базу данных.

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

Каждая таблица может быть импортирована в C# с использованием T4 template (*.tt) script.

  1. Создайте «Проект перечисления». Добавьте файл .tt, показанный ниже.
  2. Создайте подпапку для каждого имени схемы базы данных.
  3. Для каждого типа перечисления создайте файл с именем SchemaName.TableName.tt. Содержимое файла всегда одинаковы одна строка: < # @ включают файл = «.. \ EnumGenerator.ttinclude» #>
  4. Затем, чтобы создать/обновить перечислений, щелкните правой кнопкой мыши на 1 или более файлов и «Run Custom Tool "(у нас пока нет автоматического обновления). Он будет добавлять/обновлять файл .cs в проект:
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 
} 

И, наконец, несколько угловатый T4 скрипт (упрощенный из многочисленных обходные). Он должен быть настроен для вашей среды. Флаг отладки может выводить сообщения в C#. Существует также опция «Debug T4 Template» при щелчке правой кнопкой мыши на файле .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" 
      : ""; 
    } 
#> 

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

Смежные вопросы