Подтвердить что ты не робот

Как создать таблицу, соответствующую перечислению в EF6 Code First?

Я следовал MSDN о том, как обрабатывать перечисления в Code First для EF6. Это сработало, как и предполагалось, но поле в созданной таблице, которое ссылается на перечислитель, является простым int.

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

public enum Faculty { Eng, Math, Eco }     

public partial class Department 
{ 
  [Key] public Guid ID { get; set; } 
  [Required] public Faculty Name { get; set; } 
}

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

Это кажется мне громоздким подходом и большой работой, которая должна быть выполнена автоматически. В конце концов, система знает, какие фактические значения составляют перечисление. С точки зрения БД, это все еще строки данных, так же как и объекты, которые я создаю, но с точки зрения ОО, это на самом деле не данные, а тип (слабо выраженный), который может принимать конечное и заранее известное число состояний.

Рекомендуется ли подход к заполнению таблицы "вручную"?

4b9b3361

Ответ 1

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

Я предлагаю некоторые изменения в статье, которую вы предоставили.

Переименуйте перечисление

public enum FacultyEnum { Eng, Math, Eco }

Создайте класс, представляющий таблицу

public class Faculty
{
    private Faculty(FacultyEnum @enum)
    {
        Id = (int)@enum;
        Name = @enum.ToString();
        Description = @enum.GetEnumDescription();
    }

    protected Faculty() { } //For EF

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public string Name { get; set; }

    [MaxLength(100)]
    public string Description { get; set; }

    public static implicit operator Faculty(FacultyEnum @enum) => new Faculty(@enum);

    public static implicit operator FacultyEnum(Faculty faculty) => (FacultyEnum)faculty.Id;
}

Ссылка на модель класса

public class ExampleClass
{
    public virtual Faculty Faculty { get; set; }
}

Создайте метод расширения, чтобы получить описание из перечислений и значений семян

using System;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;

public static class Extensions
{
    public static string GetEnumDescription<TEnum>(this TEnum item)
        => item.GetType()
               .GetField(item.ToString())
               .GetCustomAttributes(typeof(DescriptionAttribute), false)
               .Cast<DescriptionAttribute>()
               .FirstOrDefault()?.Description ?? string.Empty;

    public static void SeedEnumValues<T, TEnum>(this IDbSet<T> dbSet, Func<TEnum, T> converter)
        where T : class => Enum.GetValues(typeof(TEnum))
                               .Cast<object>()
                               .Select(value => converter((TEnum)value))
                               .ToList()
                               .ForEach(instance => dbSet.AddOrUpdate(instance));
}

Добавить семя в Configuration.cs

protected override void Seed(Temp.MyClass context)
{
    context.Facultys.SeedEnumValues<Faculty, FacultyEnum>(@enum => @enum);
    context.SaveChanges();
}

Добавьте таблицу перечисления в свой DbContext

public class MyClass : DbContext
{
    public DbSet<ExampleClass> Examples { get; set; }
    public DbSet<Faculty> Facultys { get; set; }
}

Используйте его

var example = new ExampleClass();
example.Faculty = FacultyEnum.Eng;

if (example.Faculty == FacultyEnum.Math)
{
    //code
}

Чтобы запомнить

Если вы не добавляете виртуальную собственность в свойство Faculty, вы должны использовать метод Include из DbSet для выполнения Eager Load

var exampleFromDb = dbContext.Examples.Include(x => x.Faculty).SingleOrDefault(e => e.Id == 1);
if (example.Faculty == FacultyEnum.Math)
{
    //code
}

Если свойство Faculty является виртуальным, просто используйте его

var exampleFromDb = dbContext.Examples.Find(1);
if (example.Faculty == FacultyEnum.Math)
{
    //code
}

Ответ 2

Основываясь на ответе @Alberto Monteiro, я создал общий класс в случае, если у вас несколько таблиц. Обратите внимание, что Id - это тип TEnum. Использование его таким образом обеспечит возможность использования Enum для объявления типа свойства.

public class Question
{
    public QuestionTypeEnum QuestionTypeId { get; set; } // field property

    public QuestionType QuestionType { get; set; } // navigation property
}

По умолчанию Enum использует целые числа, поэтому поставщик db создаст поле с типом "int".

EnumTable.cs

    public class EnumTable<TEnum>
        where TEnum : struct
    {
        public TEnum Id { get; set; }
        public string Name { get; set; }

        protected EnumTable() { }

        public EnumTable(TEnum enumType)
        {
            ExceptionHelpers.ThrowIfNotEnum<TEnum>();

            Id = enumType;
            Name = enumType.ToString();
        }

        public static implicit operator EnumTable<TEnum>(TEnum enumType) => new EnumTable<TEnum>(enumType);
        public static implicit operator TEnum(EnumTable<TEnum> status) => status.Id;
    }

ExceptionHelpers.cs

static class ExceptionHelpers
{
    public static void ThrowIfNotEnum<TEnum>()
        where TEnum : struct
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new Exception($"Invalid generic method argument of type {typeof(TEnum)}");
        }
    }
}

Теперь вы можете наследовать EnumTable

public enum QuestionTypeEnum
{
    Closed = 0,
    Open = 1
}

public class QuestionType : EnumTable<QuestionTypeEnum>
{
    public QuestionType(QuestionTypeEnum enumType) : base(enumType)
    {
    }

    public QuestionType() : base() { } // should excplicitly define for EF!
}

Выделите значения

context.QuestionTypes.SeedEnumValues<QuestionType, QuestionTypeEnum>(e => new QuestionType(e));

Ответ 3

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

Затем, если вы хотите, чтобы "таблицы enum" создавались и обновлялись в вашей БД, я рекомендую использовать пакет nuget https://github.com/timabell/ef-enum-to-lookup и использовать его в начальном файле EF Migration метод например:

public enum Shape
{
    Square,
    Round
}

public class Foo
{
    public int Id { get; set; }
    public Shape Shape { get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }
}

using(var context = new MyDbContext())
{
    var enumToLookup = new EnumToLookup
    {
        TableNamePrefix = string.Empty,
        NameFieldLength = 50,
        UseTransaction = true
    };
    enumToLookup.Apply(context);
}

Это создаст таблицу "Shape" с 2 строками с именами Square и Round с соответствующим ограничением внешнего ключа в таблице "Foo"

Ответ 4

Отлично @AlbertoMonterio! Чтобы заставить его работать с ASP.NET CORE/EF Core, я внес несколько изменений в решение Alberto.

Для краткости ниже показаны только модификации:

Создайте метод расширения, чтобы получить описание из значений enum и seed

using System;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using Microsoft.EntityFrameworkCore; //added
using Microsoft.EntityFrameworkCore.Metadata.Builders; //added

public static class Extensions
{
    //unchanged from alberto answer
    public static string GetEnumDescription<TEnum>(this TEnum item)
        => item.GetType()
               .GetField(item.ToString())
               .GetCustomAttributes(typeof(DescriptionAttribute), false)
               .Cast<DescriptionAttribute>()
               .FirstOrDefault()?.Description ?? string.Empty;

    //changed
    public static void SeedEnumValues<T, TEnum>(this ModelBuilder mb, Func<TEnum, T> converter)
    where T : class => Enum.GetValues(typeof(TEnum))
                           .Cast<object>()
                           .Select(value => converter((TEnum)value))
                           .ToList()
                            .ForEach(instance => mb.Entity<T>().HasData(instance));
}

Добавьте начальное число в Configuration.cs

Добавить OnModelCreating в OnModelCreating DataContext

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.SeedEnumValues<Faculty, EnumEntityRole>(e => e);
}

Ответ 5

Другой подход, который работает (и мне кажется проще) в EF Core:

Ваш Enum

public enum Color
{
    Red = 1,
    Blue = 2,
    Green = 3,
}

Таблицы БД

public class CustomObjectDto
{
    public int ID { get; set; }

    // ... other props

    public Color ColorID { get; set; }
    public ColorDto ColorDto { get; set; }
}

public class ColorDto
{
    public Color ID { get; set; }
    public string Name { get; set; }
}

Ваш DbContext

public class Db : DbContext
{
    public Db(DbContextOptions<Db> options) : base(options) { }

    public DbSet<CustomObjectDto> CustomObjects { get; set; }
    public DbSet<ColorDto> Colors { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Seed database with all Colors
        foreach (Color color in Enum.GetValues(typeof(Color)).Cast<Color>())
        {
            ColorDto colorDto = new ColorDto
            {
                ID = color,
                Name = color.ToString(),
            };

            modelBuilder.Entity<ColorDto>().HasData(colorDto);
        }
    }
}

В коде я в основном использую только перечисление Color (никогда ColorDto). Но все же хорошо иметь таблицу "Colors" с FK в таблице "CustomObjects" для запросов и представлений sql.

Ответ 6

Вы должны добавить : byte перед объявлением enum:

enum MyFieldEnum : byte{
    one = 1,
    two = 2,
    three = 4
}

В базе данных вы должны увидеть TINYINT и не нужно кастинг!

Ответ 7

Альберто Монтейро ответил на это очень хорошо. Мне пришлось внести несколько корректировок, чтобы заставить его работать с ядром EF.

Переименуй свой enum и добавь описание декораторов

public enum FacultyEnum 
{
    [Description("English Professor")]
    Eng, 
    [Description("Math Professor")]
    Math, 
    [Description("Economics Professor")]
    Eco 
}

Создайте класс, который представляет таблицу

public class Faculty
{
    private Faculty(FacultyEnum @enum)
    {
        Id = (int)@enum;
        Name = @enum.ToString();
        Description = @enum.GetEnumDescription();
    }

    protected Faculty() { } //For EF

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public string Name { get; set; }

    [MaxLength(100)]
    public string Description { get; set; }

    public static implicit operator Faculty(FacultyEnum @enum) => new Faculty(@enum);

    public static implicit operator FacultyEnum(Faculty faculty) => (FacultyEnum)faculty.Id;
}

Ваша модель ссылается на класс

public class ExampleClass
{
    public virtual Faculty Faculty { get; set; }
}

Создайте метод расширения, чтобы получить описание из значений enum и seed

using System;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;

public static class Extensions
{
    public static string GetEnumDescription<TEnum>(this TEnum item)
        => item.GetType()
               .GetField(item.ToString())
               .GetCustomAttributes(typeof(DescriptionAttribute), false)
               .Cast<DescriptionAttribute>()
               .FirstOrDefault()?.Description ?? string.Empty;
}

Добавьте начальное число в YourDbContext.cs

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Faculty>().HasData(FacultyEnum.Eng, FacultyEnum.Math, FacultyEnum.Eco);
    }

Добавьте таблицу enum в ваш DbContext

public class MyClass : DbContext
{
    public DbSet<ExampleClass> Examples { get; set; }
    public DbSet<Faculty> Facultys { get; set; }
}

Используй это

var example = new ExampleClass();
example.Faculty = FacultyEnum.Eng;

if (example.Faculty == FacultyEnum.Math)
{
    //code
}

Помнить

Если вы не добавляете виртуальное в свойство Faculty, вы должны использовать метод Include из DbSet, чтобы выполнить Eager Load

var exampleFromDb = dbContext.Examples.Include(x => x.Faculty).SingleOrDefault(e => e.Id == 1);
if (example.Faculty == FacultyEnum.Math)
{
    //code
}

Если свойство Faculty является виртуальным, просто используйте его

var exampleFromDb = dbContext.Examples.Find(1);
if (example.Faculty == FacultyEnum.Math)
{
    //code
}