Как создать таблицу, соответствующую перечислению в 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; }
}
Исследуя проблему, я наткнулся на решение, которое предлагает создать таблицу для перечисления и заполнить ее явным образом путем заполнения.
Это кажется мне громоздким подходом и большой работой, которая должна быть выполнена автоматически. В конце концов, система знает, какие фактические значения составляют перечисление. С точки зрения БД, это все еще строки данных, так же как и объекты, которые я создаю, но с точки зрения ОО, это на самом деле не данные, а тип (слабо выраженный), который может принимать конечное и заранее известное число состояний.
Рекомендуется ли подход к заполнению таблицы "вручную"?
Ответы
Ответ 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
}