wake-up-neo.com

Wie erstelle ich eine Tabelle, die der Enumeration in EF6 Code First entspricht?

Ich habe MSDN über den Umgang mit Aufzählungen in Code First für EF6 gefolgt. Es funktionierte wie erwartet auf , aber das Feld in der erstellten Tabelle, das auf den Enumerator verweist, ist ein einfaches int.

Ich würde es vorziehen, eine zweite Tabelle zu erstellen, deren Werte der Definition des Enumerators in C # -Code folgen würden. Anstatt nur eine Tabelle zu erhalten, die Abteilung in dem Beispiel auf MSDN entspricht, möchte ich auch eine zweite Tabelle sehen, die mit den Elementen von Fakultät gefüllt wird.

public enum Faculty { Eng, Math, Eco }     

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

Bei der Erforschung des Problems bin ich auf eine Lösung gestoßen, die vorschlägt, eine Tabelle für die Aufzählung zu erstellen und diese explizit durch Seeding zu füllen. 

Es erscheint mir als umständlicher Ansatz und viel Arbeit, die automatisch erledigt werden sollte. Schließlich weiß das System, welche tatsächlichen Werte die Aufzählung ausmachen. Aus Sicht der DB sind es immer noch Datenzeilen, genau wie die Entitäten, die ich erstellt habe, aber aus Sicht von OO sind es nicht wirklich Daten - eher ein Typ (lose ausgedrückt), der eine begrenzte und vorher bekannte Anzahl von Zuständen annehmen kann .

Wird empfohlen, die Tabelle "manuell" auszufüllen?

46

Da EF es nicht automatisch behandelt, ja , ist dies der empfohlene Weg.

Ich möchte einige Änderungen in dem von Ihnen bereitgestellten Artikel vorschlagen.

Benennen Sie Ihre Aufzählung um

public enum FacultyEnum { Eng, Math, Eco }

Erstellen Sie eine Klasse, die die Tabelle darstellt

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

Ihr Modell verweist auf die Klasse

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

Erstellen Sie eine Erweiterungsmethode, um eine Beschreibung aus den Aufzählungs- und Startwerten zu erhalten

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

Fügen Sie den Seed in Configuration.cs hinzu

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

Fügen Sie die Enumentabelle in Ihrem DbContext hinzu

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

Benutze es

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

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

Erinnern

Wenn Sie in der Faculty-Eigenschaft kein virtuelles Element hinzufügen, müssen Sie die Include-Methode von DbSet verwenden, um das Eager Load durchzuführen

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

Wenn die Eigenschaft der Fakultät virtuell ist, verwenden Sie sie einfach

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

Basierend auf der Antwort von @Alberto Monteiro habe ich eine generische Klasse erstellt, falls Sie mehrere Tabellen haben. Der Hinweis hier ist, dass Id die Art von TEnum ist. Wenn Sie es auf diese Weise verwenden, können Sie Enum für die Deklaration des Eigenschaftstyps verwenden. 

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

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

Enum verwendet standardmäßig Ganzzahlen, so dass der Datenbankanbieter ein Feld mit dem Typ "int" erstellt.

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

Jetzt können Sie die EnumTable nur erben

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!
}

Werte setzten 

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

Wenn Sie Ihr Modell als POCO-Stil einfacher halten möchten, besteht die Möglichkeit, dass die Enum-Eigenschaft über eine Eigenschaft verfügt, die vom Entity-Framework als Ganzzahl gespeichert wird.

Wenn Sie möchten, dass die "Enum-Tabellen" in Ihrer Datenbank erstellt und aktualisiert werden, empfiehlt es sich, das Nuget-Paket https://github.com/timabell/ef-enum-to-lookup zu verwenden und darin zu verwenden eine EF Migration-Seed-Methode zum Beispiel:

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

Dadurch wird die "Shape" -Tabelle mit zwei Zeilen mit den Bezeichnungen "Square" und "Round" erstellt, mit der entsprechenden Fremdschlüsseleinschränkung in der Tabelle "Foo".

5
Michael

Hervorragend @AlbertoMonterio! Damit dies mit ASP.NET CORE/EF Core funktioniert, habe ich einige Anpassungen an der Alberto-Lösung vorgenommen.

Der Kürze halber werden nachfolgend nur die Änderungen gezeigt:

Erstellen Sie eine Erweiterungsmethode, um eine Beschreibung aus Enum- und Seed-Werten abzurufen

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

Fügen Sie den Startwert in Configuration.cs hinzu

Fügen Sie Seeding zu OnModelCreating von DataContext hinzu

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

Sie sollten : byte vor der enum-Deklaration hinzufügen:

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

In der Datenbank sollten Sie TINYINT sehen und kein Casting nötig sein!

0
محمد رضا

Ein weiterer Ansatz, der in EF Core funktioniert (und sich für mich einfacher anfühlt):

Deine Enum

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

DB-Tabellen

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

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

Im Code verwende ich grundsätzlich nur die Aufzählung Color (niemals ColorDto). Aber es ist immer noch schön, die Tabelle 'Colors' mit einem FK in der Tabelle 'CustomObjects' für SQL-Abfragen und -Ansichten zu haben.

0

Alberto Monteiro hat dies sehr gut beantwortet. Ich musste ein paar Anpassungen vornehmen, damit es mit EF Core funktioniert. 

Benennen Sie Ihr Enum um und fügen Sie Beschriftungsdekoratoren hinzu

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

Erstellen Sie eine Klasse, die die Tabelle darstellt

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

Ihr Modell verweist auf die Klasse

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

Erstellen Sie eine Erweiterungsmethode, um eine Beschreibung aus den Aufzählungs- und Startwerten zu erhalten

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

Fügen Sie den Samen in YourDbContext.cs hinzu

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

Fügen Sie die Enumentabelle in Ihrem DbContext hinzu

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

Benutze es

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

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

Erinnern

Wenn Sie in der Faculty-Eigenschaft kein virtuelles Element hinzufügen, müssen Sie die Include-Methode von DbSet verwenden, um das Eager Load durchzuführen

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

Wenn die Eigenschaft der Fakultät virtuell ist, verwenden Sie sie einfach

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