Как синхронизировать C # Enum с таблицей в базе данных - PullRequest
8 голосов
/ 04 мая 2011

Это несколько упрощенный пример (я изменил его, чтобы скрыть реальный код). У меня есть приложение на основе базы данных и небольшой инструмент, который разрабатывается отдельно и предназначен для работы с приложением. Существует таблица, которая определяет перечисление, но оно может со временем измениться. Предположим, какое-то приложение (медицинское?) Необходимо для точного отслеживания пола человека.

select * from sex order by id;

id | mnemonic | description
0  | U        | Unknown sex
1  | M        | Male
2  | F        | Female
3  | T        | Trans-gender

И мой C# enum:

public enum SexType
{
    /// <summary>Unknown sex.</summary>
    [Description("U")]
    Unknown = 0,

    /// <summary>Male sex.</summary>
    [Description("M")]
    Male = 1,

    /// <summary>Female sex.</summary>
    [Description("F")]
    Female = 2

    /// <summary>Trans-gender sex.</summary>
    [Description("T")]
    TransGender = 3,
}

Здесь я не должен предполагать, что id является непрерывной последовательностью; и эти флаги перечислений не могут быть объединены, даже если это выглядит так.

Некоторая логика сделана на SQL; некоторые сделаны в C# коде. Например, у меня может быть функция:

// Here we get id for some record from the database.
public static void IsMaleOrFemale(int sexId)
{
    if (!Enum.IsDefined(typeof(SexType), sexId))
    {
        string message = String.Format("There is no sex type with id {0}.", 
            sexId);
        throw new ArgumentException(message, "sexId");
    }

    var sexType = (SexType) sexId;
    return sexType == SexType.Male || sexType == SexType.Female;
}

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

Итак, как мне получить список всех пар (программно, в C#): (0, "U"), (1, "M"), (2, "F"), (3, "T"), посмотрев на enum SexType?

Идея состоит в том, чтобы сравнить этот упорядоченный список с select id, mnemonic from sex order by is asc;.

Ответы [ 6 ]

7 голосов
/ 04 мая 2011

Проверьте Tangible T4Editor .

Я использую его для этого.

Установите его, а затем добавьте этот файл в свой проект ( дополнительная информацияв этом сообщении в блоге ):

EnumGenerator.ttinclude

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".generated.cs" #>
<#@ Assembly Name="System.Data" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#  
    string tableName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
    string path = Path.GetDirectoryName(Host.TemplateFile);
    string columnId = "ID";
    string columnName = "NAME";
    string connectionString = "[your connection string]";
#>
using System; 
using System.CodeDom.Compiler;  

namespace Your.NameSpace.Enums
{     
    /// <summary>
    /// <#= tableName #> auto generated enumeration
    /// </summary>
    [GeneratedCode("TextTemplatingFileGenerator", "10")]
    public enum <#= tableName #>
    { 
<#
    SqlConnection conn = new SqlConnection(connectionString);
    string command = string.Format("select {0}, {1} from {2} order by {0}", columnId, columnName, tableName);
    SqlCommand comm = new SqlCommand(command, conn);

    conn.Open();

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

    while(loop)
    { 
#>
        /// <summary>
        /// <#= reader[columnName] #> configuration setting.
        /// </summary>
        <#= reader[columnName] #> = <#= reader[columnId] #><# loop = reader.Read(); #><#= loop ? ",\r\n" : string.Empty #> 
<#  } 
#>  }
} 
<#+     private string Pascalize(object value)
        {
            Regex rx = new Regex(@"(?:^|[^a-zA-Z]+)(?<first>[a-zA-Z])(?<reminder>[a-zA-Z0-9]+)");
            return rx.Replace(value.ToString(), m => m.Groups["first"].ToString().ToUpper() + m.Groups["reminder"].ToString().ToLower());
        }      

        private string GetSubNamespace()
        {
            Regex rx = new Regex(@"(?:.+Services\s)");
            string path = Path.GetDirectoryName(Host.TemplateFile);
            return rx.Replace(path, string.Empty).Replace("\\", ".");
        }
#>

(заполните пространство имен вашего класса и строку подключения)

Затем вы можете просто добавить пустой файл TT с одной строкой <# @ include file = "EnumGenerator.ttinclude" #> `.Имя этого файла должно совпадать с именем таблицы, а столбцы этой таблицы должны называться «ID» и «NAME», если вы не измените его в классе генератора перечисления.

При каждом сохранении TTфайл, Enum будет сгенерирован автоматически.

7 голосов
/ 04 мая 2011

Почему бы не изменить SexType с перечисления на класс (или Struct) и заполнить список из вашей базы данных во время выполнения?

Ваша структура (или класс) будет выглядеть примерно так:

public struct SexType
{
  public string Type;
  public string Code;
  public int Value;
}

Тогда вы можете заполнить из вашей базы данных List<SexType> (или, если вы используете EF, вы можете раскрыть список сущностей типа SexType или того, что позволяет ваше решение).

Предполагая, что вы используете Linq и EF, активно загружайтесь при запуске приложения, и вы должны быть готовы к работе.

5 голосов
/ 04 мая 2011
var choices = Enumerable.Zip(
    Enum.GetNames(typeof(SexType)),
    Enum.GetValues(typeof(SexType)).Cast<SexType>(),
    (name, value) => Tuple.Create(name, value));
2 голосов
/ 04 мая 2011

Было бы проще, если бы вы просто назвали перечисляемые значения U, M, F и T. Если вы это сделали, статические методы класса Enum сделают всю работу за вас.

За исключением этого, вам нужно немного подумать, чтобы найти атрибуты Description

public IEnumerable<Tuple<string, int>> GetEnumValuePairs(Type enumType)
{
    if(!enumType.IsEnum)
    {
        throw new ArgumentException();
    }

    List<Tuple<string, int>> result = new List<Tuple<string, int>>();

    foreach (var value in Enum.GetValues(enumType))
    {
        string fieldName = Enum.GetName(enumType, value);

        FieldInfo fieldInfo = enumType.GetField(fieldName);
        var descAttribute = fieldInfo.GetCustomAttributes(false).Where(a => a is DescriptionAttribute).Cast<DescriptionAttribute>().FirstOrDefault();

        // ideally check if descAttribute is null here
        result.Add(Tuple.Create(descAttribute.Description, (int)value));
    }

    return result;
}
1 голос
/ 04 мая 2011
List<Tuple<int, string>> pairs = new List<Tuple<int,string>>();
     Type enumType = typeof(SexType);
     foreach (SexType enumValue in Enum.GetValues(enumType))
     {
        var customAttributes = enumType.GetField(Enum.GetName(enumType, enumValue)).
           GetCustomAttributes(typeof(DescriptionAttribute), false);

        DescriptionAttribute descriptionAttribute = 
           (DescriptionAttribute)customAttributes.FirstOrDefault(attr => 
              attr is DescriptionAttribute);

        if (descriptionAttribute != null)
           pairs.Add(new Tuple<int, string>((int)enumValue, descriptionAttribute.Description));
     }
1 голос
/ 04 мая 2011

Я бы использовал Текстовый шаблон T4 , который будет динамически генерировать перечисление для вас при обработке шаблона (при сохранении и / или сборке).

Затем этот шаблон (которыйвключает в себя код C #) сгенерирует для вас определение перечисления в файл .cs на основе содержимого вашей базы данных.

Конечно, это означает, что вам потребуется доступ к базе данных во время сохранения / компиляции,но это единственный способ гарантировать, что эти два находятся в очереди во время компиляции.

Во время выполнения вы можете выполнить однократную проверку базы данных, чтобы убедиться, что перечисление и значения вбаза данных в строке.

...