如何使用 EF 5 存储枚举列表

Posted

技术标签:

【中文标题】如何使用 EF 5 存储枚举列表【英文标题】:How to store a List of enum using EF 5 【发布时间】:2021-12-28 18:48:05 【问题描述】:

我目前正在做一个代码优先的小项目:我必须创建一个电影数据库,并且像往常一样,每部电影可以有多个流派 (m:n)。由于流派是不变的,我决定创建一个包含所有流派的枚举类型。

Movie 表中,我有一个流派列表(枚举)。显然这是错误的,因为您无法在数据库中存储枚举列表。

于是我开始寻找答案。我遇到了很多解决方案,不幸的是它们都没有真正帮助我。所以我决定问这个问题。我知道这可能是重复的,但其他解决方案并没有真正的帮助。

我发现的一些解决方案是FlagsSmartEnum

我都试过了,但它并没有真正奏效。您能否看看我的代码并告诉我我做错了什么,或者是否有其他方法可以转换枚举列表。

电影:

class Serie

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id  get; set; 

    [Required]
    [MaxLength(255)]
    public string Titel  get; set;  = "";

    public virtual List<Genres> Genres  get;  = new();

类型:

public sealed class Genres : SmartEnum<Genres>

    public static readonly Genres Drama = new(name: "Drama", value: 1);
    public static readonly Genres Crime = new(name: "Crime", value: 2);
    ...

    private Genres(string name, int value) : base(name, value)
     

PS:我知道我可以通过额外的课程来做到这一点,但我想用枚举来做到这一点。

【问题讨论】:

在 3nf 中,您将拥有一个充当多对多的表,我们称之为 MovieGenres。这将具有 MovieId 和 GenreId。您没有 Genre 表并不重要,但它会使您的代码和架构更容易阅读,我个人的偏好是将其存储在数据库中以便您可以更改它(例如更改名称或添加一种流派)。 我知道我可以通过额外的课程来做到这一点,但我想用枚举来做到这一点。 就像我说的,这是我的偏好。我没有说你必须那样做。同样,使用表来存储关系并将其视为多对多,即使一端没有 FK,因为 Genre 值保留在代码库中而不是数据库中。基本策略不会改变。 “因为流派是不变的”——它们不是。新流派偶尔会出现。 Cyber​​punkg 在我上学的时候是新的,那实际上可以追溯到一本名为cyberpunk的书。 你可以看看创建enum lookup table 【参考方案1】:

EF 可以很好地与 Enums 一起使用,但我不认为您使用电影 Genres 的示例是枚举的一个很好的候选者,因为可以添加新的 Genres。 SmartEntity 的使用只是一个类包装器。

您的流派示例是多对多关系,因此查看 DB 端,您会看到如下内容:

Genres
 - GenreId (PK)
 - Name

Series
 - SeriesId (PK)

SeriesGenres
 - SeriesId (PK, FK)
 - GenreId (PK, FK)
 

Genre 是一个简单的类,因此用 SmartEnum 之类的结构类包装它并没有真正的好处。在一天结束时,您会希望 EF 像对待任何其他实体一样对待它,以便您可以有效地查询它。 EF Core 5 可以适应多对多关系,而无需定义 SeriesGenre 实体,其中一个 Series 仅具有 Genre 的集合,然后配置有 HasMany(x =&gt; x.Genres).WithMany() 关系以及 SeriesGenre 表和 FK 的配置。 EF 可以在幕后处理其余的工作。

枚举的一个更好的例子是状态,您需要一组固定的状态,业务规则逻辑将根据这些状态进行操作,并且除非系统更新以考虑新状态,否则它们不会改变。例如:

///<summary>
/// Enumeration for order statuses. Ensure this matches the OrderStatuses table.
///</summary>
public enum OrderStatus

    None = 0,
    Pending = 1,
    Packing = 2,
    Review = 3,
    Shipped = 4,
    Delivered = 100

在这种情况下,订单将记录任何时间点的状态。业务逻辑将取决于当前状态状态。我们希望将订单记录存储为状态,但仍要确保我们的数据库具有引用完整性,因此我们将有一个状态表,其中对应的 OrderStatusIds 与枚举中的内容匹配。然后,Orders 表中的 OrderStatusId 列可以对 OrderStatuses 表具有 FK 约束,以保持数据库中的引用完整性。 OrderStatus 永远不会得到实体声明。

我这样做的主要建议是:

枚举持有表 PK 列不应使用自增,而应使用显式 ID 来匹配 Enum。 同样,枚举应该对每个值都是显式的,而不是依赖于自动增量。 应记录表和枚举以引用它们的相互依赖关系。

【讨论】:

【参考方案2】:

这并不是真正的enum 问题,而是您想要建模多对多关系的事实。 如果您需要存储intstring 的列表,则相同。

要么你打破良好的做法,并在你的电影表中存储同一部电影的几行(每个类型的电影一个行)。在这种情况下,您将直接在数据库中存储enum。那真的很丑。

或者您的模型正确,这意味着您需要一个表格来存储您的流派。该表可以以经典方式构建,具有主键 (Id) 和值(流派枚举值)。您甚至可以使用 enumint 表示形式作为主键,但我认为这样做没有任何好处。

要使用 EF Core 将 enums 作为属性存储在数据库中,请使用预定义或内置转换器。

见https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=fluent-api#pre-defined-conversions

您需要一个流派枚举和一个用于映射的流派类:

public enum GenreEnum

    Drama,
    Western,
    //...


public class Genre

    public int Id  get; set; 
    public GenreEnum Name  get; set; 

    // And other properties needed.

然后

protected override void OnModelCreating(ModelBuilder modelBuilder)

    modelBuilder
        .Entity<Genre>()
        .Property(e => e.Name)
        .HasConversion<string>();

这甚至可以通过一个属性更简单地完成:

public enum GenreEnum

    Drama,
    Western,
    //...


public class Genre

    public int Id  get; set; 

    [Column(TypeName = "nvarchar(24)")]
    public GenreEnum Name  get; set; 

    // And other properties needed.

当然,您需要DbSet&lt;Genre&gt;DbSet&lt;Movie&gt;,以及MovieGenre 之间的多对多关系。

但我不确定我是否会将其用作解决方案。未来可能需要添加新的流派,使用简单的string 而不是enum 可能会更好。

编辑

您可以存储一个string,其中包含您的enum列表的某种形式的序列化:

例如“西方;戏剧”,或 JSON 表示

可以反序列化它,但这又是丑陋的。

【讨论】:

以上是关于如何使用 EF 5 存储枚举列表的主要内容,如果未能解决你的问题,请参考以下文章

如何在 .net 核心上使用 ef 核心在 postgresql(db first)中映射枚举

如何从可枚举转换为特定模型

如何在 EF 中获取实体更改增量?

如何在EF6 Code First中创建与枚举对应的表?

如何获取物理存储设备列表?

EF 代码生成:如何让系统枚举作为 EdmEnumType 工作?