如何使用 EF 5 存储枚举列表
Posted
技术标签:
【中文标题】如何使用 EF 5 存储枚举列表【英文标题】:How to store a List of enum using EF 5 【发布时间】:2021-12-28 18:48:05 【问题描述】:我目前正在做一个代码优先的小项目:我必须创建一个电影数据库,并且像往常一样,每部电影可以有多个流派 (m:n)。由于流派是不变的,我决定创建一个包含所有流派的枚举类型。
在Movie
表中,我有一个流派列表(枚举)。显然这是错误的,因为您无法在数据库中存储枚举列表。
于是我开始寻找答案。我遇到了很多解决方案,不幸的是它们都没有真正帮助我。所以我决定问这个问题。我知道这可能是重复的,但其他解决方案并没有真正的帮助。
我发现的一些解决方案是Flags
和SmartEnum
。
我都试过了,但它并没有真正奏效。您能否看看我的代码并告诉我我做错了什么,或者是否有其他方法可以转换枚举列表。
电影:
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 值保留在代码库中而不是数据库中。基本策略不会改变。 “因为流派是不变的”——它们不是。新流派偶尔会出现。 Cyberpunkg 在我上学的时候是新的,那实际上可以追溯到一本名为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 => 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
问题,而是您想要建模多对多关系的事实。
如果您需要存储int
或string
的列表,则相同。
要么你打破良好的做法,并在你的电影表中存储同一部电影的几行(每个类型的电影一个行)。在这种情况下,您将直接在数据库中存储enum
。那真的很丑。
或者您的模型正确,这意味着您需要一个表格来存储您的流派。该表可以以经典方式构建,具有主键 (Id) 和值(流派枚举值)。您甚至可以使用 enum
的 int
表示形式作为主键,但我认为这样做没有任何好处。
要使用 EF Core 将 enum
s 作为属性存储在数据库中,请使用预定义或内置转换器。
见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<Genre>
和DbSet<Movie>
,以及Movie
和Genre
之间的多对多关系。
但我不确定我是否会将其用作解决方案。未来可能需要添加新的流派,使用简单的string
而不是enum
可能会更好。
编辑
您可以存储一个string
,其中包含您的enum
列表的某种形式的序列化:
例如“西方;戏剧”,或 JSON 表示
可以反序列化它,但这又是丑陋的。
【讨论】:
以上是关于如何使用 EF 5 存储枚举列表的主要内容,如果未能解决你的问题,请参考以下文章